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

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

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

<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'

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

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()方法裁剪圖片後生成一個正方形。
web

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

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()便可實現效果,咱們前面已經把畫路徑的過程封裝成函數了,稍做改動便可:web安全

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

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

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

而後就是寫拖動事件,咱們能夠在鼠標按下時記錄鼠標位置,而後在拖動時給滑塊設置left值。最後在鬆開鼠標時,判斷滑塊此時的left值和最開始裁剪滑塊時的x值,若是在必定範圍內就算驗證經過,不然驗證失敗。htm

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

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


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

相關文章
相關標籤/搜索