您的位置:新葡亰496net > 新葡亰官网 > 手势解锁,爱心背景特效

手势解锁,爱心背景特效

发布时间:2019-06-20 08:35编辑:新葡亰官网浏览(179)

    用 canvas 实现 Web 手势解锁

    2017/04/04 · HTML5 · Canvas

    原文出处: songjz   

    最近参加 360 暑假的前端星计划,有一个在线作业,截止日期是 3 月 30 号,让手动实现一个 H5 手势解锁,具体的效果就像原生手机的九宫格解锁那样。

    图片 1

    实现的最终效果就像下面这张图这样:

    图片 2

    基本要求是这样的:将密码保存到 localStorage 里,开始的时候会从本地读取密码,如果没有就让用户设置密码,密码最少为五位数,少于五位要提示错误。需要对第一次输入的密码进行验证,两次一样才能保持,然后是验证密码,能够对用户输入的密码进行验证。

    HTML5实现屏幕手势解锁

    2015/07/18 · HTML5 · 1 评论 · 手势解锁

    原文出处: AlloyTeam   

    效果展示

    图片 3

    实现原理 利用HTML5的canvas,将解锁的圈圈划出,利用touch事件解锁这些圈圈,直接看代码。

    JavaScript

    function createCircle() {// 创建解锁点的坐标,根据canvas的大小来平均分配半径 var n = chooseType;// 画出n*n的矩阵 lastPoint = []; arr = []; restPoint = []; r = ctx.canvas.width / (2 4 * n);// 公式计算 半径和canvas的大小有关 for (var i = 0 ; i < n ; i ) { for (var j = 0 ; j < n ; j ) { arr.push({ x: j * 4 * r 3 * r, y: i * 4 * r 3 * r }); restPoint.push({ x: j * 4 * r 3 * r, y: i * 4 * r 3 * r }); } } //return arr; }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function createCircle() {// 创建解锁点的坐标,根据canvas的大小来平均分配半径
     
            var n = chooseType;// 画出n*n的矩阵
            lastPoint = [];
            arr = [];
            restPoint = [];
            r = ctx.canvas.width / (2 4 * n);// 公式计算 半径和canvas的大小有关
            for (var i = 0 ; i < n ; i ) {
                for (var j = 0 ; j < n ; j ) {
                    arr.push({
                        x: j * 4 * r 3 * r,
                        y: i * 4 * r 3 * r
                    });
                    restPoint.push({
                        x: j * 4 * r 3 * r,
                        y: i * 4 * r 3 * r
                    });
                }
            }
            //return arr;
        }

    canvas里的圆圈画好之后可以进行事件绑定

    JavaScript

    function bindEvent() { can.addEventListener("touchstart", function (e) { var po = getPosition(e); console.log(po); for (var i = 0 ; i < arr.length ; i ) { if (Math.abs(po.x - arr[i].x) < r && Math.abs(po.y - arr[i].y) < r) { // 用来判断起始点是否在圈圈内部 touchFlag = true; drawPoint(arr[i].x,arr[i].y); lastPoint.push(arr[i]); restPoint.splice(i,1); break; } } }, false); can.addEventListener("touchmove", function (e) { if (touchFlag) { update(getPosition(e)); } }, false); can.addEventListener("touchend", function (e) { if (touchFlag) { touchFlag = false; storePass(lastPoint); setTimeout(function(){ init(); }, 300); } }, false); }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    function bindEvent() {
            can.addEventListener("touchstart", function (e) {
                 var po = getPosition(e);
                 console.log(po);
                 for (var i = 0 ; i < arr.length ; i ) {
                    if (Math.abs(po.x - arr[i].x) < r && Math.abs(po.y - arr[i].y) < r) { // 用来判断起始点是否在圈圈内部
     
                        touchFlag = true;
                        drawPoint(arr[i].x,arr[i].y);
                        lastPoint.push(arr[i]);
                        restPoint.splice(i,1);
                        break;
                    }
                 }
             }, false);
             can.addEventListener("touchmove", function (e) {
                if (touchFlag) {
                    update(getPosition(e));
                }
             }, false);
             can.addEventListener("touchend", function (e) {
                 if (touchFlag) {
                     touchFlag = false;
                     storePass(lastPoint);
                     setTimeout(function(){
     
                        init();
                    }, 300);
                 }
     
             }, false);
        }

    接着到了最关键的步骤绘制解锁路径逻辑,通过touchmove事件的不断触发,调用canvas的moveTo方法和lineTo方法来画出折现,同时判断是否达到我们所画的圈圈里面,其中lastPoint保存正确的圈圈路径,restPoint保存全部圈圈去除正确路径之后剩余的。 Update方法:

    JavaScript

    function update(po) {// 核心变换方法在touchmove时候调用 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); for (var i = 0 ; i < arr.length ; i ) { // 每帧先把面板画出来 drawCle(arr[i].x, arr[i].y); } drawPoint(lastPoint);// 每帧花轨迹 drawLine(po , lastPoint);// 每帧画圆心 for (var i = 0 ; i < restPoint.length ; i ) { if (Math.abs(po.x - restPoint[i].x) < r && Math.abs(po.y - restPoint[i].y) < r) { drawPoint(restPoint[i].x, restPoint[i].y); lastPoint.push(restPoint[i]); restPoint.splice(i, 1); break; } } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function update(po) {// 核心变换方法在touchmove时候调用
            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
     
            for (var i = 0 ; i < arr.length ; i ) { // 每帧先把面板画出来
                drawCle(arr[i].x, arr[i].y);
            }
     
            drawPoint(lastPoint);// 每帧花轨迹
            drawLine(po , lastPoint);// 每帧画圆心
     
            for (var i = 0 ; i < restPoint.length ; i ) {
                if (Math.abs(po.x - restPoint[i].x) < r && Math.abs(po.y - restPoint[i].y) < r) {
                    drawPoint(restPoint[i].x, restPoint[i].y);
                    lastPoint.push(restPoint[i]);
                    restPoint.splice(i, 1);
                    break;
                }
            }
     
        }

    最后就是收尾工作,把路径里面的lastPoint保存的数组变成密码存在localstorage里面,之后就用来处理解锁验证逻辑了

    JavaScript

    function storePass(psw) {// touchend结束之后对密码和状态的处理 if (pswObj.step == 1) { if (checkPass(pswObj.fpassword, psw)) { pswObj.step = 2; pswObj.spassword = psw; document.getElementById('title').innerHTML = '密码保存成功'; drawStatusPoint('#2CFF26'); window.localStorage.setItem('passwordx', JSON.stringify(pswObj.spassword)); window.localStorage.setItem('chooseType', chooseType); } else { document.getElementById('title').innerHTML = '两次不一致,重新输入'; drawStatusPoint('red'); delete pswObj.step; } } else if (pswObj.step == 2) { if (checkPass(pswObj.spassword, psw)) { document.getElementById('title').innerHTML = '解锁成功'; drawStatusPoint('#2CFF26'); } else { drawStatusPoint('red'); document.getElementById('title').innerHTML = '解锁失败'; } } else { pswObj.step = 1; pswObj.fpassword = psw; document.getElementById('title').innerHTML = '再次输入'; } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    function storePass(psw) {// touchend结束之后对密码和状态的处理
            if (pswObj.step == 1) {
                if (checkPass(pswObj.fpassword, psw)) {
                    pswObj.step = 2;
                    pswObj.spassword = psw;
                    document.getElementById('title').innerHTML = '密码保存成功';
                    drawStatusPoint('#2CFF26');
                    window.localStorage.setItem('passwordx', JSON.stringify(pswObj.spassword));
                    window.localStorage.setItem('chooseType', chooseType);
                } else {
                    document.getElementById('title').innerHTML = '两次不一致,重新输入';
                    drawStatusPoint('red');
                    delete pswObj.step;
                }
            } else if (pswObj.step == 2) {
                if (checkPass(pswObj.spassword, psw)) {
                    document.getElementById('title').innerHTML = '解锁成功';
                    drawStatusPoint('#2CFF26');
                } else {
                    drawStatusPoint('red');
                    document.getElementById('title').innerHTML = '解锁失败';
                }
            } else {
                pswObj.step = 1;
                pswObj.fpassword = psw;
                document.getElementById('title').innerHTML = '再次输入';
            }
     
        }

    解锁组件

    将这个HTML5解锁写成了一个组件,放在

    二维码体验: 图片 4

     

    参考资料:

    1 赞 4 收藏 1 评论

    图片 5

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Happy Valentine's Day everyone !</title>
    </head>
    <style>
    body{
    overflow: hidden;
    margin: 0;
    }
    h1{
    position: fixed;
    top: 50%;
    left: 0;
    width: 100%;
    text-align: center;
    transform:translateY(-50%);
    font-family: 'Love Ya Like A Sister', cursive;
    font-size: 40px;
    color: #c70012;
    padding: 0 20px;
    }
    @media (min-width:1200px){
    h1{
    font-size: 60px;
    }
    }
    </style>
    <body>
    <canvas></canvas>
    <h1>canvas heart</h1>

    各位观众老爷大家好,欢迎收看内裤总动员之程序猿的IT程序大讲堂,今天给大家分享一个经典游戏.大家都应该玩过微信中的打飞机吧.今天就简单的用canvas画布来制作一个简单的游戏画面. 

    绝对值得看的来篇,哈哈。本人亲自完成,有错误请大家指出:
    现在的手机完美支持html5,所以如果手机端想要做个抽奖模块的话,用刮刮卡抽奖效果,相信这个互动体验是非常棒的
    ​ps:由于本人没有wp8系统的手机,所以没法兼容wp8系统的,目前完美兼容android,IOS
    如果要在pc浏览的话,得改下js,目前支持谷歌,火狐,ie>=10,如果网友想要的话我就去写个
    代码如下:

    H5 手势解锁

    扫码在线查看:

    图片 6

    或者点击查看手机版。

    项目 GitHub 地址,H5HandLock。

    首先,我要说明一下,对于这个项目,我是参考别人的,H5lock。

    我觉得一个比较合理的解法应该是利用 canvas 来实现,不知道有没有大神用 css 来实现。如果纯用 css 的话,可以将连线先设置 display: none,当手指划过的时候,显示出来。光设置这些应该就非常麻烦吧。

    之前了解过 canvas,但没有真正的写过,下面就来介绍我这几天学习 canvas 并实现 H5 手势解锁的过程。

    <script>
    var canvas = document.querySelector("canvas"),
    ctx = canvas.getContext("2d");


    复制代码 代码如下:

    准备及布局设置

    我这里用了一个比较常规的做法:

    (function(w){ var handLock = function(option){} handLock.prototype = { init : function(){}, ... } w.handLock = handLock; })(window) // 使用 new handLock({ el: document.getElementById('id'), ... }).init();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    (function(w){
      var handLock = function(option){}
     
      handLock.prototype = {
        init : function(){},
        ...
      }
     
      w.handLock = handLock;
    })(window)
     
    // 使用
    new handLock({
      el: document.getElementById('id'),
      ...
    }).init();

    常规方法,比较易懂和操作,弊端就是,可以被随意的修改。

    传入的参数中要包含一个 dom 对象,会在这个 dom 对象內创建一个 canvas。当然还有一些其他的 dom 参数,比如 message,info 等。

    关于 css 的话,懒得去新建文件了,就直接內联了。

    var ww,wh;

    首先画面布局

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0" />
    <title>eraser effect</title>
    <script type="text/javascript" src="jquery.core.js"></script>
    <style>
    #canvas {
    background:url(winning-ticket.jpg);<!--奖品图片-->
    width: 531px;
    height: 438px;
    }
    .before{
    background:none !important;
    }
    #canvas canvas {
    cursor: url("hand.png") 0 0, auto;<!--PC端的手势图片-->
    }
    </style>
    </head>
    <body oncontextmenu="return false;" onselectstart="return false;">
    <div id="canvas"></div>
    </body>
    <script type="text/javascript">
    (function() {
    window.onload = function(){
    /**判断浏览器是否支持canvas**/
    try{
    document.createElement('canvas').getContext('2d');
    }catch(e){
    var addDiv = document.createElement('div');
    alert('您的手机不支持刮刮卡效果哦~!');
    }
    };
    var u = navigator.userAgent,mobile = '';
    if(u.indexOf('iPhone') > -1) mobile = 'iphone';
    if(u.indexOf('Android') > -1 || u.indexOf('Linux') > -1) mobile = 'Android';
    function createCanvas(parent, width, height) {
    var canvas = {};
    canvas.node = document.createElement('canvas');
    canvas.context = canvas.node.getContext('2d');
    canvas.node.width = width || 100;
    canvas.node.height = height || 100;
    parent.appendChild(canvas.node);
    return canvas;
    }
    function init(container, width, height, fillColor, type) {
    var canvas = createCanvas(container, width, height);
    var ctx = canvas.context;
    // define a custom fillCircle method
    ctx.fillCircle = function(x, y, radius, fillColor) {
    this.fillStyle = fillColor;
    this.beginPath();
    this.moveTo(x, y);
    this.arc(x, y, radius, 0, Math.PI * 2, false);
    this.fill();
    };
    ctx.clearTo = function(fillColor) {
    ctx.fillStyle = fillColor;
    ctx.fillRect(0, 0, width, height);
    };
    ctx.clearTo(fillColor || "#ddd");
    canvas.node.addEventListener("touchstart",function(e){
    canvas.isDrawing = true;
    },false);
    canvas.node.addEventListener("touchend",function(e){
    canvas.isDrawing = false;
    },false);
    canvas.node.addEventListener("touchmove",function(e){
    if (!canvas.isDrawing) {
    return;
    }
    if(type == 'Android'){
    var x = e.changedTouches[0].pageX - this.offsetLeft;
    var y = e.changedTouches[0].pageY - this.offsetTop;
    }else{
    var x = e.pageX - this.offsetLeft;
    var y = e.pageY - this.offsetTop;
    }
    var radius = 20;
    var fillColor = '#ff0000';
    ctx.globalCompositeOperation = 'destination-out';
    ctx.fillCircle(x, y, radius, fillColor);
    },false);
    }
    var container = document.getElementById('canvas');
    init(container, 531, 438, '#ff0000', mobile);
    })();
    </script>
    </html>

    canvas

    function onResize(){
    ww = canvas.width = window.innerWidth;
    wh = canvas.height = window.innerHeight;
    }
    ctx.strokeStyle = "red";
    ctx.shadowBlur = 25;
    ctx.shadowColor = "hsla(0, 100%, 60%,0.5)";
    var precision = 100;
    var hearts = [];
    var mouseMoved = false;
    function onMove(e){
    mouseMoved = true;
    if(e.type === "touchmove"){
    hearts.push(new Heart(e.touches[0].clientX, e.touches[0].clientY));
    hearts.push(new Heart(e.touches[0].clientX, e.touches[0].clientY));
    }
    else{
    hearts.push(new Heart(e.clientX, e.clientY));
    hearts.push(new Heart(e.clientX, e.clientY));
    }
    }
    var Heart = function(x,y){
    this.x = x || Math.random()*ww;
    this.y = y || Math.random()*wh;
    this.size = Math.random()*2 1;
    this.shadowBlur = Math.random() * 10;
    this.speedX = (Math.random() 0.2-0.6) * 8;
    this.speedY = (Math.random() 0.2-0.6) * 8;
    this.speedSize = Math.random()*0.05 0.01;
    this.opacity = 1;
    this.vertices = [];
    for (var i = 0; i < precision; i ) {
    var step = (i / precision - 0.5) * (Math.PI * 2);
    var vector = {
    x : (15 * Math.pow(Math.sin(step), 3)),
    y : -(13 * Math.cos(step) - 5 * Math.cos(2 * step) - 2 * Math.cos(3 * step) - Math.cos(4 * step))
    }
    this.vertices.push(vector);
    }
    }
    Heart.prototype.draw = function(){
    this.size -= this.speedSize;
    this.x = this.speedX;
    this.y = this.speedY;
    ctx.save();
    ctx.translate(-1000,this.y);
    ctx.scale(this.size, this.size);
    ctx.beginPath();
    for (var i = 0; i < precision; i ) {
    var vector = this.vertices[i];
    ctx.lineTo(vector.x, vector.y);
    }
    ctx.globalAlpha = this.size;
    ctx.shadowBlur = Math.round((3 - this.size) * 10);
    ctx.shadowColor = "hsla(0, 100%, 60%,0.5)";
    ctx.shadowOffsetX = this.x 1000;
    ctx.globalCompositeOperation = "screen"
    ctx.closePath();
    ctx.fill()
    ctx.restore();
    };

    <canvas id = "canvas" width = "375" height = "627"></canvas>

    现在的手机完美支持html5,所以如果手机端想要做个抽奖模块的话,用刮刮卡抽...

    1. 学习 canvas 并搞定画圆

    MDN 上面有个简易的教程,大致浏览了一下,感觉还行。Canvas教程。

    先创建一个 canvas,然后设置其大小,并通过 getContext 方法获得绘画的上下文:

    var canvas = document.createElement('canvas'); canvas.width = canvas.height = width; this.el.appendChild(canvas); this.ctx = canvas.getContext('2d');

    1
    2
    3
    4
    5
    var canvas = document.createElement('canvas');
    canvas.width = canvas.height = width;
    this.el.appendChild(canvas);
     
    this.ctx = canvas.getContext('2d');

    然后呢,先画 n*n 个圆出来:

    JavaScript

    createCircles: function(){ var ctx = this.ctx, drawCircle = this.drawCircle, n = this.n; this.r = ctx.canvas.width / (2 4 * n) // 这里是参考的,感觉这种画圆的方式挺合理的,方方圆圆 r = this.r; this.circles = []; // 用来存储圆心的位置 for(var i = 0; i < n; i ){ for(var j = 0; j < n; j ){ var p = { x: j * 4 * r 3 * r, y: i * 4 * r 3 * r, id: i * 3 j } this.circles.push(p); } } ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 为了防止重复画 this.circles.forEach(function(v){ drawCircle(ctx, v.x, v.y); // 画每个圆 }) }, drawCircle: function(ctx, x, y){ // 画圆函数 ctx.strokeStyle = '#FFFFFF'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(x, y, this.r, 0, Math.PI * 2, true); ctx.closePath(); ctx.stroke(); }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    createCircles: function(){
      var ctx = this.ctx,
        drawCircle = this.drawCircle,
        n = this.n;
      this.r = ctx.canvas.width / (2 4 * n) // 这里是参考的,感觉这种画圆的方式挺合理的,方方圆圆
      r = this.r;
      this.circles = []; // 用来存储圆心的位置
      for(var i = 0; i < n; i ){
        for(var j = 0; j < n; j ){
          var p = {
            x: j * 4 * r 3 * r,
            y: i * 4 * r 3 * r,
            id: i * 3 j
          }
          this.circles.push(p);
        }
      }
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 为了防止重复画
      this.circles.forEach(function(v){
        drawCircle(ctx, v.x, v.y); // 画每个圆
      })
    },
     
    drawCircle: function(ctx, x, y){ // 画圆函数
      ctx.strokeStyle = '#FFFFFF';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.arc(x, y, this.r, 0, Math.PI * 2, true);
      ctx.closePath();
      ctx.stroke();
    }

    画圆函数,需要注意:如何确定圆的半径和每个圆的圆心坐标(这个我是参考的),如果以圆心为中点,每个圆上下左右各扩展一个半径的距离,同时为了防止四边太挤,四周在填充一个半径的距离。那么得到的半径就是 width / ( 4 * n 2),对应也可以算出每个圆所在的圆心坐标,也有一套公式,GET

    function render(a){
    requestAnimationFrame(render);

    样式布局

    2. 画线

    画线需要借助 touch event 来完成,也就是,当我们 touchstart 的时候,传入开始时的相对坐标,作为线的一端,当我们 touchmove 的时候,获得坐标,作为线的另一端,当我们 touchend 的时候,开始画线。

    这只是一个测试画线功能,具体的后面再进行修改。

    有两个函数,获得当前 touch 的相对坐标:

    getTouchPos: function(e){ // 获得触摸点的相对位置 var rect = e.target.getBoundingClientRect(); var p = { // 相对坐标 x: e.touches[0].clientX - rect.left, y: e.touches[0].clientY - rect.top }; return p; }

    1
    2
    3
    4
    5
    6
    7
    8
    getTouchPos: function(e){ // 获得触摸点的相对位置
      var rect = e.target.getBoundingClientRect();
      var p = { // 相对坐标
        x: e.touches[0].clientX - rect.left,
        y: e.touches[0].clientY - rect.top
      };
      return p;
    }

    画线:

    drawLine: function(p1, p2){ // 画线 this.ctx.beginPath(); this.ctx.lineWidth = 3; this.ctx.moveTo(p1.x, p2.y); this.ctx.lineTo(p.x, p.y); this.ctx.stroke(); this.ctx.closePath(); },

    1
    2
    3
    4
    5
    6
    7
    8
    drawLine: function(p1, p2){ // 画线
      this.ctx.beginPath();
      this.ctx.lineWidth = 3;
      this.ctx.moveTo(p1.x, p2.y);
      this.ctx.lineTo(p.x, p.y);
      this.ctx.stroke();
      this.ctx.closePath();
    },

    然后就是监听 canvas 的 touchstarttouchmove、和 touchend 事件了。

    hearts.push(new Heart())
    ctx.clearRect(0,0,ww,wh);
    for (var i = 0; i < hearts.length; i ) {
    hearts[i].draw();
    if(hearts[i].size <= 0){
    hearts.splice(i,1);
    i--;
    }
    }
    }
    onResize();
    window.addEventListener("mousemove", onMove);
    window.addEventListener("touchmove", onMove);
    window.addEventListener("resize", onResize);
    requestAnimationFrame(render);
    </script>
    </body>
    </html>

    *{

    margin: 0;

    padding: 0;

    }

    html,body{

    width: 100%;

    height: 100%;

    }

    #canvas{

    box-shadow: 0 0 50px gray;

    display: block;

    margin: 0 auto;

    }

    3. 画折线

    所谓的画折线,就是,将已经触摸到的点连起来,可以把它看作是画折线。

    首先,要用两个数组,一个数组用于已经 touch 过的点,另一个数组用于存储未 touch 的点,然后在 move 监听时候,对 touch 的相对位置进行判断,如果触到点,就把该点从未 touch 移到 touch 中,然后,画折线,思路也很简单。

    JavaScript

    drawLine: function(p){ // 画折线 this.ctx.beginPath(); this.ctx.lineWidth = 3; this.ctx.moveTo(this.touchCircles[0].x, this.touchCircles[0].y); for (var i = 1 ; i < this.touchCircles.length ; i ) { this.ctx.lineTo(this.touchCircles[i].x, this.touchCircles[i].y); } this.ctx.lineTo(p.x, p.y); this.ctx.stroke(); this.ctx.closePath(); },

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    drawLine: function(p){ // 画折线
      this.ctx.beginPath();
      this.ctx.lineWidth = 3;
      this.ctx.moveTo(this.touchCircles[0].x, this.touchCircles[0].y);
      for (var i = 1 ; i < this.touchCircles.length ; i ) {
        this.ctx.lineTo(this.touchCircles[i].x, this.touchCircles[i].y);
      }
      this.ctx.lineTo(p.x, p.y);
      this.ctx.stroke();
      this.ctx.closePath();
    },

    JavaScript

    judgePos: function(p){ // 判断 触点 是否在 circle 內 for(var i = 0; i < this.restCircles.length; i ){ temp = this.restCircles[i]; if(Math.abs(p.x - temp.x) < r && Math.abs(p.y - temp.y) < r){ this.touchCircles.push(temp); this.restCircles.splice(i, 1); this.touchFlag = true; break; } } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    judgePos: function(p){ // 判断 触点 是否在 circle 內
      for(var i = 0; i < this.restCircles.length; i ){
        temp = this.restCircles[i];
        if(Math.abs(p.x - temp.x) < r && Math.abs(p.y - temp.y) < r){
          this.touchCircles.push(temp);
          this.restCircles.splice(i, 1);
          this.touchFlag = true;
          break;
        }
      }
    }

    接下来呢,就是js原生代码方式去书写了,并且带上闭包方式进行书写.

    4. 标记已画

    前面已经说了,我们把已经 touch 的点(圆)放到数组中,这个时候需要将这些已经 touch 的点给标记一下,在圆心处画一个小实心圆:

    JavaScript

    drawPoints: function(){ for (var i = 0 ; i < this.touchCircles.length ; i ) { this.ctx.fillStyle = '#FFFFFF'; this.ctx.beginPath(); this.ctx.arc(this.touchCircles[i].x, this.touchCircles[i].y, this.r / 2, 0, Math.PI * 2, true); this.ctx.closePath(); this.ctx.fill(); } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    drawPoints: function(){
      for (var i = 0 ; i < this.touchCircles.length ; i ) {
        this.ctx.fillStyle = '#FFFFFF';
        this.ctx.beginPath();
        this.ctx.arc(this.touchCircles[i].x, this.touchCircles[i].y, this.r / 2, 0, Math.PI * 2, true);
        this.ctx.closePath();
        this.ctx.fill();
      }
    }

    同时添加一个 reset 函数,当 touchend 的时候调用,400ms 调用 reset 重置 canvas。

    到现在为止,一个 H5 手势解锁的简易版已经基本完成。

    //创建键值对图片路径

    var info = {

    "background":"img/background.png", //背景图片

    "plane": "img/plane.png", //飞机图片

    };

    //加载图片资源

    loading(info,{

    done:main

    });

    password

    为了要实现记住和重置密码的功能,把 password 保存在 localStorage 中,但首先要添加必要的 html 和样式。

    而在这里,我声明了一个函数loading 并用键值对的方式去传送图片路径和我的主调用main函数, 他将作为我的画面中对飞机和背景的处理主函数. 请往下看 loading.js文件.  我声明一个loading.js文件导入项目中.

    1. 添加 message 和 单选框

    为了尽可能的使界面简洁(越丑越好),直接在 body 后面添加了:

    XHTML

    <div id="select"> <div class="message">请输入手势密码</div> <div class="radio"> <label><input type="radio" name="pass">设置手势密码</label> <label><input type="radio" name="pass">验证手势密码</label> </div> </div>

    1
    2
    3
    4
    5
    6
    7
    <div id="select">
      <div class="message">请输入手势密码</div>
      <div class="radio">
        <label><input type="radio" name="pass">设置手势密码</label>
        <label><input type="radio" name="pass">验证手势密码</label>
      </div>
    </div>

    将添加到 dom 已 option 的形式传给 handLock:

    var el = document.getElementById('handlock'), info = el.getElementsByClassName('info')[0], select = document.getElementById('select'), message = select.getElementsByClassName('message')[0], radio = select.getElementsByClassName('radio')[0], setPass = radio.children[0].children[0], checkPass = radio.children[1].children[0]; new handLock({ el: el, info: info, message: message, setPass: setPass, checkPass: checkPass, n: 3 }).init();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var el = document.getElementById('handlock'),
      info = el.getElementsByClassName('info')[0],
      select = document.getElementById('select'),
      message = select.getElementsByClassName('message')[0],
      radio = select.getElementsByClassName('radio')[0],
      setPass = radio.children[0].children[0],
      checkPass = radio.children[1].children[0];
    new handLock({
      el: el,
      info: info,
      message: message,
      setPass: setPass,
      checkPass: checkPass,
      n: 3
    }).init();

    function loading(info,callbock){

    //图片总个数

    var allimg = 0;

    for(key in info){

        allimg ;

      }

    }

    2. info 信息显示

    关于 info 信息显示,自己写了一个悬浮窗,然后默认为 display: none,然后写了一个 showInfo 函数用来显示提示信息,直接调用:

    showInfo: function(message, timer){ // 专门用来显示 info var info = this.dom.info; info.innerHTML = message; info.style.display = 'block'; setTimeout(function(){ info.style.display = ''; }, 1000) }

    1
    2
    3
    4
    5
    6
    7
    8
    showInfo: function(message, timer){ // 专门用来显示 info
      var info = this.dom.info;
      info.innerHTML = message;
      info.style.display = 'block';
      setTimeout(function(){
        info.style.display = '';
      }, 1000)
    }

    关于 info 的样式,在 html 中呢。

    我通过info传送过来的图片路径进行遍历.

    3. 关于密码

    先不考虑从 localStorage 读取到情况,新加一个 lsPass 对象,专门用于存储密码,由于密码情况比较多,比如设置密码,二次确认密码,验证密码,为了方便管理,暂时设置了密码的三种模式,分别是:

    model:1 验证密码模式

    model:2 设置密码模式

    model:3 设置密码二次验证

    具体看下面这个图:

    图片 7

    这三种 model ,只要处理好它们之间如何跳转就 ok 了,即状态的改变。

    所以就有了 initPass:

    initPass: function(){ // 将密码初始化 this.lsPass = w.localStorage.getItem('HandLockPass') ? { model: 1, pass: w.localStorage.getItem('HandLockPass').split('-') } : { model: 2 }; this.updateMessage(); }, updateMessage: function(){ // 根据当前模式,更新 dom if(this.lsPass.model == 2){ this.dom.setPass.checked = true; this.dom.message.innerHTML = '请设置手势密码'; }else if(this.lsPass.model == 1){ this.dom.checkPass.checked = true; this.dom.message.innerHTML = '请验证手势密码'; }else if(this.lsPass.model = 3){ this.dom.setPass.checked = true; this.dom.message.innerHTML = '请再次输入密码'; } },

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    initPass: function(){ // 将密码初始化
      this.lsPass = w.localStorage.getItem('HandLockPass') ? {
        model: 1,
        pass: w.localStorage.getItem('HandLockPass').split('-')
      } : { model: 2 };
      this.updateMessage();
    },
     
    updateMessage: function(){ // 根据当前模式,更新 dom
      if(this.lsPass.model == 2){
        this.dom.setPass.checked = true;
        this.dom.message.innerHTML = '请设置手势密码';
      }else if(this.lsPass.model == 1){
        this.dom.checkPass.checked = true;
        this.dom.message.innerHTML = '请验证手势密码';
      }else if(this.lsPass.model = 3){
        this.dom.setPass.checked = true;
        this.dom.message.innerHTML = '请再次输入密码';
      }
    },

    有必要再来介绍一下 lsPass 的格式:

    this.lsPass = { model:1, // 表示当前的模式 pass: [0, 1, 2, 4, 5] // 表示当前的密码,可能不存在 }

    1
    2
    3
    4
    this.lsPass = {
      model:1, // 表示当前的模式
      pass: [0, 1, 2, 4, 5] // 表示当前的密码,可能不存在
    }

    因为之前已经有了一个基本的实现框架,现在只需要在 touchend 之后,写一个函数,功能就是先对当前的 model 进行判断,实现对应的功能,这里要用到 touchCircles 数组,表示密码的顺序:

    JavaScript

    checkPass: function(){ var succ, model = this.lsPass.model; //succ 以后会用到 if(model == 2){ // 设置密码 if(this.touchCircles.length < 5){ // 验证密码长度 succ = false; this.showInfo('密码长度至少为 5!', 1000); }else{ succ = true; this.lsPass.temp = []; // 将密码放到临时区存储 for(var i = 0; i < this.touchCircles.length; i ){ this.lsPass.temp.push(this.touchCircles[i].id); } this.lsPass.model = 3; this.showInfo('请再次输入密码', 1000); this.updateMessage(); } }else if(model == 3){// 确认密码 var flag = true; // 先要验证密码是否正确 if(this.touchCircles.length == this.lsPass.temp.length){ var tc = this.touchCircles, lt = this.lsPass.temp; for(var i = 0; i < tc.length; i ){ if(tc[i].id != lt[i]){ flag = false; } } }else{ flag = false; } if(!flag){ succ = false; this.showInfo('两次密码不一致,请重新输入', 1000); this.lsPass.model = 2; // 由于密码不正确,重新回到 model 2 this.updateMessage(); }else{ succ = true; // 密码正确,localStorage 存储,并设置状态为 model 1 w.localStorage.setItem('HandLockPass', this.lsPass.temp.join('-')); // 存储字符串 this.lsPass.model = 1; this.lsPass.pass = this.lsPass.temp; this.updateMessage(); } delete this.lsPass.temp; // 很重要,一定要删掉,bug }else if(model == 1){ // 验证密码 var tc = this.touchCircles, lp = this.lsPass.pass, flag = true; if(tc.length == lp.length){ for(var i = 0; i < tc.length; i ){ if(tc[i].id != lp[i]){ flag = false; } } }else{ flag = false; } if(!flag){ succ = false; this.showInfo('很遗憾,密码错误', 1000); }else{ succ = true; this.showInfo('恭喜你,验证通过', 1000); } } },

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    checkPass: function(){
      var succ, model = this.lsPass.model; //succ 以后会用到
      if(model == 2){ // 设置密码
        if(this.touchCircles.length < 5){ // 验证密码长度
          succ = false;
          this.showInfo('密码长度至少为 5!', 1000);
        }else{
          succ = true;
          this.lsPass.temp = []; // 将密码放到临时区存储
          for(var i = 0; i < this.touchCircles.length; i ){
            this.lsPass.temp.push(this.touchCircles[i].id);
          }
          this.lsPass.model = 3;
          this.showInfo('请再次输入密码', 1000);
          this.updateMessage();
        }
      }else if(model == 3){// 确认密码
        var flag = true;
        // 先要验证密码是否正确
        if(this.touchCircles.length == this.lsPass.temp.length){
          var tc = this.touchCircles, lt = this.lsPass.temp;
          for(var i = 0; i < tc.length; i ){
            if(tc[i].id != lt[i]){
              flag = false;
            }
          }
        }else{
          flag = false;
        }
        if(!flag){
          succ = false;
          this.showInfo('两次密码不一致,请重新输入', 1000);
          this.lsPass.model = 2; // 由于密码不正确,重新回到 model 2
          this.updateMessage();
        }else{
          succ = true; // 密码正确,localStorage 存储,并设置状态为 model 1
          w.localStorage.setItem('HandLockPass', this.lsPass.temp.join('-')); // 存储字符串
          this.lsPass.model = 1;
          this.lsPass.pass = this.lsPass.temp;
          this.updateMessage();
        }
        delete this.lsPass.temp; // 很重要,一定要删掉,bug
      }else if(model == 1){ // 验证密码
        var tc = this.touchCircles, lp = this.lsPass.pass, flag = true;
        if(tc.length == lp.length){
          for(var i = 0; i < tc.length; i ){
            if(tc[i].id != lp[i]){
              flag = false;
            }
          }
        }else{
          flag = false;
        }
        if(!flag){
          succ = false;
          this.showInfo('很遗憾,密码错误', 1000);
        }else{
          succ = true;
          this.showInfo('恭喜你,验证通过', 1000);
        }
      }
    },

    密码的设置要参考前面那张图,要时刻警惕状态的改变。

    //保存已经加载完成的图片 用键值对方式保存

    var loadingimg = {};

    // 图片已经加载完成的个数

    var loadingimgnumber = 0;

    4. 手动重置密码

    思路也很简单,就是添加点击事件,点击之后,改变 model 即可,点击事件如下:

    this.dom.setPass.addEventListener('click', function(e){ self.lsPass.model = 2; // 改变 model 为设置密码 self.updateMessage(); // 更新 message self.showInfo('请设置密码', 1000); }) this.dom.checkPass.addEventListener('click', function(e){ if(self.lsPass.pass){ self.lsPass.model = 1; self.updateMessage(); self.showInfo('请验证密码', 1000) }else{ self.showInfo('请先设置密码', 1000); self.updateMessage(); } })

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    this.dom.setPass.addEventListener('click', function(e){
      self.lsPass.model = 2; // 改变 model 为设置密码
      self.updateMessage(); // 更新 message
      self.showInfo('请设置密码', 1000);
    })
    this.dom.checkPass.addEventListener('click', function(e){
      if(self.lsPass.pass){
        self.lsPass.model = 1;
        self.updateMessage();
        self.showInfo('请验证密码', 1000)
      }else{
        self.showInfo('请先设置密码', 1000);
        self.updateMessage();
      }
    })

    ps:这里面还有几个小的 bug,因为 model 只有 3 个,所以设置的时候,当点击重置密码的时候,没有设置密码成功,又切成验证密码状态,此时无法提升沿用旧密码,原因是 model 只有三个

    在这里我又声明了加载后的图片键值对和加载完成后的图片个数.

    5. 添加 touchend 颜色变化

    实现这个基本上就大功告成了,这个功能最主要的是给用户一个提醒,若用户划出的密码符合规范,显示绿色,若不符合规范或错误,显示红色警告。

    因为之前已经设置了一个 succ 变量,专门用于重绘。

    JavaScript

    drawEndCircles: function(color){ // end 时重绘已经 touch 的圆 for(var i = 0; i < this.touchCircles.length; i ){ this.drawCircle(this.touchCircles[i].x, this.touchCircles[i].y, color); } }, // 调用 if(succ){ this.drawEndCircles('#2CFF26'); // 绿色 }else{ this.drawEndCircles('red'); // 红色 }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    drawEndCircles: function(color){ // end 时重绘已经 touch 的圆
      for(var i = 0; i < this.touchCircles.length; i ){
        this.drawCircle(this.touchCircles[i].x, this.touchCircles[i].y, color);
      }
    },
     
    // 调用
    if(succ){
      this.drawEndCircles('#2CFF26'); // 绿色
    }else{
      this.drawEndCircles('red'); // 红色
    }

    那么,一个可以演示的版本就生成了,尽管还存在一些 bug,随后会来解决。(详情分支 password)

    然后我将对info进行遍历 创建图片对象. 实现图片预加载.

    一些 bugs

    有些 bugs 在做的时候就发现了,一些 bug 后来用手机测试的时候才发现,比如,我用 chrome 的时候,没有察觉这个 bug,当我用 android 手机 chrome 浏览器测试的时候,发现当我 touchmove 向下的时候,会触发浏览器的下拉刷新,解决办法:加了一个 preventDefault,没想到居然成功了。

    this.canvas.addEventListener('touchmove', function(e){ e.preventDefault ? e.preventDefault() : null; var p = self.getTouchPos(e); if(self.touchFlag){ self.update(p); }else{ self.judgePos(p); } }, false)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    this.canvas.addEventListener('touchmove', function(e){
      e.preventDefault ? e.preventDefault() : null;
      var p = self.getTouchPos(e);
      if(self.touchFlag){
        self.update(p);
      }else{
        self.judgePos(p);
      }
    }, false)

    for(key in info){

        var obj = new Image();

       obj.src = info[key];

    //闭包方式return函数返回当前的图片加载完成的键值对.

       obj.onload = (function(key2){

          return function(){

                   loadingimg[key2] = this;

                   loadingimgnumber ;

                  if(loadingimgnumber == allimg){

                     if(callbock.done){

                      callbock.done(loadingimg);

                   }

                }

           }

       })(key);

    }

    剩下的就是主函数代码了.

    //main主函数

    function main(loadingimg){

    //开始绘制画布的飞机

    var canvas = document.getElementById("canvas");

    手势解锁,爱心背景特效。var ctx = canvas.getContext('2d');

    //获取canvas的宽高

    canvaswidth = canvas.width;

    canvasheight = canvas.height;

    //飞机的起始位置

    var loadX = canvaswidth/2 - 33;

    var loadY = canvasheight - 82;

    //飞机对象

    var plane = {

    w:66,

    h:82,

    x:canvaswidth/2 - 33,

    y:canvasheight - 82,

    startX:0,

    startY:0,

    endX:0,

    endY:0,

    flag:true,

    draw:function(){

    ctx.drawImage(loadingimg.plane,0,0,this.w,this.h,this.x,this.y,this.w,this.h);

    },

    move:function(){

    canvas.addEventListener('touchstart',function(e){

    var ev = e || window.event;

    var touchs = ev.touches[0];

    plane.startX = touchs.pageX;

    plane.startY = touchs.pageY;

    },false);

    canvas.addEventListener('touchmove',function(e){

    var ev = e || window.event;

    var touchs = ev.touches[0];

    if(plane.flag == true){

    var x = touchs.pageX - plane.startX loadX plane.endX;

    var y = touchs.pageY - plane.startY loadY plane.endY;

    plane.x = x ;

    plane.y = y ;

    plane.draw();

    }else{

    手势解锁,爱心背景特效。var x = touchs.pageX - plane.startX plane.endX;

    var y = touchs.pageY - plane.startY plane.endY;

    plane.x = x ;

    plane.y = y ;

    plane.draw();

    }

    },false);

    canvas.addEventListener('touchend',function(e){

    // var ev = e || window.event;

    // var touchs = ev.changedTouches[0];

    plane.endX = plane.x;

    plane.endY = plane.y;

    plane.flag = false;

    },false);

    }

    };

    plane.draw();

    plane.move();

    //背景对象

    var backgrounds = {

    x:0,

    y:0,

    w:canvaswidth,

    h:canvasheight,

    draw:function(){

    ctx.drawImage(loadingimg.background,this.x,this.y,this.w,this.h);

    ctx.drawImage(loadingimg.background,this.x,this.y-this.h,this.w,this.h);

    },

    move:function(){

    this.y =5;

    if(this.y >= this.h){

    this.y=0;

    }

    backgrounds.draw();

    }

    };

    backgrounds.draw();

    //动画

    function gameloop(){

    backgrounds.move();

    plane.draw();

    window.requestAnimationFrame(gameloop);

    }

    window.requestAnimationFrame(gameloop);

    }

    关于 showInfo

    由于showInfo 中有 setTimeout 函数,可以看到函数里的演出为 1s,导致如果我们操作的速度比较快,在 1s 内连续 show 了很多个 info,后面的 info 会被第一个 info 的 setTimeout 弄乱,显示的时间小于 1s,或更短。比如,当重复点击设置手势密码和验证手势密码,会产生这个 bug。

    解决办法有两个,一个是增加一个专门用于显示的数组,每次从数组中取值然后显示。另一种解题思路和防抖动的思路很像,就是当有一个新的 show 到来时,把之前的那个 setTimeout 清除掉。

    这里采用第二种思路:

    showInfo: function(message, timer){ // 专门用来显示 info clearTimeout(this.showInfo.timer); var info = this.dom.info; info.innerHTML = message; info.style.display = 'block'; this.showInfo.timer = setTimeout(function(){ info.style.display = ''; }, timer || 1000) },

    1
    2
    3
    4
    5
    6
    7
    8
    9
    showInfo: function(message, timer){ // 专门用来显示 info
      clearTimeout(this.showInfo.timer);
      var info = this.dom.info;
      info.innerHTML = message;
      info.style.display = 'block';
      this.showInfo.timer = setTimeout(function(){
        info.style.display = '';
      }, timer || 1000)
    },

         好啦,各位观众老爷,给大家一个代码运行的一个样式吧.

    解决小尾巴

    所谓的小尾巴,如下:

    图片 8

    解决办法也很简单,在 touchend 的时候,先进行 clearRect 就 ok 了。

    图片 9

    关于优化

    性能优化一直都是一个大问题,不要以为前端不需要考虑内存,就可以随便写代码。

    之前在设计自己网页的时候,用到了滚动,鼠标滑轮轻轻一碰,滚动函数就执行了几十多则几百次,之前也考虑过解决办法。

    运行后的飞机的样式.

    优化 canvas 部分

    对于 touchmove 函数,原理都是一样的,手指一划,就执行了 n 多次,这个问题后面在解决,先来看另一个问题。

    touchmove 是一个高频函数,看到这里,如果你并没有仔细看我的代码,那你对我采用的 canvas 画图方式可能不太了解,下面这个是 touchmove 函数干了哪些事:

    1. 先判断,如果当前处于未选中一个密码状态,则继续监视当前的位置,直到选中第一个密码,进入第二步;
    2. 进入 update 函数,update 函数主要干四件事,重绘圆(密码)、判断当前位置、重绘点、重绘线;

    第二步是一个很揪心的动作,为什么每次都要重绘圆,点和线呢?

    图片 10

    上面这个图可以很好的说明问题,因为在设置或验证密码的过程中,我们需要用一条线来连接触点到当前的最后一个密码,并且当 touchmove 的时候,能看到它们在变化。这个功能很棒,可以勾勒出 touchmove 的轨迹。

    但是,这就必须要时刻刷新 canvas,性能大大地降低,刷新的那可是整个 canvas。

    因为 canvas 只有一个,既要画背景圆(密码),又要画已选密码的点,和折线。这其中好多步骤,自始至终只需要一次就好了,比如背景圆,只需在启动的时候画一次,已选密码,只要当 touchCircles 新加元素的时候才会用一次,还不用重绘,只要画就可以了。折线分成两部分,一部分是已选密码之间的连线,还有就是最后一个密码点到当前触点之间的连线。

    如果有两个 canvas 就好了,一个存储静态的,一个专门用于重绘

    为什么不可以有呢!

    我的解决思路是,现在有两个 canvas,一个在底层,作为描绘静态的圆、点和折线,另一个在上层,一方面监听 touchmove 事件,另一方面不停地重绘最后一个密码点的圆心到当前触点之间的线。如果这样可以的话,touchmove 函数执行一次的效率大大提高。

    插入第二个 canvas:

    var canvas2 = canvas.cloneNode(canvas, true); canvas2.style.position = 'absolute';//让上层 canvas 覆盖底层 canvas canvas2.style.top = '0'; canvas2.style.left = '0'; this.el.appendChild(canvas2); this.ctx2 = canvas2.getContext('2d');

    1
    2
    3
    4
    5
    6
    var canvas2 = canvas.cloneNode(canvas, true);
    canvas2.style.position = 'absolute';//让上层 canvas 覆盖底层 canvas
    canvas2.style.top = '0';
    canvas2.style.left = '0';
    this.el.appendChild(canvas2);
    this.ctx2 = canvas2.getContext('2d');

    要改换对第二个 ctx2 进行 touch 监听,并设置一个 this.reDraw 参数,表示有新的密码添加进来,需要对点和折线添加新内容, update 函数要改成这样:

    update: function(p){ // 更新 touchmove this.judgePos(p); // 每次都要判断 this.drawLine2TouchPos(p); // 新加函数,用于绘最后一个密码点点圆心到触点之间的线 if(this.reDraw){ // 有新的密码加进来 this.reDraw = false; this.drawPoints(); // 添加新点 this.drawLine();// 添加新线 } },

    1
    2
    3
    4
    5
    6
    7
    8
    9
    update: function(p){ // 更新 touchmove
      this.judgePos(p); // 每次都要判断
      this.drawLine2TouchPos(p); // 新加函数,用于绘最后一个密码点点圆心到触点之间的线
      if(this.reDraw){ // 有新的密码加进来
        this.reDraw = false;
        this.drawPoints(); // 添加新点
        this.drawLine();// 添加新线
      }
    },

    drawLine2TouchPos: function(p){ var len = this.touchCircles.length; if(len >= 1){ this.ctx2.clearRect(0, 0, this.width, this.width); // 先清空 this.ctx2.beginPath(); this.ctx2.lineWidth = 3; this.ctx2.moveTo(this.touchCircles[len - 1].x, this.touchCircles[len

    • 1].y); this.ctx2.lineTo(p.x, p.y); this.ctx2.stroke(); this.ctx2.closePath(); } },
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    drawLine2TouchPos: function(p){
      var len = this.touchCircles.length;
      if(len >= 1){
        this.ctx2.clearRect(0, 0, this.width, this.width); // 先清空
        this.ctx2.beginPath();
        this.ctx2.lineWidth = 3;
        this.ctx2.moveTo(this.touchCircles[len - 1].x, this.touchCircles[len - 1].y);
        this.ctx2.lineTo(p.x, p.y);
        this.ctx2.stroke();
        this.ctx2.closePath();
      }
    },

    相应的 drawPoints 和 drawLine 函数也要对应修改,由原理画所有的,到现在只需要画新加的。

    效果怎么样:

    图片 11

    move 函数执行多次,而其他函数只有当新密码加进来的时候才执行一次。

    今天因为太晚了,实在扛不住了,代码有些地方获取没有解释到位, 我先分享给大家. 后续我会更新的. 感谢各位观众老爷的阅读,如有问题请赐教, 内裤感激不尽. 谢谢大家啦~~~~

    加入节流函数

    之前也已经说过了,这个 touchmove 函数执行的次数比较多,尽管我们已经用两个 canvas 对重绘做了很大的优化,但 touchmove 还是有点大开销。

    这个时候我想到了防抖动和节流,首先防抖动肯定是不行的,万一我一直处于 touch 状态,重绘会延迟死的,这个时候节流会好一些。防抖和节流。

    先写一个节流函数:

    throttle: function(func, delay, mustRun){ var timer, startTime = new Date(), self = this; return function(){ var curTime = new Date(), args = arguments; clearTimeout(timer); if(curTime - startTime >= mustRun){ startTime = curTime; func.apply(self, args); }else{ timer = setTimeout(function(){ func.apply(self, args); }, delay) } } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    throttle: function(func, delay, mustRun){
      var timer, startTime = new Date(), self = this;
      return function(){
        var curTime = new Date(), args = arguments;
        clearTimeout(timer);
        if(curTime - startTime >= mustRun){
          startTime = curTime;
          func.apply(self, args);
        }else{
          timer = setTimeout(function(){
            func.apply(self, args);
          }, delay)
        }
      }
    }

    节流函数的意思:在延迟为 delay 的时间内,如果函数再次触发,则重新计时,这个功能和防抖动是一样的,第三个参数 mustRun 是一个时间间隔,表示在时间间隔大于 mustRun 后的一个函数可以立即直接执行。

    然后对 touchmove 的回调函数进行改造:

    var t = this.throttle(function(e){ e.preventDefault ? e.preventDefault() : null; e.stopPropagation ? e.stopPropagation() : null; var p = this.getTouchPos(e); if(this.touchFlag){ this.update(p); }else{ this.judgePos(p); } }, 16, 16) this.canvas2.addEventListener('touchmove', t, false)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var t = this.throttle(function(e){
      e.preventDefault ? e.preventDefault() : null;
      e.stopPropagation ? e.stopPropagation() : null;
      var p = this.getTouchPos(e);
      if(this.touchFlag){
        this.update(p);
      }else{
        this.judgePos(p);
      }
    }, 16, 16)
    this.canvas2.addEventListener('touchmove', t, false)

    关于 delay 和 mustRun 的时间间隔问题,web 性能里有一个 16ms 的概念,就是说如果要达到每秒 60 帧,间隔为 1000/60 大约为 16 ms。如果间隔大于 16ms 则 fps 会比 60 低。

    鉴于此,我们这里将 delay 和 mustRun 都设为 16,在极端的情况下,也就是最坏的情况下,或许需要 15 15 = 30ms 才会执行一次,这个时候要设置两个 8 才合理,不过考虑到手指活动是一个连续的过程,怎么可能会每 15 秒执行一次,经过在线测试,发现设置成 16 效果还不错。

    性能真的能优化吗,我们来看两个图片,do 和 wantdo 表示真实执行和放到节流函数中排队准备执行。

    当 touchmove 速度一般或很快的时候:

    图片 12

    当 touchmove 速度很慢的时候:

    图片 13

    可以看出来,滑动过程中,速度一般和快速,平均优化了一半,慢速效果也优化了 20 到 30% 之间,平时手势锁解锁时候,肯定速度很快。可见,节流的优化还是很明显的。

    关键是,优化之后的流程性,没有受到任何影响。

    这个节流函数最终还是出现了一个 bug:由于是延迟执行的,导致 e.preventDefault 失效,在手机浏览器向下滑会出现刷新的情况,这也算事件延迟的一个危害吧。

    解决办法:在节流函数提前取消默认事件:

    throttle: function(func, delay, mustRun){ var timer, startTime = new Date(), self = this; return function(e){ if(e){ e.preventDefault ? e.preventDefault() : null; //提前取消默认事件,不要等到 setTimeout e.stopPropagation ? e.stopPropagation() : null; } ... } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    throttle: function(func, delay, mustRun){
      var timer, startTime = new Date(), self = this;
      return function(e){
        if(e){
          e.preventDefault ? e.preventDefault() : null; //提前取消默认事件,不要等到 setTimeout
          e.stopPropagation ? e.stopPropagation() : null;
        }
        ...
      }
    }

    总结

    大概花了三天左右的时间,将这个 H5 的手势解锁给完成,自己还是比较满意的,虽然可能达不到评委老师的认可,不过自己在做的过程中,学习到了很多新知识。

    参考

    H5lock
    Canvas教程
    js获取单选框里面的值
    前端高性能滚动 scroll 及页面渲染优化

    3 赞 5 收藏 评论

    图片 14

    本文由新葡亰496net发布于新葡亰官网,转载请注明出处:手势解锁,爱心背景特效

    关键词:

上一篇:新葡亰496net:制作动画,线条动画入门

下一篇:没有了