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