MENU
前言
1、版本一的样式比较齐全;
2、版本二的JS逻辑和功能效果比较完善,且是别人的代码,后续会对样式进行完善。[Gitee | 哔哩哔哩];
3、两个版本各有千秋,主要学习里面的一些技巧,这里主要介绍版本一的样式技巧;
4、行为验证码一般是后端实现,而且大概率是使用第三方插件,因为涉及到的逻辑和内容比较多,前后端实现起来都比较麻烦。
版本一(html+JS+css)
案例公共样式
css
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
案例一(clip-path裁剪的应用)
html
<style>
.clip_path {
width: 300px;
height: 300px;
background-color: #ff0000;
}
.clip_path:first-child {
float: left;
background-color: #0000ff;
clip-path: inset(50px 20px 30px 40px round 10px);
}
</style>
<div>
<div class="clip_path"></div>
<div class="clip_path"></div>
</div>
案例一解析
代码块中包含两个<div>元素,每个元素都有类名为clip_path。此外,还有一些CSS样式被应用于这些元素。
CSS样式部分
1、clip_path类指定了一个固定的宽度(300像素)、高度(300像素)和背景颜色(红色)的区域。
2、clip_path:first-child选择器用于选择第一个具有类名clip_path的元素,并对其应用特定样式。
2.1、float: left;
将第一个元素左浮动,使其在其右侧的元素之前显示。
2.2、background-color: #0000ff;
将第一个元素的背景颜色更改为蓝色。
2.3、clip-path: inset(50px 20px 30px 40px round 10px);
将剪切路径应用于第一个元素,剪切路径的形状是一个矩形,内边距为50px 20px 30px 40px,并且角是圆形的,半径为10px。
HTML部分
两个<div>元素被包含在一个外部<div>中。
每个<div>元素都有类名为clip_path,因此它们都受到了相同的CSS样式的影响。
综上所述,代码将显示两个相同大小的红色方块,但第一个方块的背景颜色为蓝色,并具有一种剪切路径,使其内部形成一个圆角矩形区域。
案例一效果图
案例二(clamp最大值最小值的应用)
html
<style>
.clamp {
width: clamp(300px, 50vw, 600px);
height: 300px;
background-color: #ff0000;
}
</style>
<div class="clamp"></div>
案例二解析
一段简单的HTML和CSS代码段,用于创建一个具有限制宽度的元素。
1、<style>标签用于在HTML文档中定义CSS样式。
2、.clamp是一个CSS类选择器,它用于选择具有类名为"clamp"的HTML元素。
3、width: clamp(300px, 50vw, 600px);
是一个CSS属性,它使用clamp()函数来设置元素的宽度。clamp()函数接受三个参数,分别是最小宽度、首选宽度和最大宽度。在这个例子中,元素的宽度将被限制在300像素、视窗宽度的50%和600像素之间,以保证在不同设备和屏幕尺寸下有不同的大小。
4、height: 300px;
设置元素的高度为300像素。
5、background-color: #ff0000;
设置元素的背景颜色为红色。
<div class="clamp"></div>是一个HTML<div>元素,它具有一个类名为"clamp",因此应用了上述定义的CSS样式。这个<div>元素将具有限制宽度的特性,宽度将根据视窗宽度和定义的最小和最大宽度进行调整,以便在不同设备和屏幕尺寸下呈现出不同的大小。
案例二效果图
功能效果图
html
html
<div id="captcha">
<div id="handle">
<span onmousedown="onmousedownFn()" mousemove="mousemoveFn()"></span>
</div>
</div>
JavaScript
javascript
const captcha = document.querySelector('#captcha');
const handle = document.querySelector('#handle');
const button = document.querySelector('#handle span');
const oLeft = handle.getBoundingClientRect().left;
const buttonWidth = button.getBoundingClientRect().width;
let flag = false;
function onmousedownFn() {
flag = true;
}
window.addEventListener('mousemove', ({ clientX }) => {
if (flag) {
captcha.style.setProperty('--moved', `${clientX - oLeft - buttonWidth / 2}px`);
}
});
window.addEventListener('mouseup', ({ clientX }) => {
if (flag) {
const dis = clientX - oLeft;
if (dis >= 430 && dis <= 450) {
captcha.classList.add('passed');
alert('验证通过!');
} else {
captcha.style.setProperty('--moved', '0px');
}
flag = false;
}
});
style
css
* {
margin: 0;
padding: 0;
}
body {
--width: 400px;
--height: 260px;
--puzzle-width: 80px;
--puzzle-height: 80px;
--moved: 0px;
background-color: #008b8b;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
#captcha {
width: var(--width);
height: var(--height);
background-image: url('https://cn.bing.com/th?id=OHR.AlmondBloom_ZH-CN9441550492_1920x1080.jpg');
background-size: cover;
background-position: center;
position: relative;
border-radius: 4px;
box-shadow: 0px 2px 4px rgba(0, 0, 0, .3);
}
#captcha::before,
#captcha::after {
display: flex;
position: absolute;
content: '';
width: inherit;
height: inherit;
background-image: inherit;
background-size: inherit;
background-position: inherit;
clip-path: inset( calc((var(--height) - var(--puzzle-height)) / 2) var(--puzzle-width) calc((var(--height) - var(--puzzle-height)) / 2) calc(var(--width) - var(--puzzle-width) * 2));
}
#captcha::after {
transform: translateX(clamp( calc(var(--width) * -1), calc(var(--width) * -1 + var(--moved)), var(--puzzle-width)));
transition: .25s all ease-in-out;
cursor: pointer;
}
#captcha:active::after {
transition: none;
}
#captcha::before {
background-color: rgba(0, 0, 0, .6);
background-blend-mode: multiply;
}
#handle {
height: 30px;
width: calc(var(--width) + var(--puzzle-width) * 2);
border-radius: 18px;
background-color: #eeeeee;
box-shadow: inset 0 0 12px rgba(0, 0, 0, .2);
border: 3px solid #eeeeee;
position: absolute;
bottom: -50px;
left: calc(var(--puzzle-width) * 2 * -1);
}
#handle span {
width: var(--puzzle-width);
height: inherit;
display: block;
border-radius: inherit;
background-color: #fff;
box-shadow: 0 0 6px rgba(0, 0, 0, .25), 0 2px 4px rgba(0, 0, 0, .3);
cursor: move;
transform: translateX( clamp( 0px, var(--moved), calc(var(--width) + var(--puzzle-width))));
cursor: pointer;
transition: .25s all ease-in-out;
}
#captcha:active #handle span {
transition: none;
}
#captcha.passed::before,
#captcha.passed::after,
#captcha.passed #handle {
opacity: 0;
}
版本二(html+JS+css+canvas)
效果图
html
html
<div class="container">
<div id="captcha" style="position: relative;"></div>
<div id="msg"></div>
</div>
<script src="./indexV2.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>
JavaScript
javascript
(function(win) {
// 滑块边长
let l = 42,
// 滑块半径
r = 10,
// canvas宽度
w = 310,
//canvas高度
h = 155,
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);
//10表示容错率,值越小,需要拼得越精确
return Math.abs(left - this.x) < 10;
}
}
win.captcha = {
init: function(element, success, fail) {
new captcha(element, success, fail).init();
}
}
}(window));
style
css
* {
margin: 0;
padding: 0;
background-color: #333333;
}
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;
}