vue封裝一個圖案手勢鎖組件
說在前面
??現(xiàn)在很多人都喜歡使用圖案手勢鎖,這里我使用vue來封裝了一個可以直接使用的組件,在這里記錄一下這個組件的開發(fā)步驟。
效果展示
組件實現(xiàn)效果如下圖:

預覽地址
http://jyeontu.xyz/jvuewheel/#/JAppsLock
實現(xiàn)步驟
完成一個組件需要幾步?
1.組件設(shè)計
首先我們應該要知道我們要做怎樣的組件,具備怎樣的功能,這樣才可以開始動手去實現(xiàn)。
功能上其實是已經(jīng)很明確了,就是仿照手機上現(xiàn)有的圖案鎖來進行網(wǎng)頁版組件開發(fā)。這里我們對入?yún)⒑突卣{(diào)先進行一個大致的設(shè)計。
size
圖案的尺寸,默認為3,即圖案的大小為 3 * 3,4的話即為4 * 4;
showArrow
是否顯示劃線軌跡箭頭,有的時候我們并不希望圖案劃到的軌跡箭頭顯示,這樣的保密性會更高,所以這里需要一個開關(guān)來控制箭頭的顯示與否;
commit
commit為劃動結(jié)束時的回調(diào)函數(shù),我們可以在父組件接收到劃動軌跡列表。
2.組件分析
接下來就需要對組件實現(xiàn)過程中使用到的關(guān)鍵技術(shù)點做一個分析了:
(1)觸屏事件&鼠標移動事件
我們需要在頁面上畫出圖案,那么我們肯定需要利用到網(wǎng)頁的觸屏事件和鼠標移動事件,鼠標移動事件主要是用于pc端,而在移動端使用時,我們則需要利用到網(wǎng)頁的觸屏事件。
(2)點之間的連線和箭頭方向
我們需要在劃到的相鄰的兩個點之間進行連線并用箭頭標出其劃線方向,這里我們需要借助一點數(shù)學三角函數(shù)的知識來計算,這里就不展開了,后面會對其進行分析解釋。
(3)連線完回調(diào)獲得滑動軌跡
這個時候我們需要監(jiān)聽鼠標抬起事件和觸屏結(jié)束事件,在鼠標抬起或觸屏結(jié)束的時候執(zhí)行回調(diào),將滑動的圖案軌跡以數(shù)組的形式返回。
3.組件實現(xiàn)
(1)鼠標事件和觸屏事件監(jiān)聽
首先我們應該先對鼠標事件和觸屏事件進行監(jiān)聽,這樣才可以捕捉到我們劃動圖案的軌跡。\
vue中的鼠標事件和觸屏事件
Vue中已經(jīng)為我們定義了鼠標事件和觸屏事件,我們可以直接這樣使用:
@mousedown.prevent="mousedown()" @touchstart.prevent="mousedown()" @mouseover="mouseover(cInd)" @touchmove="mouseover(cInd)"
JavaScript監(jiān)聽鼠標事件和觸屏事件
在JavaScript中我們需要對鼠標事件和觸屏事件進行監(jiān)聽:
const content = document.getElementById(this.JAppsLockId);
content.addEventListener("mousedown", this.mousedown);
content.addEventListener("mouseup", this.mouseup);
window.addEventListener("mouseup", this.mouseup);
content.addEventListener("touchstart", this.mousedown);
content.addEventListener("touchend", this.mouseup);
window.addEventListener("touchend", this.mouseup);
content.addEventListener("dragstart", () => {});
content.addEventListener("touchmove", this.touchmove);
(2)鼠標事件和觸屏事件定義 鼠標按下 & 手指觸屏開始
在組件內(nèi)鼠標按下或者手指觸屏開始的時候,我們應該做一個標記,標記當前狀態(tài)為鼠標按下狀態(tài)。
mousedown() {
this.isDown = true;
this.choosePoints = [];
this.removeLines();
},
鼠標移動 & 觸屏劃動
當當前為鼠標按下狀態(tài)且鼠標在移動時,我們需要判斷鼠標是否移動經(jīng)過某一個點,這里的鼠標移動事件和觸屏劃動事件有點區(qū)別,需要分別定義。
mouseover(ind) {
if (!this.isDown) return;
if (this.choosePoints.includes(ind)) return;
this.choosePoints.push(ind);
},
touchmove(event) {
if (!this.isDown) return;
if (this.pointsArea.length === 0) {
this.initPointsArea();
}
const content = document.getElementById(this.JAppsLockId + "lock");
let nx = event.targetTouches[0].pageX - content.offsetLeft;
let ny = event.targetTouches[0].pageY - content.offsetTop;
for (let i = 0; i < this.pointsArea.length; i++) {
const item = this.pointsArea[i];
const { x, y, r } = item;
if (Math.pow(x - nx, 2) + Math.pow(y - ny, 2) <= r * r) {
if (this.choosePoints.includes(i)) return;
this.choosePoints.push(i);
break;
}
}
},
(3)鼠標抬起和觸屏劃動結(jié)束回調(diào)
在鼠標抬起和觸屏劃動結(jié)束的時候需要進行回調(diào),將當前劃動過程中經(jīng)過的圖案軌跡輸出。
mouseup() {
if (!this.isDown) return;
this.isDown = false;
this.drawLine();
this.$emit("commit", this.choosePoints);
},
(4)組件數(shù)據(jù)初始化
我們需要先確定當前組件的id,當父組件定義了子組件的id時則使用定義的id,否則則自動生成id
initData() {
let id = this.id;
if (id == "") {
id = getUId();
}
this.JAppsLockId = id;
},
(5)圖案數(shù)據(jù)初始化
我們需要根據(jù)傳來的size參數(shù)來渲染不同尺寸的圖案點陣。
initCell() {
const id = this.JAppsLockId;
const size = this.size;
const content = document.getElementById(id);
const cH = content.offsetHeight;
const cW = content.offsetWidth;
const cellH = (cH - 20 - size * 6 * 2) / size + "px";
const cellW = (cW - 20 - size * 6 * 2) / size + "px";
this.cellH = cellH;
this.cellW = cellW;
}
(6)獲取圖案點陣的位置數(shù)據(jù)
我們可以先獲取圖案點陣的圓心坐標及半徑,為后續(xù)進行判斷計算作準備。
initPointsArea() {
this.pointsArea === [];
const cell = document.getElementsByClassName("j-apps-lock-cell")[0];
for (let i = 0; i < this.size * this.size; i++) {
const point = document.getElementById("point-" + i);
const x =
(point.offsetLeft + point.offsetWidth + point.offsetLeft) /
2;
const y =
(point.offsetTop + point.offsetHeight + point.offsetTop) /
2;
const r = cell.offsetHeight / 2;
this.pointsArea.push({ x, y, r });
}
},
(7)圖案連線 首先我們需要先計算好需要連線的兩個圖案的坐標。
drawLine() {
const domPoints = this.getPoints();
for (let i = 1; i < domPoints.length; i++) {
const x1 =
domPoints[i - 1].offsetWidth + domPoints[i - 1].offsetLeft;
const x2 = domPoints[i].offsetWidth + domPoints[i].offsetLeft;
const y1 =
domPoints[i - 1].offsetHeight + domPoints[i - 1].offsetTop;
const y2 = domPoints[i].offsetHeight + domPoints[i].offsetTop;
this.createLine(
x1,
x2,
y1,
y2,
domPoints[i - 1],
domPoints[i]
);
}
}
通過計算好的坐標數(shù)據(jù),生成對應的線段
createLine(x1, x2, y1, y2, p1, p2) {
let line = document.createElement("span");
line.classList.add("j-apps-lock-line");
line.style.position = "absolute";
line.style.display = "flex";
line.style.left = "50%";
line.style.top = "50%";
line.style.margin = "center";
line.style.width = Math.max(Math.abs(x2 - x1), 2) + "px";
line.style.height = Math.max(Math.abs(y2 - y1), 2) + "px";
line.style.backgroundColor = "gray";
if (this.showArrow)
line.appendChild(this.createArrow(x1, x2, y1, y2));
if (x1 != x2 && y1 != y2) {
const x = Math.abs(x1 - x2);
const y = Math.abs(y1 - y2);
line.style.height = Math.sqrt(x * x + y * y) + "px";
line.style.width = "2px";
let angle = (Math.atan(x / y) * 180) / Math.PI;
if ((x2 > x1 && y2 > y1) || (x2 < x1 && y2 < y1))
angle = "-" + angle;
line.style.transform = `rotate(${angle}deg)`;
line.style.transformOrigin = "left top";
if (y2 > y1) p1.appendChild(line);
else p2.appendChild(line);
} else if (x2 > x1 || y2 > y1) {
p1.appendChild(line);
} else {
p2.appendChild(line);
}
return line;
},
由上面代碼我們可以看到,在連線的繪制中,我們使用到了css中的旋轉(zhuǎn)屬性,其旋轉(zhuǎn)角度是使用Math.atan計算出來的,所以我們需要先對三角函數(shù)進行一定了解。
javascript中計算三角函數(shù)

三角函數(shù)的定義
正弦(sin) sinA = a / c sinθ = y / r 余弦(cos) cosA = b / c cosθ = y / r 正切(tan) tanA = a / b tanθ = y / x 余切(cot) cotA = b / a cotθ = x / y js中計算三角函數(shù)用Math.sin()等靜態(tài)方法,參數(shù)為弧度
角度與弧度都是角的度量單位
角度:兩條射線從圓心向圓周射出,形成一個夾角和夾角正對的一段弧。當這段弧長正好等于圓周長的360分之一時,兩條射線的夾角的大小為1度。
弧度:兩條射線從圓心向圓周射出,形成一個夾角和夾角正對的一段弧。當這段弧長正好等于圓的半徑時,兩條射線的夾角大小為1弧度。
1弧度時,弧長等于半徑,那弧長是半徑的倍數(shù)就是弧度了 弧度 = 弧長 / 半徑 弧長 = 弧度 * 半徑 弧長 = (角度 / 360) * 周長
角度與弧度換算
角度 = 弧長 / 周長 = 弧長/(2πr) = 弧度*r/(2πr) = 弧度/(2π)
弧度 = 弧長 / 半徑 = [(角度 / 360) * 周長] / 半徑 =[ (角度 / 360) * 2πr] / r = 角度 * π / 180
js計算三角函數(shù)
var sin30 = Math.sin(30 * Math.PI / 180)
console.log(sin30); //0.49999999999999994
var cos60 = Math.cos(60 * Math.PI / 180)
console.log(cos60); //0.5000000000000001
var tan45 = Math.tan(45 * Math.PI / 180)
console.log(tan45); //0.9999999999999999
var asin30 = Math.round(Math.asin(sin30) * 180 / Math.PI)
console.log(asin30); //30
var acos60 = Math.round(Math.acos(cos60) * 180 / Math.PI)
console.log(acos60); //60
var atan45 = Math.round(Math.atan(tan45) * 180 / Math.PI)
console.log(atan45); //45
(8)圖案連線軌跡箭頭
我們只需要將箭頭元素添加到線段元素中,作為線段元素的子元素,我們便不用單獨對箭頭元素的旋轉(zhuǎn)角度進行處理。
createArrow(x1, x2, y1, y2) {
let arrow = document.createElement("span");
arrow.classList.add("j-apps-lock-arrow");
arrow.style.position = "relative";
arrow.style.margin = "auto";
arrow.style.fontSize = "1.5rem";
arrow.style.zIndex = "10";
arrow.style.display = "block";
arrow.style.minWidth = "1.4rem";
arrow.style.textAlign = "center";
if (y1 === y2) {
arrow.innerText = x1 > x2 ? "<" : ">";
arrow.style.top = "-0.8rem";
} else {
arrow.innerText = y1 > y2 ? "∧" : "∨";
arrow.style.left = "-0.65rem";
}
return arrow;
},
4.組件使用

<template>
<div class="content">
<j-apps-lock @commit="commit" size="4"></j-apps-lock>
</div>
</template>
<script>
export default {
data() {
return {
}
},
methods:{
commit(password) {
this.$JToast(password);
}
}
}
</script>
組件庫引用
這里我將這個組件打包進了自己的一個組件庫,并將其發(fā)布到了npm上,有需要的同學也可以直接引入該組件進行使用。
引入教程可以看這里:http://jyeontu.xyz/jvuewheel/#/installView
引入后即可直接使用。
源碼地址
組件庫已開源,想要查看完整源碼的可以到 gitee 查看,自己也整理了相關(guān)的文檔對其進行了簡單介紹,具體如下:
組件文檔
jvuewheel: http://jyeontu.xyz/jvuewheel/#/JBarrageView
Gitee源碼
Gitee源碼:https://gitee.com/zheng_yongtao/jyeontu-component-warehouse
到此這篇關(guān)于vue封裝一個圖案手勢鎖組件的文章就介紹到這了,更多相關(guān)vue 圖案手勢鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue實現(xiàn)表單數(shù)據(jù)的增刪改功能
這篇文章主要為大家詳細介紹了vue實現(xiàn)表單數(shù)據(jù)的增刪改功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
vue?MVVM雙向綁定實例詳解(數(shù)據(jù)劫持+發(fā)布者-訂閱者模式)
使用vue也好有一段時間了,也算對其雙向綁定原理也有了解個大概,這篇文章主要給大家介紹了關(guān)于vue?MVVM雙向綁定(數(shù)據(jù)劫持+發(fā)布者-訂閱者模式)的相關(guān)資料,需要的朋友可以參考下2022-03-03

