成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

用 canvas 實(shí)現(xiàn) Web 手勢(shì)解鎖

MycLambert / 1945人閱讀

摘要:畫線畫線需要借助來(lái)完成,也就是,當(dāng)我們的時(shí)候,傳入開始時(shí)的相對(duì)坐標(biāo),作為線的一端,當(dāng)我們的時(shí)候,獲得坐標(biāo),作為線的另一端,當(dāng)我們的時(shí)候,開始畫線。有兩個(gè)函數(shù),獲得當(dāng)前的相對(duì)坐標(biāo)獲得觸摸點(diǎn)的相對(duì)位置相對(duì)坐標(biāo)畫線畫線然后就是監(jiān)聽的和事件了。

最近參加 360 暑假的前端星計(jì)劃,有一個(gè)在線作業(yè),截止日期是 3 月 30 號(hào),讓手動(dòng)實(shí)現(xiàn)一個(gè) H5 手勢(shì)解鎖,具體的效果就像原生手機(jī)的九宮格解鎖那樣。

實(shí)現(xiàn)的最終效果就像下面這張圖這樣:

基本要求是這樣的:將密碼保存到 localStorage 里,開始的時(shí)候會(huì)從本地讀取密碼,如果沒有就讓用戶設(shè)置密碼,密碼最少為五位數(shù),少于五位要提示錯(cuò)誤。需要對(duì)第一次輸入的密碼進(jìn)行驗(yàn)證,兩次一樣才能保持,然后是驗(yàn)證密碼,能夠?qū)τ脩糨斎氲拿艽a進(jìn)行驗(yàn)證。

H5 手勢(shì)解鎖

掃碼在線查看:

或者點(diǎn)擊查看手機(jī)版。

項(xiàng)目 GitHub 地址,H5HandLock。

首先,我要說(shuō)明一下,對(duì)于這個(gè)項(xiàng)目,我是參考別人的,H5lock。

我覺得一個(gè)比較合理的解法應(yīng)該是利用 canvas 來(lái)實(shí)現(xiàn),不知道有沒有大神用 css 來(lái)實(shí)現(xiàn)。如果純用 css 的話,可以將連線先設(shè)置 display: none,當(dāng)手指劃過(guò)的時(shí)候,顯示出來(lái)。光設(shè)置這些應(yīng)該就非常麻煩吧。

之前了解過(guò) canvas,但沒有真正的寫過(guò),下面就來(lái)介紹我這幾天學(xué)習(xí) canvas 并實(shí)現(xiàn) H5 手勢(shì)解鎖的過(guò)程。

準(zhǔn)備及布局設(shè)置

我這里用了一個(gè)比較常規(guī)的做法:

(function(w){
  var handLock = function(option){}

  handLock.prototype = {
    init : function(){},
    ...
  }

  w.handLock = handLock;
})(window)

// 使用
new handLock({
  el: document.getElementById("id"),
  ...
}).init();

常規(guī)方法,比較易懂和操作,弊端就是,可以被隨意的修改。

傳入的參數(shù)中要包含一個(gè) dom 對(duì)象,會(huì)在這個(gè) dom 對(duì)象內(nèi)創(chuàng)建一個(gè) canvas。當(dāng)然還有一些其他的 dom 參數(shù),比如 message,info 等。

關(guān)于 css 的話,懶得去新建文件了,就直接內(nèi)聯(lián)了。

canvas 1. 學(xué)習(xí) canvas 并搞定畫圓

MDN 上面有個(gè)簡(jiǎn)易的教程,大致瀏覽了一下,感覺還行。Canvas教程。

先創(chuàng)建一個(gè) canvas,然后設(shè)置其大小,并通過(guò) getContext 方法獲得繪畫的上下文:

var canvas = document.createElement("canvas");
canvas.width = canvas.height = width;
this.el.appendChild(canvas);

this.ctx = canvas.getContext("2d");

