200行代码实现canvas九宫格密码锁

现在很多app,在一些隐私页面,往往都会加入二次验证,例如银行app、支付宝理财和我的页面,一般会有「九宫格密码」和指纹密码。

今天我们用canvas来写一个九宫格手势密码锁,大概就是下面这样。

思路

  1. 准备一个正方形画布
  2. 找到9个小圆圈的圆心坐标(位置自己定,布局合理即可)
  3. 绘制圆圈
  4. 监听手势并连接小圆圈

实现

第一步:先初始化一个空白画布

<canvas id="canvas"></canvas>class GesturePassword {// 正方形,宽高都一样,就用一个size了// padding 画布的边距,百分比constructor(canvas, {size = 300, padding = 0.08} = {}) {this.canvas = canvas;this.ctx = canvas.getContext("2d");this.size = size; // 计算画布实际的padding大小this.padding = size * padding; // 初始化一些属性this.init();}init() {const { ctx, canvas, size } = this;canvas.width = size;canvas.height = size;// 为了开发时看得清楚,先把背景设为深色ctx.fillStyle = "#000";ctx.fillRect(0, 0, size, size);}
}

第二步:画9个小圆

canvas画圆API

ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
  • x:圆心的 x 轴坐标。
  • y:圆心的 y 轴坐标。
  • radius:圆的半径
  • startAngle:圆弧的起始点,x 轴方向开始计算,单位以弧度表示。
  • endAngle:圆弧的终点,单位以弧度表示。
  • anticlockwise(可选):可选的Boolean值,如果为 true,逆时针绘制圆弧,反之,顺时针绘制。

找圆心坐标和半径

定义函数

// 计算圆的坐标
calcCirclePos() {const { size, padding } = this;// 去除画布padding之外的内容宽高const contentSize = size - padding * 2; // 除去圆与圆之间的距离// 规定每个小圆的直径是总宽度的24%const circleWidth = contentSize * 0.24; // 每两个圆圈的圆心之间的距离,横竖都一样const distance = (contentSize - circleWidth) / 2; // 左上角第一个圆的圆心坐标,x和y都一样const firstPoint = Math.ff(circleWidth / 2); // 综上,第一行三个圆的x轴坐标如下const xy = [firstPoint,Math.ff(firstPoint + distance),Math.ff(firstPoint + distance * 2)];// 由于横竖每个圆之间的间隔都是一样的,// 所以很容易想到,通过以上三个值遍历就可以得出9个圆的圆心const points = [];let i = 0;while (i < 3) {for (let index = 0; index < xy.length; index++) {const element = xy[index];points.push({ x: element, y: xy[i] });}i++;}// 最后还要加上padding才是圆心在画布内的真实位置return {points: points.map((item) => {return {x: Math.ff(item.x + padding),y: Math.ff(item.y + padding)};}),circleWidth};
}

Math.ff是为了解决浮点数计算丢失精度问题的

// 浮点数计算,f代表需要计算的表达式,digit代表小数位数
Math.ff = function(f, digit = 2) {// Math.pow(指数,幂指数)const m = Math.pow(10, digit);// Math.round() 四舍五入return Math.round(f * m, 10) / m;
};

在init中调一下

init() {// ...前面的省略了// 计算九个圆圈的圆心的坐标和直径大小const { points, circleWidth } = this.calcCirclePos();// 存起来this.points = points;this.circleWidth = circleWidth;
}

绘制小圆

定义画圆函数

drawCircle() {const { points, circleWidth, ctx } = this;// 循环绘制9个圆points.forEach((item, index) => {// 每一次都要重新开始新路径ctx.beginPath();ctx.arc(item.x, item.y, circleWidth / 2, 0, Math.PI * 2);ctx.closePath();// 将线条颜色设置为蓝色ctx.strokeStyle = "#217bfb"; // stroke() 方法默认颜色是黑色(如果没有上面一行,则会是黑色)ctx.stroke(); });
}

看看效果

第三步:监听手势

这里要判断一下是什么设备,电脑上就监听mouse事件,手机上就监听touch事件,不过这个效果一般是在手机上用的。

这里有两个辅助函数

  • 计算触摸/鼠标移动到的当前坐标
  • 用拿到的当前坐标,和9个小圆坐标以及圆的半径对比,判断是否滑动到了圆圈内
const { canvas } = this;
// 判断设备
const isMobile = /Mobile|Android/i.test(navigator.userAgent);if (isMobile) {// 监听触摸开始事件canvas.addEventListener("touchstart",(e) => {// 这里要判断一下是几指触摸,只允许单指触摸if (e.touches.length !== 1) return;// 获取触摸的坐标位置const { x, y } = this.getTouchPosition(canvas, e.touches[0]);// 判断是否滑动到了圆圈内,是就返回圆的坐标const point = this.trigger(x, y);console.log("[ this.trigger(x, y) ] >", point);if (!point) {// 没有返回坐标,就说明没有滑到任何一个小圆内,就不用管return}// 把被触发的小圆坐标存起来this.hitPoints.push(point);// 绘制触发后的样式和连线this.drawHitCircle();},false);// 监听触摸移动事件canvas.addEventListener("touchmove",(e) => {// 防止页面跟着移动e.preventDefault();if (e.touches.length !== 1) return;const { x, y } = this.getTouchPosition(canvas, e.touches[0]);const point = this.trigger(x, y);console.log("[ this.trigger(x, y) ] >", point);if (!point) {// 没有返回坐标,就说明没有滑到任何一个小圆内,就不用管return}if (this.hitPoints.includes(point)) {// 如果那个位置已被命中过了,就不管return}// 把被触发的小圆坐标存起来this.hitPoints.push(point);// 绘制触发后的样式和连线this.drawHitCircle();},{ passive: false });canvas.addEventListener("touchend", async () => {if (this.hitPoints.length < 4) {setTimeout(() => {// 这里用计时器的作用是防止alert阻塞正常逻辑alert('密码无效,至少需要四个点')}, 0)} else {// 密码有效将密码传给后端或存起来await http()// 然后清空临时存储的点this.hitPoints = [];}// 重新绘制this.drawHitCircle();});
} else {// 非手机端,逻辑一致,不同的是监听方法不同
}

定义获取触摸坐标的函数

getTouchPosition(canvas, event) {// 获取画布相对于浏览器窗口的位置信息// 当画布不在浏览器左上角时必须这么计算const rect = canvas.getBoundingClientRect();const x = event.pageX - rect.left;const y = event.pageY - rect.top;return { x, y };
}

判断是否进入了某个圆圈内

// 接收触摸位置的坐标 x,y
// 判断手指进入了某个圆圈内,返回圈圈坐标
trigger(x, y) {// 先得到被命中的圆圈下标const index = this.points.map((item) => {const distance = Math.sqrt((x - item.x) ** 2 + (y - item.y) ** 2);return distance < this.circleWidth / 2;}).findIndex((item) => item);// 返回该坐标return this.points[index];
}

第四步:绘制命中后的样式

遍历之前存的hitPoints坐标数组,将圆环变为蓝色,并在内部画一个小圆填充

// 绘制命中后的圆圈样式
drawHitCircle() {const { hitPoints, ctx } = this;console.log("[ hitPoints ] >", hitPoints);if (hitPoints.length === 0) {// 手指离开画布后会清空坐标,此时清空画布ctx.clearRect(0, 0, canvas.width, canvas.height);// 但是要重新画圆圈drawCircle();return;}hitPoints.forEach((item, index) => {ctx.beginPath();ctx.arc(item.x, item.y, this.circleWidth / 2, 0, Math.PI * 2);ctx.closePath();// 将线条颜色设置为蓝色ctx.strokeStyle = "#217bfb"; // stroke() 方法默认颜色是黑色(如果没有上面一行,则会是黑色)ctx.stroke(); // 画小圆要重新开始路径ctx.beginPath();// 小圆半径设置为大圆半径的1/3ctx.arc(item.x, item.y, this.circleWidth / 2 / 3, 0, Math.PI * 2);ctx.closePath();// 蓝色小圆ctx.fillStyle = "#217bfb";ctx.fill();// 从第二个圆开始画一条线连接前后两个圆if (index > 0) {ctx.beginPath();ctx.moveTo(this.hitPoints[index - 1].x, this.hitPoints[index - 1].y);ctx.lineTo(item.x, item.y);ctx.strokeStyle = "#217bfb";ctx.stroke();}});
}

看看最终效果

还可以再优化的点

  1. 目前的绘制效果有点模糊

❝ 因为 canvas 不是矢量图,而是像图片一样是位图模式的。高 dpi 显示设备意味着每平方英寸有更多的像素。也就是说二倍屏,浏览器就会以 2 个像素点的宽度来渲染一个像素,该 canvas 在 Retina 屏幕下相当于占据了2倍的空间,相当于图片被放大了一倍,因此绘制出来的图片文字等会变模糊。 ❞

解决canvas模糊的问题

  1. 在还没有滑到任何一个小圆内时,页面上没有任何表现,可以加一个跟手的操作,像这样,但是要解决边移动边渲染的性能问题。

有兴趣的可以去实现一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/73025.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手写Spring:第7章-实现应用上下文

文章目录 一、目标&#xff1a;实现应用上下文二、设计&#xff1a;实现应用上下文三、实现&#xff1a;实现应用上下文3.1 工程结构3.2 Spring应用上下文和Bean对象扩展类图3.3 对象工厂和对象扩展接口3.3.1 对象工厂扩展接口3.3.2 对象扩展接口 3.4 定义应用上下文3.4.1 定义…

MySQL卸载干净再重新安装【Windows】

家人们&#xff0c;谁懂啊&#xff1f; 上学期学的数据库&#xff0c;由于上学期不知道为什么抽风&#xff0c;过得十分的迷&#xff0c;上课跟老师步骤安装好了Mysql&#xff0c;但后面在使用的过程中出现了问题&#xff0c;而且还出现了忘记密码这么蠢的操作&#xff0c;后半…

Splunk Enterprise for Mac:卓越的数据分析与管理工具

在当今的数字化时代&#xff0c;数据已经成为企业成功的核心驱动力。然而&#xff0c;如何有效地管理和分析这些数据&#xff0c;却常常让企业感到困惑。Splunk Enterprise for Mac 是一款领先的数据分析和管理工具&#xff0c;可以帮助你解决这一难题。 Splunk Enterprise fo…

在linux上挂载windows共享目录

挂载要求 非root用户&#xff08;普通用户&#xff09;能够读写windows共享目录&#xff0c;比如查看文件、创建文件、修改文件、删除文件 # 让普通用户也可以正常读写 uidvalue and gidvalue Set the owner and group of the root of the file system (default: uidgid0, bu…

阿里云ubuntu服务器搭建ftp服务器

阿里云ubuntu服务器搭建ftp服务器 服务器环境安装步骤一.创建用户二.安装 vsftp三 配置vsftp四.配置阿里云安全组 服务器环境 阿里云上的云服务器&#xff0c;操作系统为 ubuntu20.04。 安装步骤 一.创建用户 为什么需要创建用户&#xff1f; 这里的用户&#xff0c;指的是…

NFT 合约部署教程

本篇文章主要介绍如何将您的 NFT(ERC-721 Token) 通过智能合约部署到去中心化网络中 Init Project //创建一款ocean的NFT mkdir nft-ocean//进入目录 cd nft-ocean//初始化项目&#xff0c;根据提示填写即可&#xff0c;packname和description填写即可 npm init//添加hardhat…

QT Creator更改主题和编辑器风格(附几款黑色主题)

适用于qtcreator 一、使用自带主题与编辑器风格 打开Qt选择"工具"->"选项"&#xff1b; 2. 选择"环境"->"Theme"切换不同的主题风格 这里切换的是外边框的风格&#xff0c;如果编辑器中有同名的风格&#xff0c;编辑器的风格也…

数据可视化:四大发明的现代转化引擎

在科技和工业的蓬勃发展中&#xff0c;中国的四大发明——造纸术、印刷术、火药和指南针&#xff0c;早已不再是古代创新的象征&#xff0c;而是催生了众多衍生行业的崭新可能性。其中&#xff0c;数据可视化技术正成为这些行业的一颗璀璨明珠&#xff0c;开启了全新的时代。 1…

(数字图像处理MATLAB+Python)第十二章图像编码-第三、四节:有损编码和JPEG

文章目录 一&#xff1a;有损编码&#xff08;1&#xff09;预测编码A&#xff1a;概述B&#xff1a;DM编码C&#xff1a;最优预测器 &#xff08;2&#xff09;变换编码A&#xff1a;概述B&#xff1a;实现变换编码的主要问题 二&#xff1a;JPEG 一&#xff1a;有损编码 &am…

基于ArcGIS、ENVI、InVEST、FRAGSTATS等多技术融合提升环境、生态、水文、土地、土壤、农业、大气等领域的数据分析能力与项目科研水平教程

详情点击链接&#xff1a;基于ArcGIS、ENVI、InVEST、FRAGSTATS等多技术融合提升环境、生态、水文、土地、土壤、农业、大气等领域的数据分析能力与项目科研水平教程 一&#xff0c;空间数据获取与制图 1.1 软件安装与应用 1.2 空间数据 1.3海量空间数据下载 1.4 ArcGIS软件…

MATLAB中M文件编写

简介 所谓M文件就是将处理问题的各种命令融合到一个文件中&#xff0c;该文件以.m为扩展名。然后&#xff0c;由MATLAB系统编译M文件&#xff0c;得出相应的运行结果。M文件具有相当大的可开发性和扩展性。M文件有脚本文件和函数文件两种。脚本文件不需要输入参数&#xff0c;…

C++QT day3

1> 自行封装一个栈的类&#xff0c;包含私有成员属性&#xff1a;栈的数组、记录栈顶的变量 成员函数完成&#xff1a;构造函数、析构函数、拷贝构造函数、入栈、出栈、清空栈、判空、判满、获取栈顶元素、求栈的大小 2> 自行封装一个循环顺序队列的类&#xff0c;包含…

计算机视觉领域经典模型汇总(2023.09.08

一、RCNN系列 1、RCNN RCNN是用于目标检测的经典方法&#xff0c;其核心思想是将目标检测任务分解为两个主要步骤&#xff1a;候选区域生成和目标分类。 候选区域生成&#xff1a;RCNN的第一步是生成可能包含目标的候选区域&#xff0c;RCNN使用传统的计算机视觉技术&#x…

【0908练习】shell脚本使用expr截取网址

题目&#xff1a; 终端输入网址&#xff0c;如&#xff1a;www.hqyj.com&#xff0c; 要求&#xff1a;截取网址每个部分&#xff0c;并放入数组中&#xff0c;不能使用cut&#xff0c;使用expr解决 #!/bin/bash read -p "请输入一个网址" net lenexpr length $net …

Unity中Shader的屏幕抓取 GrabPass

文章目录 前言一、抓取1、抓取指令2、在使用抓取的屏幕前&#xff0c;需要像使用属性一样定义一下,_GrabTexture这个名字是Unity定义好的 前言 Unity中Shader的屏幕抓取 GrabPass 一、抓取 1、抓取指令 屏幕的抓取需要使用一个Pass GrabPass{} GrabPass{“NAME”} 2、在使用…

TGA格式文件转材质

今天淘宝上买了一个美女的模型&#xff0c;是blender的源文件&#xff0c;上面说有fbx格式的。我用unity&#xff0c;所以觉得应该可以用。文件内容如下图&#xff1a; FBX文件夹打开后&#xff0c;内容如下图所示&#xff0c;当时就预感到可能没有色彩。 unity打开后果然发现只…

CSS的break-inside 属性 的使用

break-inside 属性在 CSS 页码分隔模块中使用,它定义了一个元素内部是否允许发生页面、栏目或者区域的分隔。 break-inside有以下几个值 break-inside: avoid- 表示避免在该元素内部发生分页或者分栏。break-inside: auto - 默认允许分页break-inside: avoid-page - 避免页面…

Java虚拟机反射机制

1 什么是Java虚拟机反射机制&#xff1f; 虚拟机在运行期间&#xff0c;对于任何一个类&#xff0c;我们都能知道其内部信息&#xff0c;包括属性&#xff0c;方法&#xff0c;构造函数&#xff0c;实现接口&#xff1b;对于任何一个对象&#xff0c;我们都能获取其字段值、调…

Git 客户端基本使用及新手常见问题

Git作为一个版本管理工具&#xff0c;在企业中的应用越来越普遍。作为一个测试工程师&#xff0c;不可避免会需要接触到Git的相关操作&#xff0c;以下整理Git客户端的常见操作&#xff0c;以及应用中新手常碰到的一些问题。 1、环境安装及配置 Git下载地址&#xff1a;https…

手敲Cocos简易地图编辑器:人生地图是一本不断修改的书,每一次编辑都是为了克服新的阻挡

引言 本系列是《8年主程手把手打造Cocos独立游戏开发框架》&#xff0c;欢迎大家关注分享收藏订阅。 在上一篇文章&#xff0c;笔者给大家讲解了在Cocos独立游戏开发框架中&#xff0c;如何自定义实现Tile地图管理器&#xff0c;成功地在游戏中优化加载一张特大的地图。接下来…