初始化: 滑到時: css
驗證成功: 驗證失敗:html
相信你們看了此組件圖片後已經明白它的使用場景,在此不作過多介紹jquery
組件CSS(sliderImgPuzzle.css):git
1 .block { 2 position: absolute; 3 left: 0; 4 top: 0 5 } 6 7 .canvasContainer { 8 /*display: none;*/ 9 position: absolute; 10 bottom: 65px; 11 background: #fff 12 } 13 14 .sliderImgPuzzleContainer { 15 position: relative; 16 text-align: center; 17 width: 325px; 18 height: 50px; 19 font-size: 14px; 20 line-height: 50px; 21 background: #f7f9fa; 22 color: #45494c; 23 border: 1px solid #e4e7eb 24 } 25 26 .sliderImgPuzzleContainer_active .sliderImgPuzzle { 27 height: 48px; 28 top: -1px; 29 border: 1px solid #1991fa 30 } 31 32 .sliderImgPuzzleContainer_active .sliderImgPuzzleMask { 33 height: 48px; 34 border-width: 1px 35 } 36 37 .sliderImgPuzzleContainer_success .sliderImgPuzzle { 38 height: 48px; 39 top: -1px; 40 border: 1px solid #52ccba; 41 background-color: #52ccba !important 42 } 43 44 .sliderImgPuzzleContainer_success .sliderImgPuzzleMask { 45 height: 48px; 46 border: 1px solid #52ccba; 47 background-color: #d2f4ef 48 } 49 50 .sliderImgPuzzleContainer_success .sliderImgPuzzleIcon { 51 background-position: 0 0 !important 52 } 53 54 .sliderImgPuzzleContainer_fail .sliderImgPuzzle { 55 height: 48px; 56 top: -1px; 57 border: 1px solid #f57a7a; 58 background-color: #f57a7a !important 59 } 60 61 .sliderImgPuzzleContainer_fail .sliderImgPuzzleMask { 62 height: 48px; 63 border: 1px solid #f57a7a; 64 background-color: #fce1e1 65 } 66 67 .sliderImgPuzzleContainer_fail .sliderImgPuzzleIcon { 68 top: 19px; 69 background-position: 0 -82px !important 70 } 71 72 .sliderImgPuzzleContainer_active .sliderImgPuzzleText, .sliderImgPuzzleContainer_success .sliderImgPuzzleText, .sliderImgPuzzleContainer_fail .sliderImgPuzzleText { 73 visibility: hidden 74 } 75 76 .sliderImgPuzzleMask { 77 position: absolute; 78 left: 0; 79 top: 0; 80 height: 50px; 81 border: 0 solid #1991fa; 82 background: #d1e9fe 83 } 84 85 .sliderImgPuzzle { 86 position: absolute; 87 top: 0; 88 left: 0; 89 width: 50px; 90 height: 48px; 91 background: #fff; 92 box-shadow: 0 0 3px rgba(0, 0, 0, .3); 93 cursor: pointer; 94 transition: background .2s linear 95 } 96 97 .sliderImgPuzzle:hover { 98 background: #1991fa 99 } 100 101 .sliderImgPuzzle:hover .sliderImgPuzzleIcon { 102 background-position: 0 -13px 103 } 104 105 .sliderImgPuzzleIcon { 106 position: absolute; 107 top: 19px; 108 left: 17px; 109 width: 14px; 110 height: 12px; 111 background: url(../images/icon.png) 0 -26px; 112 background-size: 34px 471px 113 } 114 115 .refreshIcon { 116 position: absolute; 117 right: 0; 118 top: 0; 119 width: 34px; 120 height: 34px; 121 cursor: pointer; 122 background: url(../images/icon.png) 0 -437px; 123 background-size: 34px 471px 124 }
組件js(sliderImgPuzzle.js) github
1 ;(function ($) { 2 $.fn.SliderImgPuzzle = function (setting) { 3 var defaults = {callback: false} 4 var setting = $.extend(defaults, setting); 5 const l = 42, r = 10, w = 325, h = 155, PI = Math.PI 6 const L = l + r * 2 7 8 function getRandomNumberByRange(start, end) { 9 return Math.round(Math.random() * (end - start) + start) 10 } 11 12 function createCanvas(width, height) { 13 const canvas = createElement('canvas') 14 canvas.width = width 15 canvas.height = height 16 return canvas 17 } 18 19 function createImg(onload) { 20 const img = createElement('img') 21 img.crossOrigin = "Anonymous" 22 img.onload = onload 23 img.onerror = () => { 24 img.src = getRandomImg() 25 } 26 img.src = getRandomImg() 27 return img 28 } 29 30 function createElement(tagName) { 31 return document.createElement(tagName) 32 } 33 34 function addClass(tag, className) { 35 tag.classList.add(className) 36 } 37 38 function removeClass(tag, className) { 39 tag.classList.remove(className) 40 } 41 42 function getRandomImg() { 43 return 'https://picsum.photos/300/150/?image=' + getRandomNumberByRange(0, 200) 44 } 45 46 function draw(ctx, operation, x, y, type) { 47 if (type == 1) 48 draw1(ctx, operation, x, y); else if (type == 2) 49 draw2(ctx, operation, x, y); else if (type == 3) 50 draw3(ctx, operation, x, y); else if (type == 4) 51 draw4(ctx, operation, x, y); else if (type == 5) 52 draw5(ctx, operation, x, y); else if (type == 6) 53 draw6(ctx, operation, x, y); 54 } 55 56 function draw6(ctx, operation, x, y) { 57 ctx.beginPath() 58 ctx.moveTo(x, y) 59 ctx.lineTo(x + l, y) 60 ctx.lineTo(x + l, y + l / 2) 61 ctx.arc(x + l + r - 4, y + l / 2, r, 0, 2 * PI) 62 ctx.lineTo(x + l, y + l / 2) 63 ctx.lineTo(x + l, y + l) 64 ctx.lineTo(x + l / 2, y + l) 65 ctx.arc(x + l / 2, y + l + 4, r, 0, 2 * PI) 66 ctx.lineTo(x + l / 2, y + l) 67 ctx.lineTo(x, y + l) 68 ctx.lineTo(x, y) 69 ctx.fillStyle = '#fff' 70 ctx[operation]() 71 ctx.beginPath() 72 ctx.arc(x + l / 2, y, r, 2 * PI, 1 * PI) 73 ctx.globalCompositeOperation = "xor" 74 ctx.fill() 75 } 76 77 function draw5(ctx, operation, x, y) { 78 ctx.beginPath() 79 ctx.moveTo(x, y) 80 ctx.lineTo(x + l / 2, y) 81 ctx.arc(x + l / 2, y - r + 4, r, 0, 2 * PI) 82 ctx.lineTo(x + l / 2, y) 83 ctx.lineTo(x + l, y) 84 ctx.lineTo(x + l, y + l / 2) 85 ctx.arc(x + l + r - 4, y + l / 2, r, 0, 2 * PI) 86 ctx.lineTo(x + l, y + l / 2) 87 ctx.lineTo(x + l, y + l) 88 ctx.lineTo(x, y + l) 89 ctx.lineTo(x, y) 90 ctx.fillStyle = '#fff' 91 ctx[operation]() 92 ctx.beginPath() 93 ctx.arc(x + l / 2, y + l, r, 1 * PI, 2 * PI) 94 ctx.globalCompositeOperation = "xor" 95 ctx.fill() 96 } 97 98 function draw4(ctx, operation, x, y) { 99 ctx.beginPath() 100 ctx.moveTo(x, y) 101 ctx.lineTo(x + l / 2, y) 102 ctx.arc(x + l / 2, y - r + 4, r, 0, 2 * PI) 103 ctx.lineTo(x + l / 2, y) 104 ctx.lineTo(x + l, y) 105 ctx.lineTo(x + l, y + l / 2) 106 ctx.arc(x + l + r - 4, y + l / 2, r, 0, 2 * PI) 107 ctx.lineTo(x + l, y + l / 2) 108 ctx.lineTo(x + l, y + l) 109 ctx.lineTo(x + l / 2, y + l) 110 ctx.arc(x + l / 2, y + l + 4, r, 0, 2 * PI) 111 ctx.lineTo(x + l / 2, y + l) 112 ctx.lineTo(x, y + l) 113 ctx.lineTo(x, y) 114 ctx.fillStyle = '#fff' 115 ctx[operation]() 116 ctx.beginPath() 117 ctx.arc(x, y + l / 2, r, 1.5 * PI, 0.5 * PI) 118 ctx.globalCompositeOperation = "xor" 119 ctx.fill() 120 } 121 122 function draw3(ctx, operation, x, y) { 123 ctx.beginPath() 124 ctx.moveTo(x, y) 125 ctx.lineTo(x + l / 2, y) 126 ctx.lineTo(x + l, y) 127 ctx.lineTo(x + l, y + l / 2) 128 ctx.arc(x + l + r - 4, y + l / 2, r, 0, 2 * PI) 129 ctx.lineTo(x + l, y + l / 2) 130 ctx.lineTo(x + l, y + l) 131 ctx.lineTo(x + l / 2, y + l) 132 ctx.arc(x + l / 2, y + l + 4, r, 0, 2 * PI) 133 ctx.lineTo(x + l / 2, y + l) 134 ctx.lineTo(x, y + l) 135 ctx.lineTo(x, y) 136 ctx.fillStyle = '#fff' 137 ctx[operation]() 138 ctx.beginPath() 139 ctx.arc(x, y + l / 2, r, 1.5 * PI, 0.5 * PI) 140 ctx.globalCompositeOperation = "xor" 141 ctx.fill() 142 } 143 144 function draw2(ctx, operation, x, y) { 145 ctx.beginPath() 146 ctx.moveTo(x, y) 147 ctx.lineTo(x + l / 2, y) 148 ctx.lineTo(x + l, y) 149 ctx.lineTo(x + l, y + l / 2) 150 ctx.arc(x + l + r - 4, y + l / 2, r, 0, 2 * PI) 151 ctx.lineTo(x + l, y + l / 2) 152 ctx.lineTo(x + l, y + l) 153 ctx.lineTo(x, y + l) 154 ctx.lineTo(x, y) 155 ctx.fillStyle = '#fff' 156 ctx[operation]() 157 ctx.beginPath() 158 ctx.arc(x, y + l / 2, r, 1.5 * PI, 0.5 * PI) 159 ctx.globalCompositeOperation = "xor" 160 ctx.fill() 161 } 162 163 function draw1(ctx, operation, x, y) { 164 ctx.beginPath() 165 ctx.moveTo(x, y) 166 ctx.lineTo(x + l / 2, y) 167 ctx.arc(x + l / 2, y - r + 4, r, 0, 2 * PI) 168 ctx.lineTo(x + l / 2, y) 169 ctx.lineTo(x + l, y) 170 ctx.lineTo(x + l, y + l / 2) 171 ctx.arc(x + l + r - 4, y + l / 2, r, 0, 2 * PI) 172 ctx.lineTo(x + l, y + l / 2) 173 ctx.lineTo(x + l, y + l) 174 ctx.lineTo(x, y + l) 175 ctx.lineTo(x, y) 176 ctx.fillStyle = '#fff' 177 ctx[operation]() 178 ctx.beginPath() 179 ctx.arc(x, y + l / 2, r, 1.5 * PI, 0.5 * PI) 180 ctx.globalCompositeOperation = "xor" 181 ctx.fill() 182 } 183 184 function sum(x, y) { 185 return x + y 186 } 187 188 function square(x) { 189 return x * x 190 } 191 192 class sliderImgPuzzle { 193 constructor({el, onSuccess, onFail, onRefresh}) { 194 this.el = el 195 this.onSuccess = onSuccess 196 this.onFail = onFail 197 this.onRefresh = onRefresh 198 this.isEnable = true 199 } 200 201 init() { 202 this.initDOM() 203 this.initImg() 204 this.draw() 205 this.bindEvents() 206 return this; 207 } 208 209 initDOM() { 210 const canvas = createCanvas(w, h) 211 const block = canvas.cloneNode(true) 212 const canvasContainer = createElement('div') 213 const sliderImgPuzzleContainer = createElement('div') 214 const refreshIcon = createElement('div') 215 const sliderImgPuzzleMask = createElement('div') 216 const sliderImgPuzzle = createElement('div') 217 const sliderImgPuzzleIcon = createElement('span') 218 const text = createElement('span') 219 canvasContainer.className = 'canvasContainer' 220 block.className = 'block' 221 sliderImgPuzzleContainer.className = 'sliderImgPuzzleContainer' 222 refreshIcon.className = 'refreshIcon' 223 sliderImgPuzzleMask.className = 'sliderImgPuzzleMask' 224 sliderImgPuzzle.className = 'sliderImgPuzzle' 225 sliderImgPuzzleIcon.className = 'sliderImgPuzzleIcon' 226 text.innerHTML = '向右滑動滑塊填充拼圖' 227 text.className = 'sliderImgPuzzleText' 228 const el = this.el 229 canvasContainer.appendChild(canvas) 230 canvasContainer.appendChild(refreshIcon) 231 canvasContainer.appendChild(block) 232 el.appendChild(canvasContainer) 233 sliderImgPuzzle.appendChild(sliderImgPuzzleIcon) 234 sliderImgPuzzleMask.appendChild(sliderImgPuzzle) 235 sliderImgPuzzleContainer.appendChild(sliderImgPuzzleMask) 236 sliderImgPuzzleContainer.appendChild(text) 237 el.appendChild(sliderImgPuzzleContainer) 238 Object.assign(this, { 239 canvas, 240 block, 241 canvasContainer, 242 sliderImgPuzzleContainer, 243 refreshIcon, 244 sliderImgPuzzle, 245 sliderImgPuzzleMask, 246 sliderImgPuzzleIcon, 247 text, 248 canvasCtx: canvas.getContext('2d'), 249 blockCtx: block.getContext('2d') 250 }) 251 } 252 253 initImg() { 254 const img = createImg(() => { 255 this.canvasCtx.drawImage(img, 0, 0, w, h) 256 this.blockCtx.drawImage(img, 0, 0, w, h) 257 const y = this.y - r * 2 + 2 258 const ImageData = this.blockCtx.getImageData(this.x, y, L + r + 4, L + r + 4) 259 this.block.width = L 260 this.blockCtx.putImageData(ImageData, 0, y) 261 }) 262 this.img = img 263 } 264 265 draw() { 266 this.x = getRandomNumberByRange(L + 10, w - (L + 10)) 267 this.y = getRandomNumberByRange(10 + r * 2, h - (L + 10)) 268 var num = Math.floor(Math.random() * 6 + 1); 269 draw(this.canvasCtx, 'fill', this.x, this.y, num) 270 draw(this.blockCtx, 'clip', this.x, this.y, num) 271 } 272 273 clean() { 274 this.canvasCtx.clearRect(0, 0, w, h) 275 this.blockCtx.clearRect(0, 0, w, h) 276 this.block.width = w 277 } 278 279 bindEvents() { 280 this.el.onselectstart = () => false 281 this.refreshIcon.onclick = () => { 282 this.reset() 283 typeof this.onRefresh === 'function' && this.onRefresh() 284 } 285 let originX, originY, trail = [], isMouseDown = false 286 this.sliderImgPuzzle.ctrl = this; 287 this.sliderImgPuzzle.addEventListener('mousedown', function (e) { 288 if (!this.parentElement.parentElement.classList.contains("sliderImgPuzzleContainer_success")) { 289 originX = e.x, originY = e.y 290 isMouseDown = true 291 } 292 else { 293 isMouseDown = false 294 } 295 if (!this.ctrl.isEnable) isMouseDown = false; 296 }) 297 // this.sliderImgPuzzle.addEventListener('mouseover', function (e) { 298 // if (this.ctrl.isEnable) 299 // this.parentElement.parentElement.parentElement.querySelector(".canvasContainer").style.display = "block"; 300 // }) 301 // this.canvasContainer.parentElement.addEventListener('mouseleave', function (e) { 302 // this.querySelector(".canvasContainer").style.display = "none"; 303 // }) 304 document.addEventListener('mousemove', (e) => { 305 if (!isMouseDown) return false 306 const moveX = e.x - originX 307 const moveY = e.y - originY 308 if (moveX < 0 || moveX + 38 >= w) return false 309 this.sliderImgPuzzle.style.left = moveX + 'px' 310 var blockLeft = (w - 40 - 20) / (w - 40) * moveX 311 this.block.style.left = blockLeft + 'px' 312 addClass(this.sliderImgPuzzleContainer, 'sliderImgPuzzleContainer_active') 313 this.sliderImgPuzzleMask.style.width = moveX + 'px' 314 trail.push(moveY) 315 }) 316 document.addEventListener('mouseup', (e) => { 317 if (!isMouseDown) return false 318 isMouseDown = false 319 if (e.x == originX) return false 320 removeClass(this.sliderImgPuzzleContainer, 'sliderImgPuzzleContainer_active') 321 this.trail = trail 322 const {spliced, TuringTest} = this.verify() 323 if (spliced) { 324 if (TuringTest) { 325 addClass(this.sliderImgPuzzleContainer, 'sliderImgPuzzleContainer_success') 326 typeof this.onSuccess === 'function' && this.onSuccess() 327 } else { 328 addClass(this.sliderImgPuzzleContainer, 'sliderImgPuzzleContainer_fail') 329 this.text.innerHTML = '再試一次' 330 this.reset() 331 } 332 } else { 333 addClass(this.sliderImgPuzzleContainer, 'sliderImgPuzzleContainer_fail') 334 typeof this.onFail === 'function' && this.onFail() 335 setTimeout(() => { 336 this.reset() 337 }, 1000) 338 } 339 }) 340 } 341 342 verify() { 343 const arr = this.trail 344 const average = arr.reduce(sum) / arr.length 345 const deviations = arr.map(x => x - average) 346 const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length) 347 const left = parseInt(this.block.style.left) 348 return {spliced: Math.abs(left - this.x) < 10, TuringTest: average !== stddev,} 349 } 350 351 reset() { 352 this.sliderImgPuzzleContainer.className = 'sliderImgPuzzleContainer' 353 this.sliderImgPuzzle.style.left = 0 354 this.block.style.left = 0 355 this.sliderImgPuzzleMask.style.width = 0 356 this.clean() 357 this.img.src = getRandomImg() 358 this.draw() 359 } 360 361 enablePuzzle(enable) { 362 if (enable) { 363 this.isEnable = true; 364 } 365 else this.isEnable = false; 366 } 367 } 368 369 this.each(function () { 370 var $SliderImgPuzzle = $(this); 371 setting.el = $SliderImgPuzzle[0]; 372 var sip = new sliderImgPuzzle(setting).init(); 373 $SliderImgPuzzle.data("LsSliderImgPuzzle", sip); 374 }); 375 } 376 })(jQuery);
組件HTML(sliderImgPuzzle.html)canvas
1 <!DOCTYPE html> 2 <html lang="zh"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>滑動拼圖驗證碼</title> 6 <link rel="stylesheet" href="../css/sliderImgPuzzle.css"> 7 <style> 8 .container { 9 width: 310px; 10 margin: 300px auto; 11 } 12 input { 13 display: block; 14 width: 290px; 15 line-height: 40px; 16 margin: 10px 0; 17 padding: 0 10px; 18 outline: none; 19 border:1px solid #c8cccf; 20 border-radius: 4px; 21 color:#6a6f77; 22 } 23 #msg { 24 width: 100%; 25 line-height: 40px; 26 font-size: 14px; 27 text-align: center; 28 } 29 a:link,a:visited,a:hover,a:active { 30 margin-left: 100px; 31 color: #0366D6; 32 } 33 34 </style> 35 </head> 36 <body> 37 <div class="container"> 38 <div id="silderpuzzle" style="position: relative;padding-bottom: 15px;"></div> 39 <div id="msg"></div> 40 <button id="setenable">請先設置控件爲可用</button> 41 <button id="setdisabled">設置控件不可用</button> 42 <button id="reflash">刷新控件</button> 43 </div> 44 <script src="http://www.jq22.com/jquery/jquery-2.1.1.js"></script> 45 <script src="../js/sliderImgPuzzle.js"></script> 46 <script> 47 //--------------------滑塊拼圖驗證控件初始化----- 48 var option = { 49 onSuccess: function () { 50 document.getElementById('msg').innerHTML = '驗證成功!' 51 }, 52 onFail: cleanMsg, 53 onRefresh: cleanMsg 54 } 55 $("#silderpuzzle").SliderImgPuzzle(option); 56 //--------------------滑塊拼圖驗證控件初始化完成----- 57 58 //--------------------獲取控件對象----- 59 var imgPuzzle = $("#silderpuzzle").data("LsSliderImgPuzzle"); 60 61 //--------------------回調函數 62 function cleanMsg() { 63 alert("回調函數") 64 } 65 66 $("#setenable").on("click",function(){ 67 //--------------------對象提供的enable的方法 68 imgPuzzle.enablePuzzle(true); 69 }) 70 $("#setdisabled").on("click",function(){ 71 //--------------------對象提供的enable的方法 72 imgPuzzle.enablePuzzle(false); 73 }) 74 $("#reflash").on("click",function(){ 75 //--------------------對象提供的enable的方法 76 imgPuzzle.reset(); 77 }) 78 </script> 79 </body> 80 </html>
該組件爲輕量級組件,使用簡單,一看便知。支持自定義擴展,若有不會的朋友能夠拿來即用,很是方便!app
https://github.com/TopSkyhua/SliderImgPuzzledom