转:前端滑动验证+拼图滑动验证效果
相信大家都玩过B站,B站在登陆的时候有个拼图滑动验证,今天就整合一下前端实现的滑动验证
拖动滑动验证(无背景图片)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 5 <title>拖动滑块验证</title> 6 </head> 7 <body> 8 <meta charset="utf8"> 9 <style> 10 /* 滑动控件容器,灰色背景 */ 11 #dragContainer { 12 position: relative; 13 display: inline-block; 14 background: #e8e8e8; 15 width: 300px; 16 height: 33px; 17 border: 2px solid #e8e8e8; 18 } 19 /* 滑块左边部分,绿色背景 */ 20 #dragBg { 21 position: absolute; 22 background-color: #7ac23c; 23 width: 0px; 24 height: 100%; 25 } 26 /* 滑动验证容器文本 */ 27 #dragText { 28 position: absolute; 29 width: 100%; 30 height: 100%; 31 /* 文字水平居中 */ 32 text-align: center; 33 /* 文字垂直居中,这里不能用百分比,因为百分比是相对原始line-height的,而非div高度 */ 34 line-height: 33px; 35 /* 文本不允许选中 */ 36 user-select: none; 37 -webkit-user-select: none; 38 } 39 /* 滑块 */ 40 #dragHandler { 41 position: absolute; 42 width: 40px; 43 height: 100%; 44 cursor: move; 45 } 46 /* 滑块初始背景 */ 47 .dragHandlerBg { 48 background: #fff no-repeat center url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg=="); 49 } 50 /* 验证成功时的滑块背景 有√*/ 51 .dragHandlerOkBg { 52 background: #fff no-repeat center url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg=="); 53 } 54 </style> 55 <script> 56 //加载(事件会在页面加载完成后触发) 57 window.onload = function() { 58 //获取滑动控件容器,灰色背景 59 var dragContainer = document.getElementById("dragContainer"); 60 //获取滑块左边部分,绿色背景 61 var dragBg = document.getElementById("dragBg"); 62 //获取滑动验证容器文本 63 var dragText = document.getElementById("dragText"); 64 //获取滑块 65 var dragHandler = document.getElementById("dragHandler"); 66 67 //滑块的最大偏移量 = 滑动验证容器文本长度 - 滑块长度 68 var maxHandlerOffset = dragContainer.clientWidth - dragHandler.clientWidth; 69 //是否验证成功的标记 70 var isVertifySucc = false; 71 72 initDrag(); 73 74 function initDrag() { 75 //在滑动验证容器文本写入“拖动滑块验证” 76 dragText.textContent = "拖动滑块验证"; 77 //给滑块添加鼠标按下监听 78 dragHandler.addEventListener("mousedown", onDragHandlerMouseDown); 79 } 80 81 //选中滑块 82 function onDragHandlerMouseDown() { 83 //鼠标移动监听 84 document.addEventListener("mousemove", onDragHandlerMouseMove); 85 //鼠标松开监听 86 document.addEventListener("mouseup", onDragHandlerMouseUp); 87 } 88 89 //滑块移动 90 function onDragHandlerMouseMove() { 91 /* 92 html元素不存在width属性,只有clientWidth 93 offsetX是相对当前元素的,clientX和pageX是相对其父元素的 94 */ 95 //滑块移动量 96 var left = event.clientX - dragHandler.clientWidth / 2; 97 // 98 if(left < 0) { 99 left = 0; 100 //如果滑块移动量 > 滑块的最大偏移量 ,则调用验证成功函数 101 } else if(left > maxHandlerOffset) { 102 left = maxHandlerOffset; 103 verifySucc(); 104 } 105 //滑块移动量 106 dragHandler.style.left = left + "px"; 107 //绿色背景的长度 108 dragBg.style.width = dragHandler.style.left; 109 } 110 111 //松开滑块函数 112 function onDragHandlerMouseUp() { 113 //移除鼠标移动监听 114 document.removeEventListener("mousemove", onDragHandlerMouseMove); 115 //移除鼠标松开监听 116 document.removeEventListener("mouseup", onDragHandlerMouseUp); 117 //初始化滑块移动量 118 dragHandler.style.left = 0; 119 //初始化绿色背景 120 dragBg.style.width = 0; 121 } 122 123 //验证成功 124 function verifySucc() { 125 //成功标记,不可回弹 126 isVertifySucc = false; 127 //容器文本的文字改为白色“验证通过”字体 128 dragText.textContent = "验证通过"; 129 dragText.style.color = "white"; 130 //验证通过的滑块背景 131 dragHandler.setAttribute("class", "dragHandlerOkBg"); 132 //移除鼠标按下监听 133 dragHandler.removeEventListener("mousedown", onDragHandlerMouseDown); 134 //移除 鼠标移动监听 135 document.removeEventListener("mousemove", onDragHandlerMouseMove); 136 //移除鼠标松开监听 137 document.removeEventListener("mouseup", onDragHandlerMouseUp); 138 // 匹配成功以后写入你要跳转的页面 139 window.location.href="成功页面.html"; 140 }; 141 } 142 </script> 143 </head> 144 <body> 145 <div id="dragContainer"><!-- 容器初始背景 --> 146 <div id="dragBg"></div><!-- 绿色背景 --> 147 <div id="dragText"></div><!-- 滑动容器文本 --> 148 <div id="dragHandler" class="dragHandlerBg"></div> 149 </div> <!-- 滑块 滑块初始背景 --> 150 </body> 151 </html>
效果图:
图片滑动验证
css
1 .block { 2 position: absolute; 3 left: 0; 4 top: 0; 5 } 6 7 .sliderContainer { 8 position: relative; 9 text-align: center; 10 width: 310px; 11 height: 40px; 12 line-height: 40px; 13 margin-top: 15px; 14 background: #f7f9fa; 15 color: #45494c; 16 border: 1px solid #e4e7eb; 17 } 18 19 .sliderContainer_active .slider { 20 height: 38px; 21 top: -1px; 22 border: 1px solid #1991FA; 23 } 24 25 .sliderContainer_active .sliderMask { 26 height: 38px; 27 border-width: 1px; 28 } 29 30 .sliderContainer_success .slider { 31 height: 38px; 32 top: -1px; 33 border: 1px solid #52CCBA; 34 background-color: #52CCBA !important; 35 } 36 37 .sliderContainer_success .sliderMask { 38 height: 38px; 39 border: 1px solid #52CCBA; 40 background-color: #D2F4EF; 41 } 42 43 .sliderContainer_success .sliderIcon { 44 background-position: 0 0 !important; 45 } 46 47 .sliderContainer_fail .slider { 48 height: 38px; 49 top: -1px; 50 border: 1px solid #f57a7a; 51 background-color: #f57a7a !important; 52 } 53 54 .sliderContainer_fail .sliderMask { 55 height: 38px; 56 border: 1px solid #f57a7a; 57 background-color: #fce1e1; 58 } 59 60 .sliderContainer_fail .sliderIcon { 61 top: 14px; 62 background-position: 0 -82px !important; 63 } 64 .sliderContainer_active .sliderText, .sliderContainer_success .sliderText, .sliderContainer_fail .sliderText { 65 display: none; 66 } 67 68 .sliderMask { 69 position: absolute; 70 left: 0; 71 top: 0; 72 height: 40px; 73 border: 0 solid #1991FA; 74 background: #D1E9FE; 75 } 76 77 .slider { 78 position: absolute; 79 top: 0; 80 left: 0; 81 width: 40px; 82 height: 40px; 83 background: #fff; 84 box-shadow: 0 0 3px rgba(0, 0, 0, 0.3); 85 cursor: pointer; 86 transition: background .2s linear; 87 } 88 89 .slider:hover { 90 background: #1991FA; 91 } 92 93 .slider:hover .sliderIcon { 94 background-position: 0 -13px; 95 } 96 97 .sliderIcon { 98 position: absolute; 99 top: 15px; 100 left: 13px; 101 width: 14px; 102 height: 12px; 103 background: url(http://cstaticdun.126.net//2.6.3/images/icon_light.f13cff3.png) 0 -26px; 104 background-size: 34px 471px; 105 } 106 107 .refreshIcon { 108 position: absolute; 109 right: 0; 110 top: 0; 111 width: 34px; 112 height: 34px; 113 cursor: pointer; 114 background: url(http://cstaticdun.126.net//2.6.3/images/icon_light.f13cff3.png) 0 -437px; 115 background-size: 34px 471px; 116 }
js
1 (function (window) { 2 const l = 42, // 滑块边长 3 r = 9, // 滑块半径 4 w = 310, // canvas宽度 5 h = 155, // canvas高度 6 PI = Math.PI 7 const L = l + r * 2 + 3 // 滑块实际边长 8 9 function getRandomNumberByRange (start, end) { 10 return Math.round(Math.random() * (end - start) + start) 11 } 12 13 function createCanvas (width, height) { 14 const canvas = createElement('canvas') 15 canvas.width = width 16 canvas.height = height 17 return canvas 18 } 19 20 function createImg (onload) { 21 const img = createElement('img') 22 img.crossOrigin = "Anonymous" 23 img.onload = onload 24 img.onerror = () => { 25 img.src = getRandomImg() 26 } 27 img.src = getRandomImg() 28 return img 29 } 30 31 function createElement (tagName, className) { 32 const elment = document.createElement(tagName) 33 elment.className = className 34 return elment 35 } 36 37 function addClass (tag, className) { 38 tag.classList.add(className) 39 } 40 41 function removeClass (tag, className) { 42 tag.classList.remove(className) 43 } 44 45 function getRandomImg () { 46 return 'https://picsum.photos/300/150/?image=' + getRandomNumberByRange(0, 1084) 47 } 48 49 function draw (ctx, x, y, operation) { 50 ctx.beginPath() 51 ctx.moveTo(x, y) 52 ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI) 53 ctx.lineTo(x + l, y) 54 ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI) 55 ctx.lineTo(x + l, y + l) 56 ctx.lineTo(x, y + l) 57 ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true) 58 ctx.lineTo(x, y) 59 ctx.lineWidth = 2 60 ctx.fillStyle = 'rgba(255, 255, 255, 0.7)' 61 ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)' 62 ctx.stroke() 63 ctx[operation]() 64 ctx.globalCompositeOperation = 'overlay' 65 } 66 67 function sum (x, y) { 68 return x + y 69 } 70 71 function square (x) { 72 return x * x 73 } 74 75 class jigsaw { 76 constructor ({ el, onSuccess, onFail, onRefresh }) { 77 el.style.position = el.style.position || 'relative' 78 this.el = el 79 this.onSuccess = onSuccess 80 this.onFail = onFail 81 this.onRefresh = onRefresh 82 } 83 84 init () { 85 this.initDOM() 86 this.initImg() 87 this.bindEvents() 88 } 89 90 initDOM () { 91 const canvas = createCanvas(w, h) // 画布 92 const block = canvas.cloneNode(true) // 滑块 93 const sliderContainer = createElement('div', 'sliderContainer') 94 const refreshIcon = createElement('div', 'refreshIcon') 95 const sliderMask = createElement('div', 'sliderMask') 96 const slider = createElement('div', 'slider') 97 const sliderIcon = createElement('span', 'sliderIcon') 98 const text = createElement('span', 'sliderText') 99 100 block.className = 'block' 101 text.innerHTML = '向右滑动填充拼图' 102 103 const el = this.el 104 el.appendChild(canvas) 105 el.appendChild(refreshIcon) 106 el.appendChild(block) 107 slider.appendChild(sliderIcon) 108 sliderMask.appendChild(slider) 109 sliderContainer.appendChild(sliderMask) 110 sliderContainer.appendChild(text) 111 el.appendChild(sliderContainer) 112 113 Object.assign(this, { 114 canvas, 115 block, 116 sliderContainer, 117 refreshIcon, 118 slider, 119 sliderMask, 120 sliderIcon, 121 text, 122 canvasCtx: canvas.getContext('2d'), 123 blockCtx: block.getContext('2d') 124 }) 125 } 126 127 initImg () { 128 const img = createImg(() => { 129 this.draw() 130 this.canvasCtx.drawImage(img, 0, 0, w, h) 131 this.blockCtx.drawImage(img, 0, 0, w, h) 132 const y = this.y - r * 2 - 1 133 const ImageData = this.blockCtx.getImageData(this.x - 3, y, L, L) 134 this.block.width = L 135 this.blockCtx.putImageData(ImageData, 0, y) 136 }) 137 this.img = img 138 } 139 140 draw () { 141 // 随机创建滑块的位置 142 this.x = getRandomNumberByRange(L + 10, w - (L + 10)) 143 this.y = getRandomNumberByRange(10 + r * 2, h - (L + 10)) 144 draw(this.canvasCtx, this.x, this.y, 'fill') 145 draw(this.blockCtx, this.x, this.y, 'clip') 146 } 147 148 clean () { 149 this.canvasCtx.clearRect(0, 0, w, h) 150 this.blockCtx.clearRect(0, 0, w, h) 151 this.block.width = w 152 } 153 154 bindEvents () { 155 this.el.onselectstart = () => false 156 this.refreshIcon.onclick = () => { 157 this.reset() 158 typeof this.onRefresh === 'function' && this.onRefresh() 159 } 160 161 let originX, originY, trail = [], isMouseDown = false 162 163 const handleDragStart = function (e) { 164 originX = e.clientX || e.touches[0].clientX 165 originY = e.clientY || e.touches[0].clientY 166 isMouseDown = true 167 } 168 169 const handleDragMove = (e) => { 170 if (!isMouseDown) return false 171 const eventX = e.clientX || e.touches[0].clientX 172 const eventY = e.clientY || e.touches[0].clientY 173 const moveX = eventX - originX 174 const moveY = eventY - originY 175 if (moveX < 0 || moveX + 38 >= w) return false 176 this.slider.style.left = moveX + 'px' 177 const blockLeft = (w - 40 - 20) / (w - 40) * moveX 178 this.block.style.left = blockLeft + 'px' 179 180 addClass(this.sliderContainer, 'sliderContainer_active') 181 this.sliderMask.style.width = moveX + 'px' 182 trail.push(moveY) 183 } 184 185 const handleDragEnd = (e) => { 186 if (!isMouseDown) return false 187 isMouseDown = false 188 const eventX = e.clientX || e.changedTouches[0].clientX 189 if (eventX == originX) return false 190 removeClass(this.sliderContainer, 'sliderContainer_active') 191 this.trail = trail 192 const { spliced, verified } = this.verify() 193 if (spliced) { 194 if (verified) { 195 addClass(this.sliderContainer, 'sliderContainer_success') 196 typeof this.onSuccess === 'function' && this.onSuccess() 197 } else { 198 addClass(this.sliderContainer, 'sliderContainer_fail') 199 this.text.innerHTML = '再试一次' 200 this.reset() 201 } 202 } else { 203 addClass(this.sliderContainer, 'sliderContainer_fail') 204 typeof this.onFail === 'function' && this.onFail() 205 setTimeout(() => { 206 this.reset() 207 }, 1000) 208 } 209 } 210 this.slider.addEventListener('mousedown', handleDragStart) 211 this.slider.addEventListener('touchstart', handleDragStart) 212 document.addEventListener('mousemove', handleDragMove) 213 document.addEventListener('touchmove', handleDragMove) 214 document.addEventListener('mouseup', handleDragEnd) 215 document.addEventListener('touchend', handleDragEnd) 216 } 217 218 verify () { 219 const arr = this.trail // 拖动时y轴的移动距离 220 const average = arr.reduce(sum) / arr.length 221 const deviations = arr.map(x => x - average) 222 const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length) 223 const left = parseInt(this.block.style.left) 224 return { 225 spliced: Math.abs(left - this.x) < 10, 226 verified: stddev !== 0, // 简单验证下拖动轨迹,为零时表示Y轴上下没有波动,可能非人为操作 227 } 228 } 229 230 reset () { 231 this.sliderContainer.className = 'sliderContainer' 232 this.slider.style.left = 0 233 this.block.style.left = 0 234 this.sliderMask.style.width = 0 235 this.clean() 236 this.img.src = getRandomImg() 237 } 238 239 } 240 241 window.jigsaw = { 242 init: function (opts) { 243 return new jigsaw(opts).init() 244 } 245 } 246 }(window))
引用页面:
1 <link rel="stylesheet" href="../css/hdyz.css"> 2 <script src="../js/myyz.js"></script> 3 <div id="captcha"></div> 4 <script type="text/javascript"> 5 jigsaw.init({ 6 el: document.getElementById('captcha'), 7 }) 8 </script>
效果图:
文章转载自:https://www.cnblogs.com/huangting/p/11285131.html