本人博客文章地址:點(diǎn)擊進(jìn)入
代碼地址:
Mpvue版本: https://github.com/geminate/mpvue-gesture-lock
原生小程序版本:https://github.com/geminate/mini-gesture-lock
一. 簡介
最近在 開發(fā)小程序的時候 遇到了這種手勢解鎖的需求,網(wǎng)上逛了一圈基本都是使用Canvas實(shí)現(xiàn)的,經(jīng)過本人測試踏揣,所有使用Canvas實(shí)現(xiàn)的解鎖組件恼琼,在Android實(shí)機(jī)測試時均存在嚴(yán)重卡頓問題救崔。原因是 小程序的 canvas onTouchMove事件效率很低(2018/08/17 測試)琉挖,吐槽一句凌受,小程序現(xiàn)在有很多的坑官方都不去處理店雅,論壇里一大堆問題也沒人解決政基。。闹啦。沮明。
既然微信官方暫時沒有要解決這個問題的意思,那咱們開發(fā)者就只能自己想辦法了窍奋,于是本人**使用 dom 實(shí)現(xiàn) **了一個基礎(chǔ)版本的 手勢解鎖組件荐健,有兩個版本,分別使用mpvue 和 小程序原生寫法琳袄。
效果圖:
二. 核心實(shí)現(xiàn)
由于 mpvue 版本 和 小程序原生版本思路基本一致江场,僅代碼寫法略有不同,因此一下均以 mpvue 代碼做說明窖逗。
1. 布局代碼
<div class="gesture-lock"
:class="{error:error}"
:style="{width: containerWidth +'rpx', height:containerWidth +'rpx'}"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
>
<!-- 9 個圓 -->
<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>
<!-- 最后一個激活的鎖與當(dāng)前位置之間的線段 -->
<div class="line"
:style="{left:activeLine.activeLeft,top:activeLine.activeTop,width:activeLine.activeWidth,transform:'rotate('+activeLine.activeRotate+')'}">
</div>
</div>
布局代碼主要分為 3 部分:9個圓形鎖址否、已激活的鎖之間的線段 和 最后一個激活的鎖與當(dāng)前手指位置之間的線段。全部 線段 與 圓 均通過 dom和樣式實(shí)現(xiàn)碎紊。避免canvas卡頓佑附。
2. JS邏輯
(1). 初始化
constructor(containerWidth, cycleRadius) {
// ....
this.windowWidth = wx.getSystemInfoSync().windowWidth;// 窗口大小(用于rpx 和 px 轉(zhuǎn)換)
this.initCircleArray();
}
// 初始化 畫布上的 9個圓
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',
}
});
}
}
}
初始化的時候樊诺,需要將9個圓的對象數(shù)組初始化,根據(jù)輸入的容器寬度 和 鎖半徑計算出9個鎖的位置及對應(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});
}
// 檢測當(dāng)時 觸摸位置是否位于 鎖上
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)手指按下的時候,首先需要獲取到 容器的 offset瘟斜,然后檢查當(dāng)前手指的位置是否位于 鎖圓 內(nèi)部缸夹,如果位于內(nèi)部的化將這個鎖變?yōu)橐鸭せ顮顟B(tài),并壓入激活鎖數(shù)組螺句。
(3). onTouchMove
onTouchMove(e) {
this.moveDraw(e)
}
// 移動 繪制
moveDraw(e) {
// 畫經(jīng)過的圓
const x = e.pageX - this.offsetX;
const y = e.pageY - this.offsetY;
this.checkTouch({x, y});
// 畫 最后一個激活的鎖與當(dāng)前位置之間的線段
this.activeLine = this.drawLine(this.lastCheckPoint, {x, y});
}
當(dāng)手指在按下并移動的時候虽惭,實(shí)時檢查當(dāng)前手指的位置是否在未激活的鎖上,如果位于未激活的鎖上蛇尚,則將其激活并壓入激活鎖數(shù)組芽唇,并按順序繪制激活鎖之間的連線。除此之外還需要繪制上一個激活鎖到當(dāng)前手指位置的連線取劫。
(4).onTouchEnd
onTouchEnd(e) {
const checkPoints = this.checkPoints;
this.reset();
return checkPoints;
}
手指放開的時候 清空全部狀態(tài)
作者博客地址:https://liuhuihao.com
作者gitHub:https://github.com/geminate