然后呢,先畫 n*n 個(gè)圓出來(lái):

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 = []; // 用來(lái)存儲(chǔ)圓心的位置
  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); // 為了防止重復(fù)畫
  this.circles.forEach(function(v){
    drawCircle(ctx, v.x, v.y); // 畫每個(gè)圓
  })
},

drawCircle: function(ctx, x, y){ // 畫圓函數(shù)
  ctx.strokeStyle = "#FFFFFF";
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(x, y, this.r, 0, Math.PI * 2, true);
  ctx.closePath();
  ctx.stroke();
}

畫圓函數(shù),需要注意:如何確定圓的半徑和每個(gè)圓的圓心坐標(biāo)(這個(gè)我是參考的),如果以圓心為中點(diǎn),每個(gè)圓上下左右各擴(kuò)展一個(gè)半徑的距離,同時(shí)為了防止四邊太擠,四周在填充一個(gè)半徑的距離。那么得到的半徑就是 width / ( 4 * n + 2),對(duì)應(yīng)也可以算出每個(gè)圓所在的圓心坐標(biāo),也有一套公式,GET。

2. 畫線

畫線需要借助 touch event 來(lái)完成,也就是,當(dāng)我們 touchstart 的時(shí)候,傳入開始時(shí)的相對(duì)坐標(biāo),作為線的一端,當(dāng)我們 touchmove 的時(shí)候,獲得坐標(biāo),作為線的另一端,當(dāng)我們 touchend 的時(shí)候,開始畫線。

這只是一個(gè)測(cè)試畫線功能,具體的后面再進(jìn)行修改。

有兩個(gè)函數(shù),獲得當(dāng)前 touch 的相對(duì)坐標(biāo):

getTouchPos: function(e){ // 獲得觸摸點(diǎn)的相對(duì)位置
  var rect = e.target.getBoundingClientRect();
  var p = { // 相對(duì)坐標(biāo)
    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();
},

然后就是監(jiān)聽 canvas 的 touchstart、touchmove、和 touchend 事件了。

3. 畫折線

所謂的畫折線,就是,將已經(jīng)觸摸到的點(diǎn)連起來(lái),可以把它看作是畫折線。

首先,要用兩個(gè)數(shù)組,一個(gè)數(shù)組用于已經(jīng) touch 過(guò)的點(diǎn),另一個(gè)數(shù)組用于存儲(chǔ)未 touch 的點(diǎn),然后在 move 監(jiān)聽時(shí)候,對(duì) touch 的相對(duì)位置進(jìn)行判斷,如果觸到點(diǎn),就把該點(diǎn)從未 touch 移到 touch 中,然后,畫折線,思路也很簡(jiǎn)單。

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();
},
judgePos: function(p){ // 判斷 觸點(diǎn) 是否在 circle 內(nèi)
  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;
    }
  }
}
4. 標(biāo)記已畫

前面已經(jīng)說(shuō)了,我們把已經(jīng) touch 的點(diǎn)(圓)放到數(shù)組中,這個(gè)時(shí)候需要將這些已經(jīng) touch 的點(diǎn)給標(biāo)記一下,在圓心處畫一個(gè)小實(shí)心圓:

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();
  }
}

同時(shí)添加一個(gè) reset 函數(shù),當(dāng) touchend 的時(shí)候調(diào)用,400ms 調(diào)用 reset 重置 canvas。

到現(xiàn)在為止,一個(gè) H5 手勢(shì)解鎖的簡(jiǎn)易版已經(jīng)基本完成。

password

為了要實(shí)現(xiàn)記住和重置密碼的功能,把 password 保存在 localStorage 中,但首先要添加必要的 html 和樣式。

1. 添加 message 和 單選框

為了盡可能的使界面簡(jiǎn)潔(越丑越好),直接在 body 后面添加了:

請(qǐng)輸入手勢(shì)密碼

將添加到 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();
2. info 信息顯示

關(guān)于 info 信息顯示,自己寫了一個(gè)懸浮窗,然后默認(rèn)為 display: none,然后寫了一個(gè) showInfo 函數(shù)用來(lái)顯示提示信息,直接調(diào)用:

