微信小程序 手勢(shì)解鎖組件(無(wú)卡頓)

本人博客文章地址:點(diǎn)擊進(jìn)入

代碼地址:
Mpvue版本: https://github.com/geminate/mpvue-gesture-lock
原生小程序版本:https://github.com/geminate/mini-gesture-lock

一. 簡(jiǎn)介

最近在 開(kāi)發(fā)小程序的時(shí)候 遇到了這種手勢(shì)解鎖的需求,網(wǎng)上逛了一圈基本都是使用Canvas實(shí)現(xiàn)的,經(jīng)過(guò)本人測(cè)試,所有使用Canvas實(shí)現(xiàn)的解鎖組件,在Android實(shí)機(jī)測(cè)試時(shí)均存在嚴(yán)重卡頓問(wèn)題。原因是 小程序的 canvas onTouchMove事件效率很低(2018/08/17 測(cè)試),吐槽一句,小程序現(xiàn)在有很多的坑官方都不去處理,論壇里一大堆問(wèn)題也沒(méi)人解決。。。。

既然微信官方暫時(shí)沒(méi)有要解決這個(gè)問(wèn)題的意思,那咱們開(kāi)發(fā)者就只能自己想辦法了,于是本人**使用 dom 實(shí)現(xiàn) **了一個(gè)基礎(chǔ)版本的 手勢(shì)解鎖組件,有兩個(gè)版本,分別使用mpvue 和 小程序原生寫(xiě)法。

效果圖:

gesture.gif

二. 核心實(shí)現(xiàn)

由于 mpvue 版本 和 小程序原生版本思路基本一致,僅代碼寫(xiě)法略有不同,因此一下均以 mpvue 代碼做說(shuō)明。

1. 布局代碼

<div class="gesture-lock"
       :class="{error:error}"
       :style="{width: containerWidth +'rpx', height:containerWidth +'rpx'}"
       @touchstart="onTouchStart"
       @touchmove="onTouchMove"
       @touchend="onTouchEnd"
  >
    <!-- 9 個(gè)圓 -->
    <div v-for="(item,i) in circleArray" :key="i" class="cycle" :class="{check:item.check}"
         :style="{left:item.style.left,top:item.style.top,width:item.style.width,height:item.style.width}">
    </div>

    <!-- 已激活鎖之間的線段 -->
    <div v-for="(item,i) in lineArray" :key="i" class="line"
         :style="{left:item.activeLeft,top:item.activeTop,width:item.activeWidth,transform:'rotate('+item.activeRotate+')'}">
    </div>

    <!-- 最后一個(gè)激活的鎖與當(dāng)前位置之間的線段 -->
    <div class="line"
         :style="{left:activeLine.activeLeft,top:activeLine.activeTop,width:activeLine.activeWidth,transform:'rotate('+activeLine.activeRotate+')'}">
    </div>
  </div>

布局代碼主要分為 3 部分:9個(gè)圓形鎖、已激活的鎖之間的線段 和 最后一個(gè)激活的鎖與當(dāng)前手指位置之間的線段。全部 線段 與 圓 均通過(guò) dom和樣式實(shí)現(xiàn)。避免canvas卡頓。

2. JS邏輯

(1). 初始化

constructor(containerWidth, cycleRadius) {
    // ....

    this.windowWidth = wx.getSystemInfoSync().windowWidth;// 窗口大小(用于rpx 和 px 轉(zhuǎn)換)

    this.initCircleArray();
  }

  // 初始化 畫(huà)布上的 9個(gè)圓
  initCircleArray() {
    const cycleMargin = (this.containerWidth - 6 * this.cycleRadius) / 6;
    let count = 0;
    for (let i = 0; i < 3; i++) {
      for (let j = 0; j < 3; j++) {
        count++;
        this.circleArray.push({
          count: count,
          x: this.rpxTopx((cycleMargin + this.cycleRadius) * (j * 2 + 1)),
          y: this.rpxTopx((cycleMargin + this.cycleRadius) * (i * 2 + 1)),
          radius: this.rpxTopx(this.cycleRadius),
          check: false,
          style: {
            left: (cycleMargin + this.cycleRadius) * (j * 2 + 1) - this.cycleRadius + 'rpx',
            top: (cycleMargin + this.cycleRadius) * (i * 2 + 1) - this.cycleRadius + 'rpx',
            width: this.cycleRadius * 2 + 'rpx',
          }
        });
      }
    }
  }

初始化的時(shí)候,需要將9個(gè)圓的對(duì)象數(shù)組初始化,根據(jù)輸入的容器寬度 和 鎖半徑計(jì)算出9個(gè)鎖的位置及對(duì)應(yīng)的css樣式。這里需要注意rpx與px之前的轉(zhuǎn)換。

(2). onTouchStart

onTouchStart(e) {
    this.setOffset(e);
    this.checkTouch({x: e.pageX - this.offsetX, y: e.pageY - this.offsetY});
 }

// 檢測(cè)當(dāng)時(shí) 觸摸位置是否位于 鎖上
  checkTouch({x, y}) {
    for (let i = 0; i < this.circleArray.length; i++) {
      let point = this.circleArray[i];
      if (this.isPointInCycle(x, y, point.x, point.y, point.radius)) {
        if (!point.check) {
          this.checkPoints.push(point.count);
          if (this.lastCheckPoint != 0) {
            // 已激活鎖之間的線段
            const line = this.drawLine(this.lastCheckPoint, point);
            this.lineArray.push(line);
          }
          this.lastCheckPoint = point;
        }
        point.check = true;
        return;
      }
    }
  }

當(dāng)手指按下的時(shí)候,首先需要獲取到 容器的 offset,然后檢查當(dāng)前手指的位置是否位于 鎖圓 內(nèi)部,如果位于內(nèi)部的化將這個(gè)鎖變?yōu)橐鸭せ顮顟B(tài),并壓入激活鎖數(shù)組。

(3). onTouchMove

onTouchMove(e) {
    this.moveDraw(e)
  }

// 移動(dòng) 繪制
  moveDraw(e) {
    // 畫(huà)經(jīng)過(guò)的圓
    const x = e.pageX - this.offsetX;
    const y = e.pageY - this.offsetY;
    this.checkTouch({x, y});

    // 畫(huà) 最后一個(gè)激活的鎖與當(dāng)前位置之間的線段
    this.activeLine = this.drawLine(this.lastCheckPoint, {x, y});
  }

當(dāng)手指在按下并移動(dòng)的時(shí)候,實(shí)時(shí)檢查當(dāng)前手指的位置是否在未激活的鎖上,如果位于未激活的鎖上,則將其激活并壓入激活鎖數(shù)組,并按順序繪制激活鎖之間的連線。除此之外還需要繪制上一個(gè)激活鎖到當(dāng)前手指位置的連線。

(4).onTouchEnd

onTouchEnd(e) {
    const checkPoints = this.checkPoints;
    this.reset();
    return checkPoints;
  }

手指放開(kāi)的時(shí)候 清空全部狀態(tài)

作者博客地址:https://liuhuihao.com
作者gitHub:https://github.com/geminate

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容