vue封裝一個圖案手勢鎖組件

說在前面

🎈現在很多人都喜歡使用圖案手勢鎖,這裡我使用vue來封裝瞭一個可以直接使用的組件,在這裡記錄一下這個組件的開發步驟。

效果展示

組件實現效果如下圖:

JYeontu組件庫 - Google Chrome 2022_5_30 1_01_58 00_00_00-00_00_30.gif

預覽地址

http://jyeontu.xyz/jvuewheel/#/JAppsLock

實現步驟

完成一個組件需要幾步?

1.組件設計

首先我們應該要知道我們要做怎樣的組件,具備怎樣的功能,這樣才可以開始動手去實現。
功能上其實是已經很明確瞭,就是仿照手機上現有的圖案鎖來進行網頁版組件開發。這裡我們對入參和回調先進行一個大致的設計。

size

圖案的尺寸,默認為3,即圖案的大小為 3 * 3,4的話即為4 * 4;

showArrow

是否顯示劃線軌跡箭頭,有的時候我們並不希望圖案劃到的軌跡箭頭顯示,這樣的保密性會更高,所以這裡需要一個開關來控制箭頭的顯示與否;

commit

commit為劃動結束時的回調函數,我們可以在父組件接收到劃動軌跡列表。

2.組件分析

接下來就需要對組件實現過程中使用到的關鍵技術點做一個分析瞭:

(1)觸屏事件&鼠標移動事件

我們需要在頁面上畫出圖案,那麼我們肯定需要利用到網頁的觸屏事件和鼠標移動事件,鼠標移動事件主要是用於pc端,而在移動端使用時,我們則需要利用到網頁的觸屏事件。

(2)點之間的連線和箭頭方向

我們需要在劃到的相鄰的兩個點之間進行連線並用箭頭標出其劃線方向,這裡我們需要借助一點數學三角函數的知識來計算,這裡就不展開瞭,後面會對其進行分析解釋。

(3)連線完回調獲得滑動軌跡

這個時候我們需要監聽鼠標抬起事件和觸屏結束事件,在鼠標抬起或觸屏結束的時候執行回調,將滑動的圖案軌跡以數組的形式返回。

3.組件實現

(1)鼠標事件和觸屏事件監聽

首先我們應該先對鼠標事件和觸屏事件進行監聽,這樣才可以捕捉到我們劃動圖案的軌跡。\

vue中的鼠標事件和觸屏事件
Vue中已經為我們定義瞭鼠標事件和觸屏事件,我們可以直接這樣使用:

@mousedown.prevent="mousedown()"
@touchstart.prevent="mousedown()"
@mouseover="mouseover(cInd)"
@touchmove="mouseover(cInd)"

JavaScript監聽鼠標事件和觸屏事件
在JavaScript中我們需要對鼠標事件和觸屏事件進行監聽:

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)鼠標事件和觸屏事件定義 鼠標按下 & 手指觸屏開始
在組件內鼠標按下或者手指觸屏開始的時候,我們應該做一個標記,標記當前狀態為鼠標按下狀態。

mousedown() {
    this.isDown = true;
    this.choosePoints = [];
    this.removeLines();
},

鼠標移動 & 觸屏劃動
當當前為鼠標按下狀態且鼠標在移動時,我們需要判斷鼠標是否移動經過某一個點,這裡的鼠標移動事件和觸屏劃動事件有點區別,需要分別定義。

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)鼠標抬起和觸屏劃動結束回調

在鼠標抬起和觸屏劃動結束的時候需要進行回調,將當前劃動過程中經過的圖案軌跡輸出。

mouseup() {
    if (!this.isDown) return;
    this.isDown = false;
    this.drawLine();
    this.$emit("commit", this.choosePoints);
},

(4)組件數據初始化

我們需要先確定當前組件的id,當父組件定義瞭子組件的id時則使用定義的id,否則則自動生成id

initData() {
    let id = this.id;
    if (id == "") {
        id = getUId();
    }
    this.JAppsLockId = id;
},

(5)圖案數據初始化

我們需要根據傳來的size參數來渲染不同尺寸的圖案點陣。

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)獲取圖案點陣的位置數據

我們可以先獲取圖案點陣的圓心坐標及半徑,為後續進行判斷計算作準備。

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

通過計算好的坐標數據,生成對應的線段

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中的旋轉屬性,其旋轉角度是使用Math.atan計算出來的,所以我們需要先對三角函數進行一定瞭解。

javascript中計算三角函數

image.png

三角函數的定義

正弦(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中計算三角函數用Math.sin()等靜態方法,參數為弧度

角度與弧度都是角的度量單位

角度:兩條射線從圓心向圓周射出,形成一個夾角和夾角正對的一段弧。當這段弧長正好等於圓周長的360分之一時,兩條射線的夾角的大小為1度。
弧度:兩條射線從圓心向圓周射出,形成一個夾角和夾角正對的一段弧。當這段弧長正好等於圓的半徑時,兩條射線的夾角大小為1弧度。

1弧度時,弧長等於半徑,那弧長是半徑的倍數就是弧度瞭
弧度 = 弧長 / 半徑
弧長 = 弧度 * 半徑
弧長 = (角度 / 360) * 周長

角度與弧度換算

角度 = 弧長 / 周長 = 弧長/(2πr) = 弧度*r/(2πr) = 弧度/(2π)
弧度 = 弧長 / 半徑 = [(角度 / 360) * 周長] / 半徑 =[ (角度 / 360) * 2πr] / r = 角度 * π / 180

js計算三角函數

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)圖案連線軌跡箭頭

我們隻需要將箭頭元素添加到線段元素中,作為線段元素的子元素,我們便不用單獨對箭頭元素的旋轉角度進行處理。

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.組件使用

image.png

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

組件庫引用

這裡我將這個組件打包進瞭自己的一個組件庫,並將其發佈到瞭npm上,有需要的同學也可以直接引入該組件進行使用。
引入教程可以看這裡:http://jyeontu.xyz/jvuewheel/#/installView
引入後即可直接使用。

源碼地址

組件庫已開源,想要查看完整源碼的可以到 gitee 查看,自己也整理瞭相關的文檔對其進行瞭簡單介紹,具體如下:

組件文檔

jvuewheel: http://jyeontu.xyz/jvuewheel/#/JBarrageView

Gitee源碼

Gitee源碼:https://gitee.com/zheng_yongtao/jyeontu-component-warehouse

到此這篇關於vue封裝一個圖案手勢鎖組件的文章就介紹到這瞭,更多相關vue 圖案手勢鎖內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: