最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue+canvas實現(xiàn)簡易的九宮格手勢解鎖器

 更新時間:2023年09月08日 10:01:49   作者:柏成  
這篇文章主要為大家詳細介紹了如何流vue+canvas實現(xiàn)一個簡易的九宮格手勢解鎖器,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

前言

此篇文章用于記錄柏成從零開發(fā)一個canvas九宮格手勢解鎖器的歷程,最終效果如下:

設(shè)置圖案密碼時,需進行兩次繪制圖案操作,若兩次繪制圖案一致,則密碼設(shè)置成功;若不一致,則需重新設(shè)置密碼

輸入圖案密碼時,密碼一致則驗證通過;密碼不一致則提示圖案密碼錯誤,請重試

介紹

我們基于 canvas 實現(xiàn)了一款簡單的九宮格手勢解鎖器,用戶可以通過在九宮格中繪制特定的手勢來解鎖

我們可以通過 new Locker 創(chuàng)建一個圖案解鎖器,其接收一個容器作為第一個參數(shù),第二個參數(shù)為選項,下面是個基本例子:

<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 的實現(xiàn)是一個類,在 src/canvas/locker.js中定義。

new Locker(container,{...})時做了什么?我們在構(gòu)造函數(shù)中創(chuàng)建一個 canvas 畫布追加到了 container 容器中,并定義了一系列屬性,最后執(zhí)行了 init 初始化方法。

在初始化方法中,我們繪制了9個宮格圓圈,作為解鎖單元;并注冊監(jiān)聽了鼠標(biāo)事件,用于繪制解鎖軌跡。

// 初始化
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()
}

自定義鼠標(biāo)事件

我們之前在 init 初始化方法中注冊了 onmousedown 鼠標(biāo)按下事件,需要在此處實現(xiàn)鼠標(biāo)按下拖拽可以繪制解鎖軌跡的邏輯

鼠標(biāo)按下:先執(zhí)行 selectCellAt 方法(如果在圓圈內(nèi)按下鼠標(biāo),會立即繪制選中樣式,并保存選中樣式之后的畫布快照)

鼠標(biāo)移動:先恢復(fù)快照,再繪制路徑中最后一個點到當(dāng)前鼠標(biāo)坐標(biāo)的軌跡,最后再執(zhí)行 selectCellAt 方法,一直重復(fù)此過程。。。直到鼠標(biāo)移動到圓圈內(nèi)部(則先恢復(fù)快照,然后繪制點的選中樣式,繪制路徑中最后一個點到當(dāng)前點的路徑,最后保存繪制路徑之后的畫布快照)

鼠標(biāo)抬起:清空onmousemove、onmouseup事件,并校驗密碼

此時我們小小的腦袋里可能有兩個大大的問號??

selectCellAt 方法作用是什么?

如果鼠標(biāo)移動到圓圈內(nèi)部,則會將圖案路徑連接到當(dāng)前圓圈,并繪制選中樣式

快照是什么?

快照是當(dāng)前畫布的像素點信息。我們永遠會在激活一個解鎖單元后(即鼠標(biāo)移動到圓圈內(nèi)部時),先恢復(fù)畫布快照,然后去繪制圓圈的選中樣式,并將圖案路徑延伸連接到當(dāng)前圓圈,然后!會保存此時此刻的畫布快照!之后,我們會在鼠標(biāo)移動時,不停的恢復(fù)快照,然后繪制最后一個圓圈到當(dāng)前鼠標(biāo)坐標(biāo)的連線軌跡,直到我們激活下一個解鎖單元(即鼠標(biāo)移動到下一個圓圈內(nèi)部)。我們會又會重復(fù)上面的過程,這就構(gòu)成一個一個的循環(huán)

mousedownEvent(e) {
  const that = this
  // 選中宮格,并繪制點到點路徑
  const selected = this.selectCellAt(e.offsetX, e.offsetY)
  if (!selected) return
  // 鼠標(biāo)移動事件
  this.canvas.onmousemove = function (e) {
    // 路徑的最后一個點
    const lastData = that.currentPath[that.currentPath.length - 1]
    // 恢復(fù)快照
    that.restoreImageData(that.lastImageData)
    // 繪制路徑
    that.drawLine(lastData, { x: e.offsetX, y: e.offsetY })
    // 選中宮格,并繪制點到點路徑
    that.selectCellAt(e.offsetX, e.offsetY)
  }
  // 鼠標(biāo)抬起/移出事件
  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 = '圖案密碼設(shè)置成功,請輸入您的密碼'
      } 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) // 恢復(fù)背景宮格快照
    that.drawText(text) // 繪制提示文字
    that.currentPath = [] // 清空當(dāng)前繪制路徑
    that.lastImageData = that.cellImageData // 重置上一次繪制的畫布快照
  }
}

繪制路徑及選中樣式

我們會在鼠標(biāo)按下(onmousedown)、鼠標(biāo)移動(onmousemove)事件中調(diào)用 selectCellAt 方法,并傳入當(dāng)前鼠標(biāo)坐標(biāo)信息

若當(dāng)前坐標(biāo)在宮格圓圈內(nèi) 且 改圓圈未被連接過,則先恢復(fù)畫布快照,然后繪制圓圈選中樣式,繪制路徑中最后一個圓圈到當(dāng)前圓圈的路徑,最后保存此時此刻的畫布快照,返回true

