新增第181个小实例:Canvas图片滑块拖动验证码

This commit is contained in:
DESKTOP-BM6RJU5\wyanh 2022-08-21 19:19:09 +08:00
parent 12c9f8faed
commit 3ed4a9ad90
5 changed files with 426 additions and 0 deletions

View File

@ -188,6 +188,7 @@
178. HTML5+CSS3小实例炫酷的流边按钮
179. HTML5+CSS3小实例悬停放大图片的旅游画廊
180. HTML5+CSS3+JS小实例使用L2Dwidget实现二次元卡通看板娘
181. HTML5+CSS3+JS小实例Canvas图片滑块拖动验证码
#### 赞赏作者
![image](https://gitee.com/wyanhui02/html_css_demo/raw/master/images/%E8%B5%9E%E8%B5%8F%E4%BD%9C%E8%80%85/%E8%B5%9E%E8%B5%8F%E7%A0%81.jpg)

130
code/181/181.css Normal file
View File

@ -0,0 +1,130 @@
*{
margin: 0;
padding: 0;
}
body{
/* 方便演示,满屏居中 */
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
/* 小拼图 */
.block{
position: absolute;
left: 0;
top: 0;
}
/* 滑动条 */
.slider-container{
position: relative;
text-align: center;
width: 310px;
height: 40px;
line-height: 40px;
margin-top: 15px;
background-color: #f7f9fa;
color: #45454c;
border: 1px solid #e4e7eb;
}
.slider-mask{
position: absolute;
left: 0;
top: 0;
height: 40px;
border: 0px solid #1991fa;
background-color: #d1e9fe;
}
.slider{
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 40px;
background: #fff;
box-shadow: 0 0 3px rgba(0,0,0,0.3);
cursor: pointer;
transition: background 0.2s linear;
}
.slider-icon{
position: absolute;
left: 13px;
top: 15px;
width: 14px;
height: 10px;
background: url(/images/icon_light.png) 0 -26px;
background-size: 34px 471px;
}
/* 滑动条活动态 */
.slider-container-active .slider{
height: 38px;
top: -1px;
border: 1px solid #1991fa;
}
.slider-container-active .slider-mask{
height: 38px;
border-width: 1px;
}
/* 滑动条成功态 */
.slider-container-success .slider{
height: 38px;
top: -1px;
border: 1px solid #52ccba;
background-color: #52ccba !important;
}
.slider-container-success .slider-mask{
height: 38px;
border: 1px solid #52ccba;
background-color: #d2f4ef;
}
/* 成功图标 */
.slider-container-success .slider-icon{
background-position: 0 0 !important;
}
/* 滑动条失败态 */
.slider-container-fail .slider{
height: 38px;
top: -1px;
border: 1px solid #f57a7a;
background-color: #f57a7a !important;
}
.slider-container-fail .slider-mask{
height: 38px;
border: 1px solid #f57a7a;
background-color: #fce1e1;
}
/* 失败图标 */
.slider-container-fail .slider-icon{
background-position: 0 -83px !important;
}
.slider-container-active .slider-text,
.slider-container-success .slider-text,
.slider-container-fail .slider-text{
display: none;
}
.slider:hover{
background: #1991fa;
}
.slider:hover .slider-icon{
background-position: 0 -13px;
}
.refresh-icon{
position: absolute;
right: 0;
top: 0;
width: 34px;
height: 34px;
background: url(/images/icon_light.png) 0 -437px;
background-size: 34px 471px;
cursor: pointer;
}
#msg{
height: 20px;
line-height: 20px;
text-align: center;
margin-top: 15px;
}

46
code/181/181.html Normal file
View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Canvas图片滑块拖动验证码</title>
<link rel="stylesheet" href="181.css">
</head>
<body>
<div class="container">
<div id="captcha" style="position: relative;">
<!-- <canvas width="310" height="155"></canvas>
<div class="refresh-icon"></div>
<canvas width="62" height="155" class="block"></canvas>
<div class="slider-container">
<div class="slider-mask">
<div class="slider">
<span class="slider-icon"></span>
</div>
</div>
<span class="slider-text">向右滑动滑块填充拼图</span>
</div> -->
</div>
<div id="msg"></div>
</div>
</body>
</html>
<script src="181.js"></script>
<script>
captcha.init(document.getElementById('captcha'),function(){
document.getElementById('msg').innerHTML='验证成功';
setTimeout(() => {
document.getElementById('msg').innerHTML='';
}, 2000);
},function(){
document.getElementById('msg').innerHTML='验证失败';
setTimeout(() => {
document.getElementById('msg').innerHTML='';
}, 1000);
})
</script>

249
code/181/181.js Normal file
View File

@ -0,0 +1,249 @@
(function(win){
const l=42, //滑块边长
r=10, //滑块半径
w=310, //canvas宽度
h=155, //canvas高度
PI=Math.PI;
const ll=l+r*2; //滑块的实际边长
// 获取指定区间内的随机数
function getRandomNumberByRange(start,end){
return Math.round(Math.random()*(end-start)+start);
}
// 创建元素
function createElement(tagName){
return document.createElement(tagName);
}
// 创建画布
function createCanvas(width,height){
const canvas=createElement('canvas');
canvas.width=width;
canvas.height=height;
return canvas;
}
// 获取随机图片
function getRandomImg(){
// 这个网站可以生成随机图片
return 'https://picsum.photos/300/150/?image='+getRandomNumberByRange(0,100);
}
// 创建图片
function createImg(onload){
const img=createElement('img');
img.crossOrigin='Anonymous';
img.onload=onload;
img.onerror=()=>{
img.src=getRandomImg();
}
img.src=getRandomImg();
return img;
}
// 添加样式
function addClass(tag,className){
tag.classList.add(className);
}
// 移除样式
function removeClass(tag,className){
tag.classList.remove(className);
}
// 绘制
function draw(ctx,operation,x,y){
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(x+l/2,y);
ctx.arc(x+l/2,y-r+2,r,0,2*PI);
ctx.lineTo(x+l/2,y);
ctx.lineTo(x+l,y);
ctx.lineTo(x+l,y+l/2);
ctx.arc(x+l+r-2,y+l/2,r,0,2*PI);
ctx.lineTo(x+l,y+l/2);
ctx.lineTo(x+l,y+l);
ctx.lineTo(x,y+l);
ctx.lineTo(x,y);
ctx.fillStyle='#fff';
ctx[operation]();
ctx.beginPath();
ctx.arc(x,y+l/2,r,1.5*PI,0.5*PI);
ctx.globalCompositeOperation='xor';
ctx.fill();
}
// 求和
function sum(x,y){
return x+y;
}
// 求平方
function square(x){
return x*x;
}
// 验证码类
class captcha{
// 构造器
constructor(el,success,fail){
this.el=el;
this.success=success;
this.fail=fail;
}
// 初始化
init(){
this.initDOM();
this.initImg();
this.draw();
this.bindEvents();
}
// 初始化DOM
initDOM(){
const canvas=createCanvas(w,h),
block=canvas.cloneNode(true),
sliderContainer=createElement('div'),
sliderMask=createElement('div'),
slider=createElement('div'),
refreshIcon=createElement('div'),
sliderIcon=createElement('span'),
text=createElement('span');
block.className='block';
sliderContainer.className='slider-container';
sliderMask.className='slider-mask';
slider.className='slider';
refreshIcon.className='refresh-icon';
sliderIcon.className='slider-icon';
text.className='slider-text';
text.innerHTML='向右滑动滑块填充拼图';
const el=this.el;
el.appendChild(canvas);
el.appendChild(refreshIcon);
el.appendChild(block);
slider.appendChild(sliderIcon);
sliderMask.appendChild(slider);
sliderContainer.appendChild(sliderMask);
sliderContainer.appendChild(text);
el.appendChild(sliderContainer);
Object.assign(this,{
canvas,
block,
sliderContainer,
refreshIcon,
slider,
sliderMask,
sliderIcon,
text,
canvasCtx:canvas.getContext('2d'),
blockCtx:block.getContext('2d')
});
}
// 初始化图像
initImg(){
const img=createImg(()=>{
this.canvasCtx.drawImage(img,0,0,w,h);
this.blockCtx.drawImage(img,0,0,w,h);
const y=this.y-r*2+2;
const imageData=this.blockCtx.getImageData(this.x,y,ll,ll);
this.block.width=ll;
this.blockCtx.putImageData(imageData,0,y);
});
this.img=img;
}
// 绘画
draw(){
this.x=getRandomNumberByRange(ll+10,w-(ll+10));
this.y=getRandomNumberByRange(10+r*2,h-(ll+10));
draw(this.canvasCtx,'fill',this.x,this.y);
draw(this.blockCtx,'clip',this.x,this.y);
}
// 清除
clean(){
this.canvasCtx.clearRect(0,0,w,h);
this.blockCtx.clearRect(0,0,w,h);
this.block.width=w;
}
// 绑定事件
bindEvents(){
this.el.onselectstart=()=>false;
this.refreshIcon.onclick=()=>{
this.reset();
}
let originX,originY,trail=[],isMouseDown=false;
this.slider.addEventListener('mousedown',function(e){
originX=e.x;
originY=e.y;
isMouseDown=true;
})
document.addEventListener('mousemove',(e)=>{
if(!isMouseDown){
return false;
}
const moveX=e.x-originX;
const moveY=e.y-originY;
if(moveX<0 || moveX+38>=w){
return false;
}
this.slider.style.left=moveX+'px';
var blockLeft=(w-40-20)/(w-40)*moveX;
this.block.style.left=blockLeft+'px';
addClass(this.sliderContainer,'slider-container-active');
this.sliderMask.style.width=moveX+'px';
trail.push(moveY);
})
document.addEventListener('mouseup',(e)=>{
if(!isMouseDown){
return false;
}
isMouseDown=false;
if(e.x==originX){
return false;
}
removeClass(this.sliderContainer,'slider-container-active');
this.trail=trail;
const spliced=this.verify();
if(spliced){
addClass(this.sliderContainer,'slider-container-success');
this.success && this.success();
}else{
addClass(this.sliderContainer,'slider-container-fail');
this.fail && this.fail();
setTimeout(() => {
this.reset();
}, 1000);
}
})
}
// 重置
reset(){
this.sliderContainer.className='slider-container';
this.slider.style.left=0;
this.block.style.left=0;
this.sliderMask.style.width=0;
this.clean();
this.img.src=getRandomImg();
this.draw();
}
// 验证
verify(){
const left=parseInt(this.block.style.left);
return Math.abs(left-this.x)<10; //10表示容错率值越小需要拼得越精确
}
}
win.captcha={
init: function(element,success,fail){
new captcha(element,success,fail).init();
}
}
}(window))

BIN
images/icon_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB