前言

专栏分享:vue2源码专栏vue router源码专栏玩具项目专栏,硬核推荐

欢迎各位 ITer 关注点赞收藏

此篇文章用于记录柏成从零开发一个canvas九宮格手势解锁器的历程,最终效果如下:

  1. 设置图案密码时,需进行两次绘制图案操作,若两次绘制图案一致,则密码设置成功;若不一致,则需重新设置密码

  2. 输入图案密码时,密码一致则验证通过;密码不一致则提示图案密码错误,请重试

介绍

我们基于 canvas 实现了一款简单的九宫格手势解锁器,用户可以通过在九宫格中绘制特定的手势来解锁

我们可以通过 new Locker 创建一个图案解锁器,其接收一个容器作为第一个参数,第二个参数为选项,下面是个基本例子:

<template>
<div class="pattren-locker">
<div id="container" ref="container" style="width: 360px; height: 600px"></div>
</div>
</template> <script setup>
import { ref, onMounted } from 'vue'
import Locker from '@/canvas/locker' const container = ref(null)
onMounted(() => {
// 新建一个解锁器
new Locker(container.value,{
radius: 30, // 圆圈半径
columnSpacing: 50, // 圆圈列间距
rowsSpacing: 90, // 圆圈行间距
stroke: '#b5b5b5', // 圆圈描边颜色
lineStroke: '#237fb4', // 路径描边颜色
selectedFill: '#237fb4', // 图案选中填充颜色
backgroundColor: '#f7f7f7', // 画布背景颜色
})
})
</script>

初始化

Locker 的实现是一个类,在 src/canvas/locker.js中定义。

new Locker(container,{...})时做了什么?我们在构造函数中创建一个 canvas 画布追加到了 container 容器中,并定义了一系列属性,最后执行了 init 初始化方法。

在初始化方法中,我们绘制了9个宫格圆圈,作为解锁单元;并注册监听了鼠标事件,用于绘制解锁轨迹。

// 初始化
init() {
this.drawCellGrids()
this.drawText('请绘制新的图案密码')
this.canvas.addEventListener('contextmenu', (e) => e.preventDefault())
this.canvas.addEventListener('mousedown', this.mousedownEvent.bind(this))
} // 绘制9个宫格圆圈
drawCellGrids() {
const columns = 3
const rows = 3
const width = this.canvas.width
const height = this.canvas.height
const paddingTop = (height - rows * 2 * this.radius - (rows - 1) * this.rowsSpacing) / 2
const paddingLeft = (width - columns * 2 * this.radius - (columns - 1) * this.columnSpacing) / 2
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
const data = {
x: paddingLeft + (2 * j + 1) * this.radius + j * this.columnSpacing,
y: paddingTop + (2 * i + 1) * this.radius + i * this.rowsSpacing,
id: i * columns + j
} this.lockerCells.push(data) this.ctx.beginPath()
this.ctx.arc(data.x, data.y, this.radius, 0, 2 * Math.PI, true)
this.ctx.strokeStyle = this.stroke
this.ctx.lineWidth = 3
this.ctx.stroke()
}
}
this.cellImageData = this.lastImageData = this.getImageData()
}

自定义鼠标事件

我们之前在 init 初始化方法中注册了 onmousedown 鼠标按下事件,需要在此处实现鼠标按下拖拽可以绘制解锁轨迹的逻辑

鼠标按下:先执行 selectCellAt 方法(如果在圆圈内按下鼠标,会立即绘制选中样式,并保存选中样式之后的画布快照)

鼠标移动:先恢复快照,再绘制路径中最后一个点到当前鼠标坐标的轨迹,最后再执行 selectCellAt 方法,一直重复此过程。。。直到鼠标移动到圆圈内部(则先恢复快照,然后绘制点的选中样式,绘制路径中最后一个点到当前点的路径,最后保存绘制路径之后的画布快照)

鼠标抬起:清空onmousemove、onmouseup事件,并校验密码

此时我们小小的脑袋里可能有两个大大的问号??

  1. selectCellAt 方法作用是什么?

    如果鼠标移动到圆圈内部,则会将图案路径连接到当前圆圈,并绘制选中样式

  2. 快照是什么?

    快照是当前画布的像素点信息。我们永远会在激活一个解锁单元后(即鼠标移动到圆圈内部时),先恢复画布快照,然后去绘制圆圈的选中样式,并将图案路径延伸连接到当前圆圈,然后!会保存此时此刻的画布快照!

    之后,我们会在鼠标移动时,不停的恢复快照,然后绘制最后一个圆圈到当前鼠标坐标的连线轨迹,直到我们激活下一个解锁单元(即鼠标移动到下一个圆圈内部)。我们会又会重复上面的过程,这就构成一个一个的循环

mousedownEvent(e) {
const that = this
// 选中宫格,并绘制点到点路径
const selected = this.selectCellAt(e.offsetX, e.offsetY)
if (!selected) return // 鼠标移动事件
this.canvas.onmousemove = function (e) {
// 路径的最后一个点
const lastData = that.currentPath[that.currentPath.length - 1]
// 恢复快照
that.restoreImageData(that.lastImageData) // 绘制路径
that.drawLine(lastData, { x: e.offsetX, y: e.offsetY })
// 选中宫格,并绘制点到点路径
that.selectCellAt(e.offsetX, e.offsetY)
} // 鼠标抬起/移出事件
this.canvas.onmouseup = this.canvas.onmouseout = function () {
const canvas = this
canvas.onmousemove = null
canvas.onmouseup = null
canvas.onmouseout = null const currentPathIds = that.currentPath.map((item) => item.id)
let text = ''
if (that.password.length === 0) {
that.password = currentPathIds
text = '请再次绘制图案进行确认'
} else if (that.confirmPassword.length === 0) {
that.confirmPassword = currentPathIds
if (that.password.join('') === that.confirmPassword.join('')) {
text = '图案密码设置成功,请输入您的密码'
} else {
text = '与上次绘制不一致,请重试'
that.password = []
that.confirmPassword = []
}
} else {
if (that.password.join('') === currentPathIds.join('')) {
text = '图案密码正确 (づ ̄3 ̄)づ╭~'
} else {
text = '图案密码错误,请重试'
}
} that.ctx.clearRect(0, 0, canvas.width, canvas.height) // 清空画布
that.restoreImageData(that.cellImageData) // 恢复背景宫格快照
that.drawText(text) // 绘制提示文字
that.currentPath = [] // 清空当前绘制路径
that.lastImageData = that.cellImageData // 重置上一次绘制的画布快照
}
}

绘制路径及选中样式

我们会在鼠标按下(onmousedown)、鼠标移动(onmousemove)事件中调用 selectCellAt 方法,并传入当前鼠标坐标信息

  1. 若当前坐标在宫格圆圈内 且 改圆圈未被连接过,则先恢复画布快照,然后绘制圆圈选中样式,绘制路径中最后一个圆圈到当前圆圈的路径,最后保存此时此刻的画布快照,返回true

  2. 若当前坐标不在宫格圆圈内 或者 该圆圈被连接过,则返回false

selectCellAt(x, y) {
// 当前坐标点是否在圆内
const data = this.lockerCells.find((item) => {
return Math.pow(item.x - x, 2) + Math.pow(item.y - y, 2) <= Math.pow(this.radius, 2)
})
const existing = this.currentPath.some((item) => item.id === data?.id)
if (!data || existing) return false // 恢复画布快照
this.restoreImageData(this.lastImageData) // 绘制选中样式
this.drawCircle(data.x, data.y, this.radius / 1.5, 'rgba(0,0,0,0.2)')
this.drawCircle(data.x, data.y, this.radius / 2.5, this.selectedFill) // 绘制路径 从最后一个点到当前点
const lastData = this.currentPath[this.currentPath.length - 1]
if (lastData) {
this.drawLine(lastData, data)
} // 保存画布快照
this.lastImageData = this.getImageData() // 保存当前点
this.currentPath.push(data)
return true
} // 绘制选中样式
drawCircle(x, y, radius, fill) {
this.ctx.beginPath()
this.ctx.arc(x, y, radius, 0, 2 * Math.PI, true)
this.ctx.fillStyle = fill
this.ctx.fill()
} // 绘制路径
drawLine(start, end, stroke = this.lineStroke) {
this.ctx.beginPath()
this.ctx.moveTo(start.x, start.y)
this.ctx.lineTo(end.x, end.y)
this.ctx.strokeStyle = stroke
this.ctx.lineWidth = 3
this.ctx.lineCap = 'round'
this.ctx.lineJoin = 'round'
this.ctx.stroke()
}

画布快照

我们如何获取到当前画布快照?又如何根据快照数据恢复画布呢?

查阅 canvas官方API文档 得知,获取快照 API 为 getImageData;通过快照恢复画布的 API 为 putImageData

// 获取画布快照
getImageData() {
return this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)
} // 恢复画布快照
restoreImageData(imageData) {
if (!imageData) return
this.ctx.putImageData(imageData, 0, 0)
}

源码

涂鸦面板demo代码vue-canvas

vue + canvas 实现九宮格手势解锁器的更多相关文章

  1. HTML5 Canvas简简单单实现手机九宫格手势密码解锁

    原文:HTML5 Canvas简简单单实现手机九宫格手势密码解锁 早上花了一个半小时写了一个基于HTML Canvas的手势解锁,主要是为了好玩,可能以后会用到. 思路:根据配置计算出九个点的位置,存 ...

  2. canvas手势解锁源码

    先放图 demo.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=& ...

  3. HTML5实现屏幕手势解锁

    HTML5实现屏幕手势解锁(转载) https://github.com/lvming6816077/H5lockHow to use? <script type="text/java ...

  4. WPF实现手势解锁

    桌面程序的解锁方式一般是账号密码,互联网的可以使用扫码解锁,甚至人脸识别.但扫码需要网络,人脸识别又较复杂.所以就想把安卓常用的手势解锁移植到桌面程序上. 先来张效果图,有兴趣的往下看,没兴趣的打扰了 ...

  5. SJGestureUnlock快速集成手势解锁

    前言:如果页面显示不完整或图片看不了还请移步:简书 SJGestureUnlock.h 常用自定义属性 @interface SJGestureUnlock : UIView @property (n ...

  6. Quartz2D复习(二) --- 手势解锁

    这次支付宝手机客户端升级,把手势解锁那个功能去掉了,引起很多人的抱怨,觉得少了手势解锁的保护,个人信息容易泄漏了... 那么手势解锁功能是怎么是实现的呢,这里使用Quart2D来简单模拟一下, 先看下 ...

  7. iOS--开发之手势解锁

    本文主要介绍通过手势识别实现手势解锁功能,这个方法被广泛用于手机解锁,密码验证,快捷支付等功能实现.事例效果如下所示. 首先,我们先分析功能的实现过程,首先我们需要先看大致的实现过程: 1.加载九宫格 ...

  8. Swift实战-豆瓣电台(九)简单手势控制暂停播放(全文完)

    Swift实战-豆瓣电台(九)简单手势控制暂停播放 全屏清晰观看地址:http://www.tudou.com/programs/view/tANnovvxR8U/ 这节我们主要讲UITapGestu ...

  9. 2016-1-10 手势解锁demo的实现

    一:实现自定义view,在.h,.m文件中代码如下: #import <UIKit/UIKit.h> @class ZLLockView; @protocol ZLLockViewDele ...

  10. iOS绘制手势解锁密码

    手势解锁这个功能其实已经用的越来越少了.但是郁闷不知道我公司为什么每次做一个app都要把手势解锁加上.....于是就自己研究了一下手势解锁页面的实现.. 要想实现这个页面,先说说需要掌握哪些: UIP ...

随机推荐

  1. 使用js闭包封装一个原生的模态框

    现在都是用的是人家封装的框架什么的,但是对于底层的了解也是必须的,不然就无法提升,下面分享一个2 years ago 自己封装的一个提示框 样式很简单(适用于任何分辨率) 具体代码如下 /** * 该 ...

  2. ubuntu18 安装单机k8s v1.18.2

    背景 当我们需要对k8s进行二次开发时,k8s环境是必须的,那么在ubuntu上部署单机k8s是最方便的,便于开发调试 系统准备 本人用的是Ubuntu18,以下以此为例 部署之前,最好切换至root ...

  3. Java面向对象基础学习

    一.面向对象语言编程 ​ Java是一门面向对象的编程语言(OOP),万物皆对象 ​ 面向对象初步认识,在大多数编程语言中根据解决问题的思维方式不同分为两种编程语言 ​ 1.面向过程编程 ​ 2.面向 ...

  4. .netcore中的虚拟文件EmbeddedFile

    以前一直比较好奇像swagger,cap,skywalking等组件是如何实现引用一个dll即可在网页上展示界面的,难道这么多html,js,css等都是硬编码写死在代码文件中的?后面接触apb里面也 ...

  5. wait_timeout and interactive_timeout 参数

    wait_timeout and interactive_timeout 参数 非交互模式连接:通常情况下,应用到RDS实例会采用非交互模式,具体采用哪个模式需要查看应用的连接方式配置,比如PHP通过 ...

  6. 从输入URI到浏览器渲染中间都经历了什么

    这篇文章总共分为两个部分,第一部分我会把从输入url到浏览器渲染的整个流程给大致说一下.第二部分我就会一一介绍各个部分的详细作用. 一.从输入url到浏览器渲染的整个流程   1.DNS域名解析 2. ...

  7. Pinot2的开发者社区和教程

    目录 文章背景: Pinot 2 是任天堂公司于2018年发布的一款游戏机,采用了基于马里奥兄弟游戏<塞尔达传说:荒野之息>的开放世界操作系统,并推出了许多创新的功能,例如"超级 ...

  8. MySQL 存储引擎 InnoDB 内存结构之更改缓冲区

    更改缓冲区(Change Buffer)是一种特殊的数据结构,用于缓存不在缓冲池中的二级索引(secondary index)页的更改.可能来自于INSERT.UPDATE或DELETE操作(数据操作 ...

  9. 数仓性能调优:大宽表关联MERGE性能优化

    摘要:本文主要为大家讲解在数仓性能调优过程中,关于大宽表关联MERGE性能优化过程. 本文分享自华为云社区<GaussDB(DWS)性能调优:大宽表关联MERGE性能优化>,作者:譡里个檔 ...

  10. python接口自动化封装导出excel方法和读写excel数据

    一.首先需要思考,我们在页面导出excel,用python导出如何写入文件的 封装前需要确认python导出excel接口返回的是一个什么样的数据类型 如下:我们先看下不对返回结果做处理,直接接收数据 ...