若當(dāng)前坐標(biāo)不在宮格圓圈內(nèi) 或者 該圓圈被連接過,則返回false

selectCellAt(x, y) {
  // 當(dāng)前坐標(biāo)點是否在圓內(nèi)
  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
  // 恢復(fù)畫布快照
  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)
  // 繪制路徑 從最后一個點到當(dāng)前點
  const lastData = this.currentPath[this.currentPath.length - 1]
  if (lastData) {
    this.drawLine(lastData, data)
  }
  // 保存畫布快照
  this.lastImageData = this.getImageData()
  // 保存當(dāng)前點
  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()
}

畫布快照

我們?nèi)绾潍@取到當(dāng)前畫布快照?又如何根據(jù)快照數(shù)據(jù)恢復(fù)畫布呢?

查閱 canvas官方API文檔 得知,獲取快照 API 為 getImageData;通過快照恢復(fù)畫布的 API 為 putImageData

// 獲取畫布快照
getImageData() {
  return this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)
}
// 恢復(fù)畫布快照
restoreImageData(imageData) {
  if (!imageData) return
  this.ctx.putImageData(imageData, 0, 0)
}

源碼

涂鴉面板demo代碼vue-canvas

到此這篇關(guān)于vue+canvas實現(xiàn)簡易的九宮格手勢解鎖器的文章就介紹到這了,更多相關(guān)vue canvas九宮格手勢解鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • element-ui 彈窗組件封裝的步驟

    element-ui 彈窗組件封裝的步驟

    這篇文章主要介紹了element-ui 彈窗組件封裝的步驟,幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下
    2021-01-01
  • Vue3.2?新增指令?v-memo?用法詳解(提高性能利器)

    Vue3.2?新增指令?v-memo?用法詳解(提高性能利器)

    v-memo 接受一個依賴的數(shù)組,依賴的數(shù)組變化,v-memo 所對應(yīng)的 DOM 包括子集將會重新渲染,這篇文章主要介紹了Vue3.2?新增指令?v-memo?用法,提高性能的又一利器,需要的朋友可以參考下
    2022-09-09
  • vue.js實現(xiàn)圖書管理功能

    vue.js實現(xiàn)圖書管理功能

    這篇文章主要為大家詳細介紹了vue.js實現(xiàn)圖書管理功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • 解決vue項目中出現(xiàn)Invalid Host header的問題

    解決vue項目中出現(xiàn)Invalid Host header的問題

    這篇文章主要介紹了解決vue項目中出現(xiàn)"Invalid Host header"的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • vue實現(xiàn)Excel文件的上傳與下載功能的兩種方式

    vue實現(xiàn)Excel文件的上傳與下載功能的兩種方式

    這篇文章主要介紹了vue實現(xiàn)Excel文件的上傳與下載功能,本文通過兩種方式給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06
  • Vue源碼分析之Vue實例初始化詳解

    Vue源碼分析之Vue實例初始化詳解

    這篇文章主要給大家介紹了關(guān)于Vue源碼分析之Vue實例初始化的相關(guān)資料,需要的朋友可以參考下
    2019-08-08
  • vue-virtual-scroll-list虛擬組件實現(xiàn)思路詳解

    vue-virtual-scroll-list虛擬組件實現(xiàn)思路詳解

    這篇文章主要給大家介紹了關(guān)于vue-virtual-scroll-list虛擬組件實現(xiàn)思路的相關(guān)資料,文中通過實例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2023-02-02
  • vue.js常用的自定義校驗方式

    vue.js常用的自定義校驗方式

    文章介紹了使用ElementUI時如何自定義校驗規(guī)則,包括將校驗邏輯封裝為JavaScript文件,通過`validator`和`trigger`屬性定義校驗方法和觸發(fā)條件等
    2026-04-04
  • Vue清除定時器setInterval優(yōu)化方案分享

    Vue清除定時器setInterval優(yōu)化方案分享

    這篇文章主要介紹了Vue清除定時器setInterval優(yōu)化方案分享,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-07-07
  • vue實現(xiàn)文件流形式的導(dǎo)出下載全過程

    vue實現(xiàn)文件流形式的導(dǎo)出下載全過程

    Vue項目中下載返回的文件流操作包括:使用Axios請求文件流數(shù)據(jù)、設(shè)置響應(yīng)類型為'blob'、創(chuàng)建下載鏈接并觸發(fā)下載,關(guān)鍵步驟是使用Blob對象和URL.createObjectURL生成下載鏈接
    2025-12-12

最新評論

武陟县| 洛宁县| 乡宁县| 新龙县| 阳西县| 齐齐哈尔市| 怀仁县| 张家港市| 石城县| 辽阳市| 安平县| 尚志市| 新龙县| 大英县| 新兴县| 泸西县| 湛江市| 得荣县| 承德县| 雷波县| 封丘县| 南部县| 大关县| 津南区| 兰溪市| 崇明县| 灌南县| 朝阳县| 蒙山县| 澄迈县| 松溪县| 抚松县| 舞钢市| 嘉峪关市| 昭苏县| 张家界市| 吴桥县| 三台县| 灯塔市| 金昌市| 三门县|