showInfo: function(message, timer){ // 專門用來(lái)顯示 info
  var info = this.dom.info;
  info.innerHTML = message;
  info.style.display = "block";
  setTimeout(function(){
    info.style.display = "";
  }, 1000)
}

關(guān)于 info 的樣式,在 html 中呢。

3. 關(guān)于密碼

先不考慮從 localStorage 讀取到情況,新加一個(gè) lsPass 對(duì)象,專門用于存儲(chǔ)密碼,由于密碼情況比較多,比如設(shè)置密碼,二次確認(rèn)密碼,驗(yàn)證密碼,為了方便管理,暫時(shí)設(shè)置了密碼的三種模式,分別是:

model:1 驗(yàn)證密碼模式

model:2 設(shè)置密碼模式

model:3 設(shè)置密碼二次驗(yàn)證

具體看下面這個(gè)圖:

這三種 model ,只要處理好它們之間如何跳轉(zhuǎn)就 ok 了,即狀態(tài)的改變。

所以就有了 initPass:

initPass: function(){ // 將密碼初始化
  this.lsPass = w.localStorage.getItem("HandLockPass") ? {
    model: 1,
    pass: w.localStorage.getItem("HandLockPass").split("-")
  } : { model: 2 };
  this.updateMessage();
},

updateMessage: function(){ // 根據(jù)當(dāng)前模式,更新 dom
  if(this.lsPass.model == 2){
    this.dom.setPass.checked = true;
    this.dom.message.innerHTML = "請(qǐng)?jiān)O(shè)置手勢(shì)密碼";
  }else if(this.lsPass.model == 1){
    this.dom.checkPass.checked = true;
    this.dom.message.innerHTML = "請(qǐng)驗(yàn)證手勢(shì)密碼";
  }else if(this.lsPass.model = 3){
    this.dom.setPass.checked = true;
    this.dom.message.innerHTML = "請(qǐng)?jiān)俅屋斎朊艽a";
  }
},

有必要再來(lái)介紹一下 lsPass 的格式:

this.lsPass = {
  model:1, // 表示當(dāng)前的模式
  pass: [0, 1, 2, 4, 5] // 表示當(dāng)前的密碼,可能不存在
}

因?yàn)橹耙呀?jīng)有了一個(gè)基本的實(shí)現(xiàn)框架,現(xiàn)在只需要在 touchend 之后,寫一個(gè)函數(shù),功能就是先對(duì)當(dāng)前的 model 進(jìn)行判斷,實(shí)現(xiàn)對(duì)應(yīng)的功能,這里要用到 touchCircles 數(shù)組,表示密碼的順序:

checkPass: function(){
  var succ, model = this.lsPass.model; //succ 以后會(huì)用到
  if(model == 2){ // 設(shè)置密碼
    if(this.touchCircles.length < 5){ // 驗(yàn)證密碼長(zhǎng)度
      succ = false;
      this.showInfo("密碼長(zhǎng)度至少為 5!", 1000);
    }else{
      succ = true;
      this.lsPass.temp = []; // 將密碼放到臨時(shí)區(qū)存儲(chǔ)
      for(var i = 0; i < this.touchCircles.length; i++){
        this.lsPass.temp.push(this.touchCircles[i].id);
      }
      this.lsPass.model = 3;
      this.showInfo("請(qǐng)?jiān)俅屋斎朊艽a", 1000);
      this.updateMessage();
    }
  }else if(model == 3){// 確認(rèn)密碼
    var flag = true;
    // 先要驗(yàn)證密碼是否正確
    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("兩次密碼不一致,請(qǐng)重新輸入", 1000);
      this.lsPass.model = 2; // 由于密碼不正確,重新回到 model 2
      this.updateMessage();
    }else{
      succ = true; // 密碼正確,localStorage 存儲(chǔ),并設(shè)置狀態(tài)為 model 1
      w.localStorage.setItem("HandLockPass", this.lsPass.temp.join("-")); // 存儲(chǔ)字符串
      this.lsPass.model = 1; 
      this.lsPass.pass = this.lsPass.temp;
      this.updateMessage();
    }
    delete this.lsPass.temp; // 很重要,一定要?jiǎng)h掉,bug
  }else if(model == 1){ // 驗(yàn)證密碼
    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("很遺憾,密碼錯(cuò)誤", 1000);
    }else{
      succ = true;
      this.showInfo("恭喜你,驗(yàn)證通過(guò)", 1000);
    }
  }
},

