canvas实现一个解锁demo

问题1:

代码如下:

function Foo() {};
Foo.prototype.talk = function () {
    alert('hello~\n');
};

var a = new Foo;
a.talk(); // 输出'hello~\n'

但是如果这样:

Foo.talk() // 报错:Object doesn't support property or method 'talk'
Foo.prototype.talk() // 没有问题

解答:

因为这里a是构造出来的对象,其[[proto]]属性指向Foo.prototype。但Foo不是,Foo是一个函数对象,它的[[proto]]指向的是Function.prototype,因此有的是Foo.call Foo.apply这些函数对象上的方法(来自Function.prototypr)而不会有你定义的那个talk


问题2:

对一个函数对象添加属性,用prototype何不用prototype有何不同

用prototype添加属性的时候,当你实例化以后调用实际上只是调用了原型对象,只调用了一次,不管你实例化几次最多只是内容变了,而在内存中只会出现一次原型对象的函数。



js完成dom结构


tips: setAttribute和.style方法


setAttribute是给html元素属性设置值的。html元素属性就是指的标签中的键值对的前者,
例如<div id="name" class="text"><div>中的id和class就是html元素属性。

.style.property="值"是用来设置css样式的。例如说
document.getElementById("#name").style.background="red"设置文本框的背景颜色为红色。

在标签中我们也会内嵌样式,
例如<div id="name" class="text" style="width:100px;"><div>,说明style也是html元素的一个属性。
在设置的时候可以使用document.getElementById("#name").setAttribute("style","color:red;font:9px;");
实际上平时用setAtrribute()这个方法就用的不多,也不太支持这种设置样式属性的写法。
如果引用了jQuery的话,在设置CSS样式的时候还可以使用.css("border","1px solid red");这种方式。



H5lock.prototype.initDom = function(){
    var wrap = document.createElement('div');
    var str = '<h4 id="title" class="title">绘制解锁图案</h4>'+
              '<a id="updatePassword" style="position: absolute;right: 5px;top: 5px;color:#fff;font-size: 10px;display:none;">重置密码</a>';

    wrap.setAttribute('style','position: absolute;top:0;left:0;right:0;bottom:0;');
    var canvas = document.createElement('canvas');
    canvas.setAttribute('id','canvas');
    canvas.style.cssText = 'background-color: #305066;display: inline-block;margin-top: 15px;';
    wrap.innerHTML = str;
    wrap.appendChild(canvas);

    var width = this.width || 300;
    var height = this.height || 300;
    
    document.body.appendChild(wrap);

    // 高清屏锁放
    canvas.style.width = width + "px";
    canvas.style.height = height + "px";
    canvas.height = height * this.devicePixelRatio; //修改canvas默认宽高
    canvas.width = width * this.devicePixelRatio;

}




画外层大圆的函数

关于大圆半径求法


如下图为一行三个圆的时候  






this.r = this.ctx.canvas.width / (2 + 4 * n);// 公式计算
把大圆的直径和大圆左侧距离看成一个整体 也就是    4个半径的长度   一行就是n个圆   4*n  加上最右剩余的距离两个半径     所以计算半径公式如上




实现存放大圆坐标

H5lock.prototype.createCircle = function() {// 创建解锁点的坐标,根据canvas的大小来平均分配半径

    var n = this.chooseType;
    var count = 0;
    this.r = this.ctx.canvas.width / (2 + 4 * n);// 公式计算

    //确定圆心
    this.lastPoint = [];
    this.arr = [];//9个圆的中心点坐标
    this.restPoint = [];//同样是9个圆
    var r = this.r;
    for (var i = 0 ; i < n ; i++) {//3*3


        for (var j = 0 ; j < n ; j++) {
            count++;
            var obj = {
                x: j * 4 * r + 3 * r,
                y: i * 4 * r + 3 * r,
                index: count
            };
            this.arr.push(obj);
            this.restPoint.push(obj);
        }
    }
    this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);//开始画
    for (var i = 0 ; i < this.arr.length ; i++) {
        this.drawCle(this.arr[i].x, this.arr[i].y);
    }
    //return arr;
}



绘制圆  drawCle


H5lock.prototype.drawCle = function(x, y) { // 初始化解锁密码面板
    this.ctx.strokeStyle = '#CFE6FF';
    this.ctx.lineWidth = 2;
    this.ctx.beginPath();
    this.ctx.arc(x, y, this.r, 0, Math.PI * 2, true);
    this.ctx.closePath();
    this.ctx.stroke();
}




