信息发布→ 登录 注册 退出

vue封装一个图案手势锁组件

发布时间:2026-01-11

点击量:
目录
  • 说在前面
  • 效果展示
  • 预览地址
  • 实现步骤
    • 1.组件设计
    • 2.组件分析
    • 3.组件实现
    • 4.组件使用
  • 组件库引用
    • 源码地址
      • 组件文档

        说在前面

        现在很多人都喜欢使用图案手势锁,这里我使用vue来封装了一个可以直接使用的组件,在这里记录一下这个组件的开发步骤。

        效果展示

        组件实现效果如下图:

        预览地址

        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中计算三角函数

        三角函数的定义

        正弦(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.组件使用

        <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

        在线客服
        服务热线

        服务热线

        4008888355

        微信咨询
        二维码
        返回顶部
        ×二维码

        截屏,微信识别二维码

        打开微信

        微信号已复制,请打开微信添加咨询详情!