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();//繫結事件函式 }