原生js+canvas實現滑動拼圖驗證碼


上圖為網易雲盾的滑動拼圖驗證碼,同一張圖片裁剪的位置是固定的,猜想其應該有一個專門的圖片庫,保存了被裁剪的背景圖和可移動的滑塊。我的想法是,隨機生成圖片,隨機生成位置,再用canvas裁剪出滑塊和背景圖。下面介紹具體步驟。

首先隨便找一張圖片渲染到canvas上,這里#canvas作為畫布,#block作為裁剪出來的小滑塊。

<canvas width="310" height="155" id="canvas"></canvas>
<canvas width="310" height="155" id="block"></canvas>
  var canvas = document.getElementById('canvas')
  var block = document.getElementById('block')
  var canvas_ctx = canvas.getContext('2d')
  var block_ctx = block.getContext('2d')
  var img = document.createElement('img')
  img.onload = function() {
    canvas_ctx.drawImage(img, 0, 0, 310, 155)
    block_ctx.drawImage(img, 0, 0, 310, 155)
  };
  img.src = 'img.jpg'

下面考慮如何裁剪出拼圖的形狀,拼圖形狀比較復雜,首先我們畫一個正方形,接着上邊的代碼寫:

  var x = 150, y = 40, w = 42, r = 10, PI = Math.PI 
  function draw(ctx) {
    ctx.beginPath()
    ctx.moveTo(x, y)
    ctx.lineTo(x + w, y)
    ctx.lineTo(x + w, y + w)
    ctx.lineTo(x, y + w)
    ctx.clip()
  }
  draw(canvas_ctx)
  draw(block_ctx)

x,y為正方形左上角的坐標,現在先隨便寫后邊生成的時候用隨機數,w為正方形的邊長,r為后邊畫缺口的圓的半徑。我們先把繪圖過程用函數封裝起來,方便后面同時操作背景和滑塊。用clip()方法裁剪圖片后生成一個正方形。

接下來畫正方形上邊和右邊的圓形:

  function draw(ctx) {
    ctx.beginPath()
    ctx.moveTo(x,y)
+   ctx.lineTo(x+w/2,y)
+   ctx.arc(x+w/2,y-r+2, r,0,2*PI) //
+   ctx.lineTo(x+w/2,y)
    ctx.lineTo(x+w,y)
+   ctx.lineTo(x+w,y+w/2)
+   ctx.arc(x+w+r-2,y+w/2,r,0,2*PI) //
+   ctx.lineTo(x+w,y+w/2)
    ctx.lineTo(x+w,y+w)
    ctx.lineTo(x,y+w)
    ctx.lineTo(x,y)
    ctx.clip()
  }

兩處注釋的位置將圓心往內偏移了2px,實現缺口的樣式。然后是左邊空心的部分,由於clip是裁剪路徑內的部分,因此直接像上面畫圓是不行的,我們開啟一條新的路徑,然后畫圓將這個正方形“遮蓋”出一個缺口,這里會用到globalCompositeOperation 屬性,'xor'顧名思義。代碼接上邊:

  function draw(ctx) {
    ctx.beginPath()
    ...
    ctx.lineTo(x,y)
    ctx.clip()
+   ctx.beginPath()
+   ctx.arc(x,y+w/2, r,1.5*PI,0.5*PI) // 只需要畫正方形內的半圓就行,方便背景圖片的裁剪
+   ctx.globalCompositeOperation = "xor"
+   ctx.fill()
}



現在一個基本的拼圖形狀有了,我們調整#block的大小,並將裁剪出來的滑塊放入#block中:

  img.onload = function() {
    ctx.drawImage(img, 0, 0, 310, 155)
    block_ctx.drawImage(img, 0, 0, 310, 155)

+   var blockWidth = w + r * 2
+   var _y = y - r * 2 + 2 // 滑塊實際的y坐標
+   var ImageData = block_ctx.getImageData(x, _y, blockWidth, blockWidth)
+   block.width = blockWidth
+   block_ctx.putImageData(ImageData, 0, _y)
  }

現在我們需要把左邊畫布展示原來的圖片,並且摳掉中間滑塊的部分,這里畫路徑的過程都是一樣的,唯一不同只是clip()那里改成fill()即可實現效果,我們前面已經把畫路徑的過程封裝成函數了,稍作改動即可:

-  function draw(ctx) {
+  function draw(ctx, operation) {
    ...
-  ctx.clip()

+  ctx.fillStyle = '#fff'
+  ctx[operation]()
   ...
}
+  draw(canvas_ctx, 'fill')
+  draw(block_ctx, 'clip')

接下來就是寫樣式了,略過:

然后就是寫拖動事件,我們可以在鼠標按下時記錄鼠標位置,然后在拖動時給滑塊設置left值。最后在松開鼠標時,判斷滑塊此時的left值和最開始裁剪滑塊時的x值,如果在一定范圍內就算驗證通過,否則驗證失敗。

最后再加上隨機圖片和隨機剪切位置,基本就ok了。另外可以判斷下鼠標移動時y軸的變化,以判斷是否是“人”在操作,當然web安全這塊神魔亂舞的,我就不班門弄斧了,只是做個簡單判斷就行。

后邊代碼比較雜亂,就不貼上來了,查看完整代碼點這里 演示地址點這里


代碼更新了,現在直接用ctx.arc畫出滑塊的邊界,而不是像之前畫正方形+三個圓去拼接,因此也能加上stroke提高辨識度了,詳細見github


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM