vue自定义开发滑动图片验证组件

Irina ·
更新时间:2024-11-14
· 1908 次阅读

纯前端,通过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的差距,可以自己改



VUE 图片

需要 登录 后方可回复, 如果你还没有账号请 注册新账号