密碼的設(shè)置要參考前面那張圖,要時(shí)刻警惕狀態(tài)的改變。

4. 手動(dòng)重置密碼

思路也很簡(jiǎn)單,就是添加點(diǎn)擊事件,點(diǎn)擊之后,改變 model 即可,點(diǎn)擊事件如下:

this.dom.setPass.addEventListener("click", function(e){
  self.lsPass.model = 2; // 改變 model 為設(shè)置密碼
  self.updateMessage(); // 更新 message
  self.showInfo("請(qǐng)?jiān)O(shè)置密碼", 1000);
})
this.dom.checkPass.addEventListener("click", function(e){
  if(self.lsPass.pass){
    self.lsPass.model = 1;
    self.updateMessage();
    self.showInfo("請(qǐng)驗(yàn)證密碼", 1000)
  }else{
    self.showInfo("請(qǐng)先設(shè)置密碼", 1000);
    self.updateMessage();
  }
})

ps:這里面還有幾個(gè)小的 bug,因?yàn)?model 只有 3 個(gè),所以設(shè)置的時(shí)候,當(dāng)點(diǎn)擊重置密碼的時(shí)候,沒有設(shè)置密碼成功,又切成驗(yàn)證密碼狀態(tài),此時(shí)無(wú)法提升沿用舊密碼,原因是 model 只有三個(gè)

5. 添加 touchend 顏色變化

實(shí)現(xiàn)這個(gè)基本上就大功告成了,這個(gè)功能最主要的是給用戶一個(gè)提醒,若用戶劃出的密碼符合規(guī)范,顯示綠色,若不符合規(guī)范或錯(cuò)誤,顯示紅色警告。

因?yàn)橹耙呀?jīng)設(shè)置了一個(gè) succ 變量,專門用于重繪。

drawEndCircles: function(color){ // end 時(shí)重繪已經(jīng) touch 的圓
  for(var i = 0; i < this.touchCircles.length; i++){
    this.drawCircle(this.touchCircles[i].x, this.touchCircles[i].y, color);
  }
},

// 調(diào)用
if(succ){
  this.drawEndCircles("#2CFF26"); // 綠色
}else{
  this.drawEndCircles("red"); // 紅色
}

那么,一個(gè)可以演示的版本就生成了,盡管還存在一些 bug,隨后會(huì)來(lái)解決。(詳情分支 password)

一些 bugs

有些 bugs 在做的時(shí)候就發(fā)現(xiàn)了,一些 bug 后來(lái)用手機(jī)測(cè)試的時(shí)候才發(fā)現(xiàn),比如,我用 chrome 的時(shí)候,沒有察覺這個(gè) bug,當(dāng)我用 android 手機(jī) chrome 瀏覽器測(cè)試的時(shí)候,發(fā)現(xiàn)當(dāng)我 touchmove 向下的時(shí)候,會(huì)觸發(fā)瀏覽器的下拉刷新,解決辦法:加了一個(gè) 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)
關(guān)于 showInfo

由于showInfo 中有 setTimeout 函數(shù),可以看到函數(shù)里的演出為 1s,導(dǎo)致如果我們操作的速度比較快,在 1s 內(nèi)連續(xù) show 了很多個(gè) info,后面的 info 會(huì)被第一個(gè) info 的 setTimeout 弄亂,顯示的時(shí)間小于 1s,或更短。比如,當(dāng)重復(fù)點(diǎn)擊設(shè)置手勢(shì)密碼和驗(yàn)證手勢(shì)密碼,會(huì)產(chǎn)生這個(gè) bug。