绑定触摸事件


触发触摸:判断触摸点是否在大圆内  

判断方法  触发点到画布最左的距离 减去 大圆圆心到最左的距离 的绝对值小于半径   那么这个触发点一定在某个圆内   有九个大圆 所以得遍历比较每个圆  遇到满足条件的就终止循环



this.canvas.addEventListener("touchstart", function (e) {//判断触发点是不是再大圆内
    e.preventDefault();// 某些android 的 touchmove不宜触发 所以增加此行代码
     var po = self.getPosition(e);//判断触摸点位置
     console.log(po);
     //判断条件 触发点与圆心的位置到最左端的绝对值是否小于r   是的话就在圆内
     for (var i = 0 ; i < self.arr.length ; i++) {
        if (Math.abs(po.x - self.arr[i].x) < self.r && Math.abs(po.y - self.arr[i].y) < self.r) {
            self.touchFlag = true;
            self.drawPoint(self.arr[i].x,self.arr[i].y);
            self.lastPoint.push(self.arr[i]);//点在哪个圆内就往lastpoint中push哪个圆的坐标
            self.restPoint.splice(i,1);
            break;//每次都要从第一个圆开始判断直到最后一个圆  如果中间判断出在某个圆内  就要终止循环
        }
     }
 }, false);


获取触发点坐标的函数getPosition


//判断触摸点位置的函数
H5lock.prototype.getPosition = function(e) {// 获取touch点相对于canvas的坐标
    var rect = e.currentTarget.getBoundingClientRect();//返回的是画布距离屏幕的上下左右距离
    var po = {
        x: (e.touches[0].clientX - rect.left)*this.devicePixelRatio,//一个手指操作 所以 下标为0  注意这里是触发点到屏幕左的距离减去画布到屏幕左的距离
        y: (e.touches[0].clientY - rect.top)*this.devicePixelRatio
      };
    return po;
}

补充

getBoundingClientRect用于获取某个元素相对于视窗的位置集合。集合中有top, right, bottom, left等属性。

 rectObject.top:元素上边到视窗上边的距离;

 rectObject.right:元素右边到视窗左边的距离;

 rectObject.bottom:元素下边到视窗上边的距离;

 rectObject.left:元素左边到视窗左边的距离;



clientX  、 clientY

clientX 事件属性返回当事件被触发时鼠标指针向对于浏览器页面(或客户区)的水平坐标。



touches

这是应用于移动端触摸事件的,event.x是他在手机上点的X轴位置,event.touches,多点触碰时的位置数组,比如缩放手势必须要用两指的触摸点,就是一个数组



添加touchmove事件

this.canvas.addEventListener("touchmove", function (e) {//画圆和画线
   if (self.touchFlag) {
       self.update(self.getPosition(e));
   }
}, false);

update函数


H5lock.prototype.update = function(po) {// 核心变换方法在touchmove时候调用
    this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);

    //重画九个圆
    for (var i = 0 ; i < this.arr.length ; i++) { // 每帧先把面板画出来
        this.drawCle(this.arr[i].x, this.arr[i].y);
    }

    this.drawPoint(this.lastPoint);// 每帧画圆心
    this.drawLine(po , this.lastPoint);// 每帧花轨迹


    //1.检测手势移动的位置是否处于下一个圆内
    //2.如果处于圆内则画圆
    //3.已经划过实心圆的圆  无需重复检测
    for (var i = 0 ; i < this.restPoint.length ; i++) {
        if (Math.abs(po.x - this.restPoint[i].x) < this.r && Math.abs(po.y - this.restPoint[i].y) < this.r) {
            this.drawPoint(this.restPoint[i].x, this.restPoint[i].y);
            this.lastPoint.push(this.restPoint[i]);

            //上面是找到画过内圆的 大圆  然后从restPoint中删除掉这个
            this.restPoint.splice(i, 1);
            break;
        }
    }

}

画内圆的函数   drawPoint
H5lock.prototype.drawPoint = function() { // 初始化圆心
    for (var i = 0 ; i < this.lastPoint.length ; i++) {
        this.ctx.fillStyle = '#CFE6FF';
        this.ctx.beginPath();
        this.ctx.arc(this.lastPoint[i].x, this.lastPoint[i].y, this.r / 2, 0, Math.PI * 2, true);
        this.ctx.closePath();
        this.ctx.fill();
    }
}


画线段的函数   drawLine

