使用elementui框架实现
<div class="button-center">
<el-popover
placement="top"
:width="imgWidth"
title="安全验证"
trigger="manual"
v-model="popoverVisible"
@hide="popoverHide"
@show="popoverShow">
<div class="z-popover">
<canvas id="sliderImg">canvas>
<canvas id="backgroundImg">canvas>
<el-slider v-model="sliderValue" :show-tooltip="showTooltip" @input="sliderInput"
@change="sliderChange" v-mousedown="sliderDown">el-slider>
<el-divider>el-divider>
<i class="el-icon-circle-close z-el-popover-size" @click="popoverVisible = false"
title="关闭">i>
<i class="el-icon-refresh z-el-popover-size" @click="popoverShow"
title="更换验证码">i>
<span class="z-slider-result">{{sliderResult}}span>
div>
<el-button slot="reference" type="primary" @click="login()" :disabled="isDisabled"
class="login-register-button-width">登录
el-button>
el-popover>
div>
function SliderImg(width, height, r, w) {
this.width = width;
this.height = height;
this.r = r;
this.w = w;
// 使用双缓冲区去闪烁
let getBufferCanvas = () => {
// 创建隐藏Canvas
let buffer = document.createElement('canvas');
buffer.width = this.width;
buffer.height = this.height;
buffer.ctx = buffer.getContext('2d');
buffer.ctx.setTransform(1, 0, 0, 1, 0, 0);
return buffer;
}
let init = () => {
if (!this.backgroundImg || !this.sliderImg) {
this.backgroundImg = document.getElementById("backgroundImg")
this.backgroundImg.width = this.width;
this.backgroundImg.height = this.height;
this.backgroundImg.ctx = this.backgroundImg.getContext('2d');
this.sliderImg = document.getElementById("sliderImg")
this.sliderImg.width = this.width;
this.sliderImg.height = this.height;
this.sliderImg.ctx = this.sliderImg.getContext('2d');
}
this.sliderWidth = 2 * (this.r + this.w);
$("#sliderImg").css('position', 'absolute').css("left", "0px");
this.buffer1 = this.buffer1 == null ? getBufferCanvas() : this.buffer1;
this.buffer2 = this.buffer2 == null ? getBufferCanvas() : this.buffer2;
this.pos = getPos();
}
this.drawImg = function (src) {
init();
let img = new Image();
img.src = src;
img.onload = () => {
drawBgBlock(img, this.buffer1.ctx);
drawSiBlock(img, this.buffer2.ctx);
}
}
let drawBgBlock = (img, ctx) => {
ctx.clearRect(0, 0, this.width, this.height);
ctx.drawImage(img, 0, 0, this.width, this.height);
// 绘制滑块的形状
drawBlock(ctx);
ctx.fill();
// 将缓冲区内容绘制到实际的画布中
this.backgroundImg.ctx.clearRect(0, 0, this.width, this.height);
this.backgroundImg.ctx.drawImage(this.buffer1, 0, 0, this.width, this.height);
}
let drawSiBlock = (img, ctx) => {
ctx.clearRect(0, 0, this.width, this.height);
ctx.drawImage(img, 0, 0, this.width, this.height);
// 创建一个临时画布画一个圆, 然后将图片绘制上去, 形成一个滑块
let tempCanvas = getBufferCanvas();
tempCanvas.ctx.translate(-this.pos.siOffset, 0);
drawBlock(tempCanvas.ctx);
tempCanvas.ctx.clip();
tempCanvas.ctx.drawImage(this.buffer2, 0, 0);
// 将缓冲区内容绘制到实际的画布中
this.sliderImg.ctx.clearRect(0, 0, this.width, this.height);
this.sliderImg.ctx.drawImage(tempCanvas, 0, 0, this.width, this.height);
}
/**
* 绘制缺口
*
* @param ctx
*/
let drawBlock = (ctx) => {
let x = this.pos.x, y = this.pos.y, r = this.pos.r, w = this.pos.w;
ctx.beginPath();
ctx.moveTo(x, y);
// left
// ctx.lineTo(x, y + w); 第一条直线可以省略, 下同理
ctx.arc(x, y + w + r, r, -0.5 * Math.PI, 0.5 * Math.PI, false);
ctx.lineTo(x, y + 2 * (w + r));
// bottom
ctx.arc(x + w + r, y + 2 * (w + r), r, Math.PI, 0, true);
ctx.lineTo(x + 2 * (w + r), y + 2 * (w + r));
// right
ctx.arc(x + 2 * (w + r), y + w + r, r, 0.5 * Math.PI, -0.5 * Math.PI, true);
ctx.lineTo(x + 2 * (w + r), y);
// top
ctx.arc(x + w + r, y, r, 0, Math.PI, false);
ctx.lineTo(x, y);
// 添加可见的效果
ctx.lineWidth = 1;
ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
ctx.strokeStyle = "rgba(255, 255, 255, 0.5)";
ctx.stroke();
// 和已有的图形进行异或操作
ctx.globalCompositeOperation = "xor";
}
/**
* 获取缺口坐标
*/
let getPos = () => {
// 背景缺口的x轴坐标
let x = getRandomNum(this.width / 2 + this.sliderWidth, this.width - 1.5 * this.sliderWidth);
// 滑块的偏移量
let siOffset = getRandomNum(0.45 * this.width, 0.55 * this.width);
// 相同的y轴高度
let y = getRandomNum(0.5 * this.sliderWidth, this.height - 1.5 * this.sliderWidth);
return {
x: x,
y: y,
r: this.r,
w: this.w,
siOffset: siOffset
}
}
/**
* 获取随机数
*
* @param min
* @param max
* @returns {number}
*/
let getRandomNum = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
}
// 控制滑块移动
sliderInput(value) {
if (this.sliderImg == null) {
return;
}
// 移动的距离
let moveLength = value * (this.imgWidth / 100)
let start = this.sliderImg.pos.x - this.sliderImg.pos.siOffset;
// 控制最大位置
if (start + moveLength >= this.imgWidth) {
this.sliderValue = value;
} else {
$("#sliderImg").css("left", moveLength);
}
},
// 鼠标按下时, 记录当下时间
sliderDown() {
this.startTime = new Date().getTime();
},
// 是否成功的判断
sliderChange(value) {
// 移动的距离
let moveLength = value * (this.imgWidth / 100) - this.sliderImg.sliderWidth / 3
// 偏移量
let offset = this.sliderImg.pos.siOffset;
// 允许的误差
let mis = 5;
// 成功的判断
if (Math.abs(moveLength - offset) < mis) {
let time = ((new Date().getTime() - this.startTime) / 1000.0).toFixed(2);
switch (true) {
case (time > 0 && time <= 1): {
this.sliderResult = "只用了" + time + "s,快如闪电!"
break;
}
case (time > 1 && time <= 2): {
this.sliderResult = "用了" + time + "s,还不错!"
break;
}
default: {
this.sliderResult = "居然使用了" + time + "s,果然持久!"
}
}
$(".z-slider-result").removeClass("z-slider-result-error").addClass("z-slider-result-success");
// 后续成功的处理
setTimeout(() => {
this.loginForm.sliderCode = this.getSliderResult(time);
}, 500);
} else {
this.sliderResult = "验证失败!"
$(".z-slider-result").removeClass("z-slider-result-success").addClass("z-slider-result-error");
this.popoverShow();
}
},
// 以下时滑动图片验证码相关
sliderValue: 0, // 滑块的值
sliderResult: "", // 验证结果
showTooltip: false, // 隐藏tooltip
imgSrc: "/imgs/test2.png", // 图片的src
popoverVisible: false, // 验证框的显示和隐藏
imgWidth: 300, // 验证码图片的宽度
imgHeight: 120, // 验证码图片的高度
sliderImg: null, // 滑动图片验证码对象
startTime: 0, // 按下滑块的时间
if (this.sliderImg == null) {
this.sliderImg = new SliderImg(this.imgWidth, this.imgHeight, 5, 10);
}
this.sliderImg.drawImg(this.imgSrc);