解決辦法有兩個(gè),一個(gè)是增加一個(gè)專門用于顯示的數(shù)組,每次從數(shù)組中取值然后顯示。另一種解題思路和防抖動(dòng)的思路很像,就是當(dāng)有一個(gè)新的 show 到來(lái)時(shí),把之前的那個(gè) setTimeout 清除掉。

這里采用第二種思路:

showInfo: function(message, timer){ // 專門用來(lái)顯示 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)
},
解決小尾巴

所謂的小尾巴,如下:

解決辦法也很簡(jiǎn)單,在 touchend 的時(shí)候,先進(jìn)行 clearRect 就 ok 了。

關(guān)于優(yōu)化

性能優(yōu)化一直都是一個(gè)大問(wèn)題,不要以為前端不需要考慮內(nèi)存,就可以隨便寫代碼。

之前在設(shè)計(jì)自己網(wǎng)頁(yè)的時(shí)候,用到了滾動(dòng),鼠標(biāo)滑輪輕輕一碰,滾動(dòng)函數(shù)就執(zhí)行了幾十多則幾百次,之前也考慮過(guò)解決辦法。

優(yōu)化 canvas 部分

對(duì)于 touchmove 函數(shù),原理都是一樣的,手指一劃,就執(zhí)行了 n 多次,這個(gè)問(wèn)題后面在解決,先來(lái)看另一個(gè)問(wèn)題。

touchmove 是一個(gè)高頻函數(shù),看到這里,如果你并沒有仔細(xì)看我的代碼,那你對(duì)我采用的 canvas 畫圖方式可能不太了解,下面這個(gè)是 touchmove 函數(shù)干了哪些事:

先判斷,如果當(dāng)前處于未選中一個(gè)密碼狀態(tài),則繼續(xù)監(jiān)視當(dāng)前的位置,直到選中第一個(gè)密碼,進(jìn)入第二步;

進(jìn)入 update 函數(shù),update 函數(shù)主要干四件事,重繪圓(密碼)、判斷當(dāng)前位置、重繪點(diǎn)、重繪線;

第二步是一個(gè)很揪心的動(dòng)作,為什么每次都要重繪圓,點(diǎn)和線呢?

上面這個(gè)圖可以很好的說(shuō)明問(wèn)題,因?yàn)樵谠O(shè)置或驗(yàn)證密碼的過(guò)程中,我們需要用一條線來(lái)連接觸點(diǎn)到當(dāng)前的最后一個(gè)密碼,并且當(dāng) touchmove 的時(shí)候,能看到它們?cè)谧兓?。這個(gè)功能很棒,可以勾勒出 touchmove 的軌跡。

但是,這就必須要時(shí)刻刷新 canvas,性能大大地降低,刷新的那可是整個(gè) canvas。

因?yàn)?canvas 只有一個(gè),既要畫背景圓(密碼),又要畫已選密碼的點(diǎn),和折線。這其中好多步驟,自始至終只需要一次就好了,比如背景圓,只需在啟動(dòng)的時(shí)候畫一次,已選密碼,只要當(dāng) touchCircles 新加元素的時(shí)候才會(huì)用一次,還不用重繪,只要畫就可以了。折線分成兩部分,一部分是已選密碼之間的連線,還有就是最后一個(gè)密碼點(diǎn)到當(dāng)前觸點(diǎn)之間的連線。

如果有兩個(gè) canvas 就好了,一個(gè)存儲(chǔ)靜態(tài)的,一個(gè)專門用于重繪。

為什么不可以有呢!

我的解決思路是,現(xiàn)在有兩個(gè) canvas,一個(gè)在底層,作為描繪靜態(tài)的圓、點(diǎn)和折線,另一個(gè)在上層,一方面監(jiān)聽 touchmove 事件,另一方面不停地重繪最后一個(gè)密碼點(diǎn)的圓心到當(dāng)前觸點(diǎn)之間的線。如果這樣可以的話,touchmove 函數(shù)執(zhí)行一次的效率大大提高。

