新增第181个小实例:Canvas图片滑块拖动验证码
This commit is contained in:
parent
12c9f8faed
commit
3ed4a9ad90
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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))
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Loading…
Reference in New Issue