H5lock.prototype.drawLine = function(po, lastPoint) {// 解锁轨迹
    this.ctx.beginPath();
    this.ctx.lineWidth = 3;
    this.ctx.moveTo(this.lastPoint[0].x, this.lastPoint[0].y);//起始点为圆心
    console.log(this.lastPoint.length);
    for (var i = 1 ; i < this.lastPoint.length ; i++) {
        this.ctx.lineTo(this.lastPoint[i].x, this.lastPoint[i].y);
    }
    this.ctx.lineTo(po.x, po.y);//画到触摸点
    this.ctx.stroke();
    this.ctx.closePath();

}


继续添加touchend事件

this.canvas.addEventListener("touchend", function (e) {
    if (self.touchFlag) {
        self.touchFlag = false;
        self.storePass(self.lastPoint);
        setTimeout(function(){

           self.reset();
//不管密码输入正确与否  过了一会要将页面重置为开始时的九个无状态的大圆
}, 300); }}, false);

tips:

setTimeout(表达式,延时时间)在执行时,是在载入后延迟指定时间后,去执行一次表达式,记住,次数是一次 
     而setInterval(表达式,交互时间)则不一样,它从载入后,每隔指定的时间就执行一次表达式 



存储密码函数 storePass

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

}

检测密码checkPass

/*检测解锁成功:
1.检测路径是否正确
2.正确就重置,圆圈变绿
3.不对也重置,圆圈变红
4.重置

*/
 H5lock.prototype.checkPass = function(psw1, psw2) {// 检测密码
    var p1 = '',//成功解锁的密码
    p2 = '';
    for (var i = 0 ; i < psw1.length ; i++) {//index是九个点  分别为1 ~9 
        p1 += psw1[i].index + psw1[i].index;
    }
    for (var i = 0 ; i < psw2.length ; i++) {
        p2 += psw2[i].index + psw2[i].index;
    }
    return p1 === p2;//返回的是布尔值
}
注意psw都是数组

drawStatusPoint是重绘大圆颜色的 
正确是绿色  错误是红色
H5lock.prototype.drawStatusPoint = function(type) { // 初始化状态线条
    for (var i = 0 ; i < this.lastPoint.length ; i++) {
        this.ctx.strokeStyle = type;
        this.ctx.beginPath();
        this.ctx.arc(this.lastPoint[i].x, this.lastPoint[i].y, this.r, 0, Math.PI * 2, true);
        this.ctx.closePath();
        this.ctx.stroke();
    }
}


reset重置

H5lock.prototype.reset = function() {
    this.makeState();
    this.createCircle();//重新画出最开的九个圆
}


makeState

H5lock.prototype.makeState = function() {//决定右上角的重置密码这几个字是否显示
    if (this.pswObj.step == 2) {//如果在设置密码的时候 第二次输入的密码与第一次不符 根本不会有等于2的情况
        document.getElementById('updatePassword').style.display = 'block';
        //document.getElementById('chooseType').style.display = 'none';
        document.getElementById('title').innerHTML = '请解锁';
    } else if (this.pswObj.step == 1) {
        //document.getElementById('chooseType').style.display = 'none';
        document.getElementById('updatePassword').style.display = 'none';
    } else {
        document.getElementById('updatePassword').style.display = 'none'; //点击重置密码的时候触发这里
        //document.getElementById('chooseType').style.display = 'block';
    }
}



给重置密码这个div绑定一个点击事件

document.getElementById('updatePassword').addEventListener('click', function(){
    self.updatePassword();
 });




updatePassword

H5lock.prototype.updatePassword = function(){
    window.localStorage.removeItem('passwordxx');
    window.localStorage.removeItem('chooseType');
    this.pswObj = {};//step被删除  
    document.getElementById('title').innerHTML = '绘制解锁图案';
    this.reset();
}



初始化
H5lock.prototype.init = function() {
    //1.确定半径
    //2.确定每个圆的中心坐标
    //3.    14个半径
    this.initDom();
    this.pswObj = window.localStorage.getItem('passwordxx') ? {
        step: 2,
        spassword: JSON.parse(window.localStorage.getItem('passwordxx'))
    } : {};
    this.lastPoint = [];
    this.makeState();//决定重置密码这几个字是否显示
    this.touchFlag = false;//初始化touchFlag
    this.canvas = document.getElementById('canvas');
    this.ctx = this.canvas.getContext('2d');
    this.createCircle();//大圆绘制
    this.bindEvent();//绑定事件函数
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值