问题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画线段的函数 drawLineH5lock.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(); } }
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();//绑定事件函数 }