• vue + canvas 实现九宮格手势解锁器


    前言

    专栏分享:vue2源码专栏vue router源码专栏玩具项目专栏,硬核💪推荐🙌
    欢迎各位 ITer 关注点赞收藏🌸🌸🌸

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

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

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

    介绍

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

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

    shell
    
    
    

    初始化

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

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

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

    javascript
    // 初始化
    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. 快照是什么?
      快照是当前画布的像素点信息。我们永远会在激活一个解锁单元后(即鼠标移动到圆圈内部时),先恢复画布快照,然后去绘制圆圈的选中样式,并将图案路径延伸连接到当前圆圈,然后!会保存此时此刻的画布快照!
      之后,我们会在鼠标移动时,不停的恢复快照,然后绘制最后一个圆圈到当前鼠标坐标的连线轨迹,直到我们激活下一个解锁单元(即鼠标移动到下一个圆圈内部)。我们会又会重复上面的过程,这就构成一个一个的循环

    javascript
    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

    javascript
    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

    javascript
    // 获取画布快照
    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


    __EOF__

  • 本文作者: 柏成
  • 本文链接: https://www.cnblogs.com/burc/p/17673996.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    Spring Boot如何进行单元测试呢?
    sql6(Leetcode1387使用唯一标识码替换员工ID)
    用原生JavaScript实现jQuery的$.getJSON
    .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
    【MySQL高级】MySQL的锁机制
    谷歌发布基于声学建模的无限虚拟房间增强现实鲁棒语音识别技术
    MacOS 配置Clion的详细步骤及mac终端报错问题解决(完整版)
    MongoDB 排序超过内存限制的问题
    受了刺激,决定专升本
    NacosException: endpoint is blank错误
  • 原文地址:https://www.cnblogs.com/burc/p/17673996.html