vue封裝一個(gè)圖案手勢(shì)鎖組件
說(shuō)在前面
??現(xiàn)在很多人都喜歡使用圖案手勢(shì)鎖,這里我使用vue來(lái)封裝了一個(gè)可以直接使用的組件,在這里記錄一下這個(gè)組件的開(kāi)發(fā)步驟。
效果展示
組件實(shí)現(xiàn)效果如下圖:
預(yù)覽地址
http://jyeontu.xyz/jvuewheel/#/JAppsLock
實(shí)現(xiàn)步驟
完成一個(gè)組件需要幾步?
1.組件設(shè)計(jì)
首先我們應(yīng)該要知道我們要做怎樣的組件,具備怎樣的功能,這樣才可以開(kāi)始動(dòng)手去實(shí)現(xiàn)。
功能上其實(shí)是已經(jīng)很明確了,就是仿照手機(jī)上現(xiàn)有的圖案鎖來(lái)進(jìn)行網(wǎng)頁(yè)版組件開(kāi)發(fā)。這里我們對(duì)入?yún)⒑突卣{(diào)先進(jìn)行一個(gè)大致的設(shè)計(jì)。
size
圖案的尺寸,默認(rèn)為3,即圖案的大小為 3 * 3,4的話即為4 * 4;
showArrow
是否顯示劃線軌跡箭頭,有的時(shí)候我們并不希望圖案劃到的軌跡箭頭顯示,這樣的保密性會(huì)更高,所以這里需要一個(gè)開(kāi)關(guān)來(lái)控制箭頭的顯示與否;
commit
commit為劃動(dòng)結(jié)束時(shí)的回調(diào)函數(shù),我們可以在父組件接收到劃動(dòng)軌跡列表。
2.組件分析
接下來(lái)就需要對(duì)組件實(shí)現(xiàn)過(guò)程中使用到的關(guān)鍵技術(shù)點(diǎn)做一個(gè)分析了:
(1)觸屏事件&鼠標(biāo)移動(dòng)事件
我們需要在頁(yè)面上畫(huà)出圖案,那么我們肯定需要利用到網(wǎng)頁(yè)的觸屏事件和鼠標(biāo)移動(dòng)事件,鼠標(biāo)移動(dòng)事件主要是用于pc端,而在移動(dòng)端使用時(shí),我們則需要利用到網(wǎng)頁(yè)的觸屏事件。
(2)點(diǎn)之間的連線和箭頭方向
我們需要在劃到的相鄰的兩個(gè)點(diǎn)之間進(jìn)行連線并用箭頭標(biāo)出其劃線方向,這里我們需要借助一點(diǎn)數(shù)學(xué)三角函數(shù)的知識(shí)來(lái)計(jì)算,這里就不展開(kāi)了,后面會(huì)對(duì)其進(jìn)行分析解釋。
(3)連線完回調(diào)獲得滑動(dòng)軌跡
這個(gè)時(shí)候我們需要監(jiān)聽(tīng)鼠標(biāo)抬起事件和觸屏結(jié)束事件,在鼠標(biāo)抬起或觸屏結(jié)束的時(shí)候執(zhí)行回調(diào),將滑動(dòng)的圖案軌跡以數(shù)組的形式返回。
3.組件實(shí)現(xiàn)
(1)鼠標(biāo)事件和觸屏事件監(jiān)聽(tīng)
首先我們應(yīng)該先對(duì)鼠標(biāo)事件和觸屏事件進(jìn)行監(jiān)聽(tīng),這樣才可以捕捉到我們劃動(dòng)圖案的軌跡。\
vue中的鼠標(biāo)事件和觸屏事件
Vue中已經(jīng)為我們定義了鼠標(biāo)事件和觸屏事件,我們可以直接這樣使用:
@mousedown.prevent="mousedown()" @touchstart.prevent="mousedown()" @mouseover="mouseover(cInd)" @touchmove="mouseover(cInd)"
JavaScript監(jiān)聽(tīng)鼠標(biāo)事件和觸屏事件
在JavaScript中我們需要對(duì)鼠標(biāo)事件和觸屏事件進(jìn)行監(jiān)聽(tīng):
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)鼠標(biāo)事件和觸屏事件定義 鼠標(biāo)按下 & 手指觸屏開(kāi)始
在組件內(nèi)鼠標(biāo)按下或者手指觸屏開(kāi)始的時(shí)候,我們應(yīng)該做一個(gè)標(biāo)記,標(biāo)記當(dāng)前狀態(tài)為鼠標(biāo)按下?tīng)顟B(tài)。
mousedown() { this.isDown = true; this.choosePoints = []; this.removeLines(); },
鼠標(biāo)移動(dòng) & 觸屏劃動(dòng)
當(dāng)當(dāng)前為鼠標(biāo)按下?tīng)顟B(tài)且鼠標(biāo)在移動(dòng)時(shí),我們需要判斷鼠標(biāo)是否移動(dòng)經(jīng)過(guò)某一個(gè)點(diǎn),這里的鼠標(biāo)移動(dòng)事件和觸屏劃動(dòng)事件有點(diǎn)區(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)鼠標(biāo)抬起和觸屏劃動(dòng)結(jié)束回調(diào)
在鼠標(biāo)抬起和觸屏劃動(dòng)結(jié)束的時(shí)候需要進(jìn)行回調(diào),將當(dāng)前劃動(dòng)過(guò)程中經(jīng)過(guò)的圖案軌跡輸出。
mouseup() { if (!this.isDown) return; this.isDown = false; this.drawLine(); this.$emit("commit", this.choosePoints); },
(4)組件數(shù)據(jù)初始化
我們需要先確定當(dāng)前組件的id,當(dāng)父組件定義了子組件的id時(shí)則使用定義的id,否則則自動(dòng)生成id
initData() { let id = this.id; if (id == "") { id = getUId(); } this.JAppsLockId = id; },
(5)圖案數(shù)據(jù)初始化
我們需要根據(jù)傳來(lái)的size參數(shù)來(lái)渲染不同尺寸的圖案點(diǎn)陣。
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)獲取圖案點(diǎn)陣的位置數(shù)據(jù)
我們可以先獲取圖案點(diǎn)陣的圓心坐標(biāo)及半徑,為后續(xù)進(jìn)行判斷計(jì)算作準(zhǔn)備。
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)圖案連線 首先我們需要先計(jì)算好需要連線的兩個(gè)圖案的坐標(biāo)。
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] ); } }
通過(guò)計(jì)算好的坐標(biāo)數(shù)據(jù),生成對(duì)應(yīng)的線段
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
計(jì)算出來(lái)的,所以我們需要先對(duì)三角函數(shù)進(jìn)行一定了解。
javascript中計(jì)算三角函數(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中計(jì)算三角函數(shù)用Math.sin()等靜態(tài)方法,參數(shù)為弧度
角度與弧度都是角的度量單位
角度:兩條射線從圓心向圓周射出,形成一個(gè)夾角和夾角正對(duì)的一段弧。當(dāng)這段弧長(zhǎng)正好等于圓周長(zhǎng)的360分之一時(shí),兩條射線的夾角的大小為1度。
弧度:兩條射線從圓心向圓周射出,形成一個(gè)夾角和夾角正對(duì)的一段弧。當(dāng)這段弧長(zhǎng)正好等于圓的半徑時(shí),兩條射線的夾角大小為1弧度。
1弧度時(shí),弧長(zhǎng)等于半徑,那弧長(zhǎng)是半徑的倍數(shù)就是弧度了 弧度 = 弧長(zhǎng) / 半徑 弧長(zhǎng) = 弧度 * 半徑 弧長(zhǎng) = (角度 / 360) * 周長(zhǎng)
角度與弧度換算
角度 = 弧長(zhǎng) / 周長(zhǎng) = 弧長(zhǎng)/(2πr) = 弧度*r/(2πr) = 弧度/(2π)
弧度 = 弧長(zhǎng) / 半徑 = [(角度 / 360) * 周長(zhǎng)] / 半徑 =[ (角度 / 360) * 2πr] / r = 角度 * π / 180
js計(jì)算三角函數(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)圖案連線軌跡箭頭
我們只需要將箭頭元素添加到線段元素中,作為線段元素的子元素,我們便不用單獨(dú)對(duì)箭頭元素的旋轉(zhuǎn)角度進(jì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>
組件庫(kù)引用
這里我將這個(gè)組件打包進(jìn)了自己的一個(gè)組件庫(kù),并將其發(fā)布到了npm上,有需要的同學(xué)也可以直接引入該組件進(jìn)行使用。
引入教程可以看這里:http://jyeontu.xyz/jvuewheel/#/installView
引入后即可直接使用。
源碼地址
組件庫(kù)已開(kāi)源,想要查看完整源碼的可以到 gitee 查看,自己也整理了相關(guān)的文檔對(duì)其進(jìn)行了簡(jiǎn)單介紹,具體如下:
組件文檔
jvuewheel: http://jyeontu.xyz/jvuewheel/#/JBarrageView
Gitee源碼
Gitee源碼:https://gitee.com/zheng_yongtao/jyeontu-component-warehouse
到此這篇關(guān)于vue封裝一個(gè)圖案手勢(shì)鎖組件的文章就介紹到這了,更多相關(guān)vue 圖案手勢(shì)鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
學(xué)習(xí)vue.js中class與style綁定
這篇文章主要和大家一起學(xué)習(xí)vue.js中class與style綁定操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12關(guān)于Vue的 watch、computed和methods的區(qū)別匯總
這篇文章主要介紹關(guān)于Vue的 watch、computed和methods的區(qū)別,下面文章將圍繞Vue的 watch、computed和methods的續(xù)航管資料展開(kāi)全文它們之間區(qū)別的內(nèi)容,需要的朋友可以參考一下,希望能幫助到大家2021-11-11vue directive全局自定義指令實(shí)現(xiàn)按鈕級(jí)別權(quán)限控制的操作方法
這篇文章主要介紹了vue directive全局自定義指令實(shí)現(xiàn)按鈕級(jí)別權(quán)限控制,本文結(jié)合實(shí)例代碼對(duì)基本概念做了詳細(xì)講解,需要的朋友可以參考下2023-02-02el-menu實(shí)現(xiàn)橫向溢出截取的示例代碼
在進(jìn)行vue開(kāi)發(fā)的時(shí)候,我們不可避免會(huì)使用到導(dǎo)航菜單,element方便的為我們提供了導(dǎo)航菜單組件,下面這篇文章主要給大家介紹了關(guān)于el-menu實(shí)現(xiàn)橫向溢出截取的相關(guān)資料,需要的朋友可以參考下2022-06-06iview table render集成switch開(kāi)關(guān)的實(shí)例
下面小編就為大家分享一篇iview table render集成switch開(kāi)關(guān)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03詳解Vue監(jiān)聽(tīng)數(shù)據(jù)變化原理
本篇文章主要介紹了Vue監(jiān)聽(tīng)數(shù)據(jù)變化,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03Django Vue實(shí)現(xiàn)動(dòng)態(tài)菜單和動(dòng)態(tài)權(quán)限
本文主要介紹了Django Vue實(shí)現(xiàn)動(dòng)態(tài)菜單和動(dòng)態(tài)權(quán)限,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06