时间:2022-08-05 11:43:56 | 栏目:vue | 点击:次
纯前端,通过canvas来自定义开发滑动图片验证,反过来也能完成纯滑动验证。
<template> <div class="verification" ref="verification"> <!-- 画布部分 --> <canvas ref="slideVerify" class="slide-img"></canvas> <div style="display:none"> <img ref="imgs" :src="imgList[imgIndex]"/> </div> <!-- 下面滑块部分 --> <div class="slide-wrapper bg-start"> <!-- 滑块 --> <div class="btn" ref="btn" @mousedown="mouseDown" @mouseup="mouseUp"></div> <p class="text" ref="text">{{content}}</p> <div class="bg" ref="bg"></div> </div> <!-- 刷新按钮 --> <button class="refresh" @click="refresh"></button> </div> </template> <script> export default { data () { return { imgIndex: 0, blockCanvas: null, imgList: [require('../assets/1.png'), require('../assets/3.png'), require('../assets/4.png'), require('../assets/5.png') ], content: '滑动滑块', isDown: false, // 鼠标是否按下 btnX: 0, imgX: 0 } }, mounted () { this.imgIndex = this.randomNumber(0, 4) document.addEventListener('mousemove', this.mouseMove) this.imageCanvas() }, methods: { // 生成随机数字 randomNumber (min, max) { return Math.floor(Math.random() * (max - min) + min) }, // 鼠标按下时 mouseDown (e) { this.isDown = true this.btnX = e.clientX - this.$refs.btn.offsetLeft }, // 鼠标滑动时 mouseMove (e) { // 滑块左端向右边移动的距离 let moveX = e.clientX - this.btnX if (this.isDown) { // 滑块滑动时不能超过的距离 if (this.$refs.btn.offsetLeft <= 259 && this.$refs.btn.offsetLeft >= 0) { this.$refs.btn.style.left = `${moveX}px` this.blockCanvas.style.left = `${moveX - this.imgX}px` this.$refs.bg.style.width = `${moveX}px` } } }, // 滑动中松开 mouseUp () { let leftX = this.$refs.btn.offsetLeft // 方块的位置和缺失的位置重合允许左右2px的误差 if (this.imgX >= leftX - 1 && this.imgX <= leftX + 1) { // 滑动成功时的逻辑 this.$refs.btn.classList.add('btnsuccess') this.isDown = false } // 如果滑动失败,滑块自动回到左边 if (this.isDown) { this.$refs.btn.style.left = 0 this.blockCanvas.style.left = `-${this.imgX}px` this.$refs.bg.style.width = 0 } // 开关原则 this.isDown = false }, // 画图 imageCanvas () { this.blockCanvas = this.blockCanvas ? this.blockCanvas.remove() : null this.blockCanvas = this.createCanvas(300, 150) this.$refs.verification.insertBefore(this.blockCanvas, this.$refs.slideVerify) let x = this.randomNumber(60, 200) let y = 40 this.imgX = x let c = this.$refs.slideVerify let bg = c.getContext('2d') let img = this.$refs.imgs let bk = this.blockCanvas.getContext('2d') // 在两块画布上都放上相同的图片 img.onload = () => { bg.drawImage(img, 0, 0) bk.drawImage(img, 0, 0) } this.drawBlock(bg, x, y, 'fill') this.drawBlock(bk, x, y, 'clip') }, // 画抠出来的方块 drawBlock (ctx, x, y, type) { ctx.beginPath() ctx.moveTo(x, y) ctx.arc(x + 42 / 2, y - 9 + 2, 9, 0.72 * Math.PI, 2.26 * Math.PI) ctx.lineTo(x + 42, y) ctx.arc(x + 42 + 9 - 2, y + 42 / 2, 9, 1.21 * Math.PI, 2.78 * Math.PI) ctx.lineTo(x + 42, y + 42) ctx.lineTo(x, y + 42) ctx.arc(x + 9 - 2, y + 42 / 2, 9 + 0.4, 2.76 * Math.PI, 1.24 * Math.PI, true) ctx.lineTo(x, y) ctx.lineWidth = 2 ctx.fillStyle = 'rgba(255, 255, 255, 0.7)' ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)' ctx.stroke() ctx[type]() ctx.globalCompositeOperation = 'destination-over' // 解决进入页面时不自动扣拼图样式的麻烦(有时需要鼠标点击后才会出现裁剪后的拼图) this.blockCanvas.style.left = `-${x}px` }, // 刷新 refresh () { // 有时会出现点击刷新,randomNumber返回的数字和上次存储的一样,画布清空后但是填充时没有改变;所以当一样时,不会执行刷新操作 if (this.imgIndex == this.randomNumber(0, 4)) { return false } this.clean() this.$refs.btn.style.left = 0 this.$refs.bg.style.width = 0 this.$refs.btn.classList.remove('btnsuccess') this.isDown = false // 鼠标是否按下 this.btnX = 0 // 鼠标点击的水平位置与滑块移动水平位置的差 this.imgX = 0 this.imageCanvas('restore') this.imgIndex = this.randomNumber(0, 4) }, // 清空canvas clean () { let cxt2 = this.$refs.slideVerify.getContext('2d') cxt2.clearRect(0, 0, 300, 150) }, // 新建canvas createCanvas (width, height) { const canvas = document.createElement('canvas') canvas.width = width canvas.height = height canvas.style.position = 'absolute' return canvas } } } </script> <style scoped> .verification { position: relative; width: 300px; margin: 0 auto; } .slide-wrapper { position: relative; width: 300px; height: 40px; } .bg-start { background: cadetblue; } .bg { position: absolute; height: 40px; background: #ccc; } .text { position: absolute; width: 100%; height: 40px; text-align: center; line-height: 40px; margin: 0; /* z-index: 1; */ } .text-success { color: white; z-index: 2; } .btn { position: absolute; width: 40px; height: 40px; z-index: 1; border-radius: 5px; background: rgb(143, 145, 148); text-align: center; font-size: 24px; color: white; box-shadow: 0 0 1px 1px #fff; 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=="); } .btnsuccess { 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=="); } .refresh { cursor: pointer; width: 20px; height: 20px; position: absolute; z-index: 1; top: 0; right: 10px; opacity: .6; background: url('../assets/ref.jpg') no-repeat; background-size: cover; } </style>
完成效果图
滑动完成时
因为允许1px的差距,可以自己改