插入第二個(gè) 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");

要改換對(duì)第二個(gè) ctx2 進(jìn)行 touch 監(jiān)聽,并設(shè)置一個(gè) this.reDraw 參數(shù),表示有新的密碼添加進(jìn)來(lái),需要對(duì)點(diǎn)和折線添加新內(nèi)容, update 函數(shù)要改成這樣:

update: function(p){ // 更新 touchmove
  this.judgePos(p); // 每次都要判斷
  this.drawLine2TouchPos(p); // 新加函數(shù),用于繪最后一個(gè)密碼點(diǎn)點(diǎn)圓心到觸點(diǎn)之間的線
  if(this.reDraw){ // 有新的密碼加進(jìn)來(lái)
    this.reDraw = false;
    this.drawPoints(); // 添加新點(diǎn)
    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();
  }
},

相應(yīng)的 drawPoints 和 drawLine 函數(shù)也要對(duì)應(yīng)修改,由原理畫所有的,到現(xiàn)在只需要畫新加的。

效果怎么樣:

move 函數(shù)執(zhí)行多次,而其他函數(shù)只有當(dāng)新密碼加進(jìn)來(lái)的時(shí)候才執(zhí)行一次。

加入節(jié)流函數(shù)

之前也已經(jīng)說(shuō)過(guò)了,這個(gè) touchmove 函數(shù)執(zhí)行的次數(shù)比較多,盡管我們已經(jīng)用兩個(gè) canvas 對(duì)重繪做了很大的優(yōu)化,但 touchmove 還是有點(diǎn)大開銷。

這個(gè)時(shí)候我想到了防抖動(dòng)和節(jié)流,首先防抖動(dòng)肯定是不行的,萬(wàn)一我一直處于 touch 狀態(tài),重繪會(huì)延遲死的,這個(gè)時(shí)候節(jié)流會(huì)好一些。防抖和節(jié)流。

先寫一個(gè)節(jié)流函數(shù):

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)
    }
  }
}

節(jié)流函數(shù)的意思:在延遲為 delay 的時(shí)間內(nèi),如果函數(shù)再次觸發(fā),則重新計(jì)時(shí),這個(gè)功能和防抖動(dòng)是一樣的,第三個(gè)參數(shù) mustRun 是一個(gè)時(shí)間間隔,表示在時(shí)間間隔大于 mustRun 后的一個(gè)函數(shù)可以立即直接執(zhí)行。

然后對(duì) touchmove 的回調(diào)函數(shù)進(jìn)行改造:

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)

關(guān)于 delay 和 mustRun 的時(shí)間間隔問(wèn)題,web 性能里有一個(gè) 16ms 的概念,就是說(shuō)如果要達(dá)到每秒 60 幀,間隔為 1000/60 大約為 16 ms。如果間隔大于 16ms 則 fps 會(huì)比 60 低。

鑒于此,我們這里將 delay 和 mustRun 都設(shè)為 16,在極端的情況下,也就是最壞的情況下,或許需要 15 + 15 = 30ms 才會(huì)執(zhí)行一次,這個(gè)時(shí)候要設(shè)置兩個(gè) 8 才合理,不過(guò)考慮到手指活動(dòng)是一個(gè)連續(xù)的過(guò)程,怎么可能會(huì)每 15 秒執(zhí)行一次,經(jīng)過(guò)在線測(cè)試,發(fā)現(xiàn)設(shè)置成 16 效果還不錯(cuò)。

性能真的能優(yōu)化嗎,我們來(lái)看兩個(gè)圖片,do 和 wantdo 表示真實(shí)執(zhí)行和放到節(jié)流函數(shù)中排隊(duì)準(zhǔn)備執(zhí)行。

當(dāng) touchmove 速度一般或很快的時(shí)候:

當(dāng) touchmove 速度很慢的時(shí)候:

可以看出來(lái),滑動(dòng)過(guò)程中,速度一般和快速,平均優(yōu)化了一半,慢速效果也優(yōu)化了 20 到 30% 之間,平時(shí)手勢(shì)鎖解鎖時(shí)候,肯定速度很快??梢?,節(jié)流的優(yōu)化還是很明顯的。

關(guān)鍵是,優(yōu)化之后的流程性,沒有受到任何影響。

這個(gè)節(jié)流函數(shù)最終還是出現(xiàn)了一個(gè) bug:由于是延遲執(zhí)行的,導(dǎo)致 e.preventDefault 失效,在手機(jī)瀏覽器向下滑會(huì)出現(xiàn)刷新的情況,這也算事件延遲的一個(gè)危害吧。

解決辦法:在節(jié)流函數(shù)提前取消默認(rèn)事件:

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

大概花了三天左右的時(shí)間,將這個(gè) H5 的手勢(shì)解鎖給完成,自己還是比較滿意的,雖然可能達(dá)不到評(píng)委老師的認(rèn)可,不過(guò)自己在做的過(guò)程中,學(xué)習(xí)到了很多新知識(shí)。

參考

H5lock
Canvas教程
js獲取單選框里面的值
前端高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化

歡迎來(lái)我的博客交流。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82313.html

相關(guān)文章

  • ionic 2+ 手勢(shì)解鎖界面

    摘要:手勢(shì)解鎖界面一些對(duì)安全要求比較高的少不了鎖屏頁(yè)面,而手勢(shì)解鎖對(duì)于用戶來(lái)說(shuō)使用方便,對(duì)于程序員來(lái)說(shuō)小有挑戰(zhàn),怎么有棄之不用的道理。 ionic 2+ 手勢(shì)解鎖界面 一些對(duì)安全要求比較高的app少不了鎖屏頁(yè)面,而手勢(shì)解鎖對(duì)于用戶來(lái)說(shuō)使用方便,對(duì)于程序員來(lái)說(shuō)小有挑戰(zhàn),怎么有棄之不用的道理。 效果圖 效果圖處理短,方便大家閱讀showImg(https://segmentfault.co...

    Hancock_Xu 評(píng)論0 收藏0
  • 超級(jí)小的web手勢(shì)庫(kù)AlloyFinger發(fā)布

    摘要:擁有兩個(gè)版本,無(wú)依賴的獨(dú)立版和版本。除了對(duì)象,也可監(jiān)聽內(nèi)元素的手勢(shì)需要引擎內(nèi)置對(duì)象支持綁定相關(guān)事件。據(jù)不完全統(tǒng)計(jì),目前服務(wù)于興趣部落群動(dòng)漫騰訊學(xué)院騰訊等多個(gè)部門團(tuán)隊(duì)和項(xiàng)目。也可以在事件回調(diào)里根據(jù)攜帶的信息使用去操作。 簡(jiǎn)介 針對(duì)多點(diǎn)觸控設(shè)備編程的Web手勢(shì)組件,快速幫助你的web程序增加手勢(shì)支持,也不用再擔(dān)心click 300ms的延遲了。擁有兩個(gè)版本,無(wú)依賴的獨(dú)立版和react版本。...

    ymyang 評(píng)論0 收藏0
  • 小程序圖片剪裁

    摘要:基于上面原因,我采用的是里面放置圖片,監(jiān)聽上面的手勢(shì),通過(guò)樣式控制圖片的旋轉(zhuǎn)縮放和移動(dòng),最后剪裁用隱藏的。 一個(gè)微信小程序圖片剪裁組件,可以通過(guò)手勢(shì)控制旋轉(zhuǎn)縮放移動(dòng),也可以點(diǎn)擊旋轉(zhuǎn)進(jìn)行90度旋轉(zhuǎn),先看下效果(視屏不知道為啥用不了,上個(gè)壓縮過(guò)度的GIF先):showImg(https://segmentfault.com/img/bVbewtR?w=312&h=550); 圖片剪裁毫無(wú)疑...

    qylost 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<