手撸一个简单的滑块验证码
使用过很多次滑块验证码的功能,偶然一次想起来,能不能简单的实现一个呢,于是就尝试了一下,然后记录下来了,图个乐子。
思路
- 首先是绘制一张图片,自然而然,要用canvas了,就像下面这样,首先加载一张图片,然后去绘制到canvas中。
<div id="validate">
<canvas id="canvas"></canvas>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// 加载图片
const loadImage = (url) => {
return new Promise((resolve, reject) => {
const image = new Image();
image.src = url;
image.crossOrigin = 'anonymous';
image.onload = () => {
resolve(image);
};
image.onerror = () => {
reject();
}
});
};
const renderImage = (image) => {
const w = image.width;
const h = image.height;
canvas.width = w;
canvas.height = h;
ctx.drawImage(image, 0, 0, w, h);
return [w, h];
};
// 函数入口
const start = () => {
loadImage(url).then(renderImage).catch(() => {
console.log('图片加载失败');
});
};
start();
</script>
- 接下来呢,肯定就是扣出一块区域了,把原图的区域清空,然后把该区域转化成一张新的可拖拽的图片。
const clipImage = () => {
// 设置滑块的宽高
const dw = 50;
const dh = 50;
// 随机生成空缺的x坐标
const x = Math.floor(Math.random() * (w - 2 * dw) + dw);
// y坐标就固定在中间
const y = (h - dh) / 2;
// 获取到这块区域的ImageData对象
const imageData = ctx.getImageData(x, y, dw, dh);
// 清空这块区域
ctx.clearRect(x, y, dw, dh);
// 使用一个临时的canvas承载这个ImageData去生成滑块图片
let avatarCanvas = document.createElement("canvas");
avatarCanvas.width = dw;
avatarCanvas.height = dh;
let avatarCtx = avatarCanvas.getContext("2d");
avatarCtx.putImageData(imageData, 0, 0);
let avatarDataUrl = avatarCanvas.toDataURL();
const img = document.createElement('img');
img.src = avatarDataUrl;
img.style.top = `${y}px`;
img.style.left = 0;
// 图片可以拖拽
img.draggable = true;
// 挂载到页面上,样式使用绝对定位固定到最左侧的中间位置
validate.appendChild(img);
return [img, x];
}
- 最后呢,就处理一下图片的滑动事件,每次拖动的时候去修改img的left值,然后结束的时候就判断一下距离之前的随机x是否在一个可接受范围内就ok了。这个地方有一个注意点,就是图片拖拽的时候,默认会出现一个阴影,和原图片一样,会感觉很丑,可以设置一个空标签来隐藏这个阴影。
const handlerDrag = (img, x) => {
let startX = 0;
img.ondragstart = (e) => {
startX = e.pageX;
// // create an empty element
dragElement = document.createElement("span");
dragElement.innerHTML = " ";
dragElement.style.position = "absolute";
dragElement.style.left = "-1000%";
// add the element to the dom
document.body.appendChild(dragElement);
//set it as the drag image
event.dataTransfer.setDragImage(dragElement, 0, 0);
}
img.ondrag = (e) => {
if (e.pageX === 0) {
return;
}
const deltaX = e.pageX - startX;
img.style.left = `${deltaX}px`;
};
img.ondragend = (e) => {
const deltaX = e.pageX - startX;
if(Math.abs(deltaX - x) < 50) {
alert('验证成功');
} else {
alert('验证失败');
}
img.draggable = false;
}
}
- 最后把之前的代码整合一下,其实就是在初始方法中去以此调用就好了。
const start = () => {
loadImage(url).then(image => {
// 这个地方是不是很眼熟,上一个函数的执行结果,是下一个函数的参数,这个大家有兴趣的可以试试,实现一个compose函数了,类似compose: (fns: Array<(arg1, arg2...) => Array<any>) => any;
const [w, h] = renderImage(image);
const [img, x] = clipImage(w, h);
handlerDrag(img, x);
}).catch(() => {
console.log('图片加载失败');
});
};
效果图
小结
最后呢,我想说的是,这个肯定还有很多优化的地方,仅仅是提供一种实现思路,代码上也有更精简的写法,欢迎大家交流学习。