轻松创建自定义手势图案锁 - Vue 手势密码锁组件

简介: 轻松创建自定义手势图案锁 - Vue 手势密码锁组件

说在前面

🎈Vue 手势密码锁组件是一个功能强大且易于集成的解决方案,旨在为您的应用程序提供安全的登录体验。该组件允许用户通过绘制特定的手势图案来解锁应用程序,取代传统的用户名和密码输入方式。通过引入图案手势锁,用户可以享受到更高的安全性,因为手势图案具有较高的复杂度和难以猜测性。同时,使用手势密码锁还可以提升用户体验,让登录过程更加便捷和流畅。Vue 手势密码锁组件支持高度定制化,您可以轻松地调整样式、主题和验证规则,以适应不同的应用场景。无论您是开发移动应用、网页应用还是桌面应用,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源码

Gitee源码:gitee.com/zheng_yongt…

觉得有帮助的同学可以帮忙给我点个star,感激不尽~~~

有什么想法或者改良可以给我提个pr,十分欢迎~~~

有什么问题都可以在评论告诉我~~~

公众号

关注公众号『前端也能这么有趣』发送 组件库即可获取源码。

组件使用

目前该组件库已经发布到npm,除了手势密码锁之外还有其他许多好玩的组件,后续会继续维护,源码也已经开源,感兴趣的朋友可以瞧瞧,觉得有点意思的可以顺手点个star

组件文档:http://jyeontu.xyz/jvuewheel/#/installView

组件仓库:https://gitee.com/zheng_yongtao/jyeontu-component-warehouse.git

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

目录
相关文章
|
13天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
116 64
|
19天前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
34 1
vue学习第四章
|
19天前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
28 1
vue学习第九章(v-model)
|
19天前
|
JavaScript 前端开发 开发者
vue学习第十章(组件开发)
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文深入讲解Vue组件的基本使用、全局与局部组件、父子组件通信及数据传递等内容,适合前端开发者学习参考。持续更新中,期待您的关注!🎉🎉🎉
33 1
vue学习第十章(组件开发)
|
13天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
23 8
|
13天前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
25天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
25天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
25天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
25天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。