此功能为移动端效果
pc端请修改触发事件
ontouchstart();
ontouchmove();
ontouchend();
onmousedown();
onmousemove();
onmouseup();
/**
* @name BlockImgMove
* @description 滑动拼图验证
*/
import React, { Component } from 'react';
import { Icon, Spin } from 'antd';
import { main } from '@/utils/base64'; //js代码
import styles from './index.less'; //样式
import { history } from 'umi';
const imgSrc = require('@/assets/233.png'); //图片路径
const canWidth = 300; //容器宽
const canHeight = 160; //容器高
const canLitWidth = 42; //图片滑块宽
const canLitR = 10; //滑块附带小圆半径
const canLitL = canLitWidth + canLitR * 2 + 3; //小拼图实际边长
const PI = Math.PI; //圆周率
class BlockImgMove extends Component {
state = {
blockX: 0, //小拼图X轴坐标
textMess: '向右移动拼接图片,完成验证',
type: false, //验证状态,false为验证失败,true验证成功
loading: false, //加载状态
canvasRand: '',
blockRand: '',
imgPath: '',
};
y = 0; //小拼图达到的Y轴坐标
x = 0; //小拼图达到的X轴坐标
img = null;
componentDidMount() {
this.onMouseDown();
this.init();
}
init = () => {
this.y = 0;
this.x = 0;
// this.img = null;
this.setState(
{
type: false,
blockX: 0,
textMess: '向右移动拼接图片,完成验证',
canvasRand: `canvas${this.getRandomNumberByRange(0, 100)}`, //"canvasRand", //this.getRandomNumberByRange(0, 100)
blockRand: `block${this.getRandomNumberByRange(101, 200)}`, // "blockRand" //this.getRandomNumberByRange(101, 200)
},
() => {
this.getImg();
},
);
};
/**
* @name onMouseDown
* @description 监听鼠标点击
*/
onMouseDown = () => {
let outBox = document.getElementById('out_mouse_img');
let mouseBox = document.getElementById('mouse_img');
let that = this;
mouseBox.ontouchstart = function (ev) {
let ev00 = ev || window.event;
let px = ev00.targetTouches[0].pageX; //初始位置,对于整个页面来说,光标点击位置
let oL = this.offsetLeft; //初始位置,对于有定位的父级,元素边框侧与父级边框侧的距离 初始为0
// console.log(this.offsetLeft, '0');
// console.log(px, '1');
// console.log(window.event, '2');
mouseBox.ontouchmove = function (evs) {
if (that.state.type) {
return;
}
let ev01 = evs || window.event;
let px1 = ev01.targetTouches[0].pageX; //滑动后,当前鼠标所在位置
let oL1 = px1 - px + oL; //距初始位置移动的距离
// console.log(oL, '0', oL1, '1');
// console.log(px, '1', px1, '2');
// console.log(outBox.clientWidth,mouseBox.clientWidth);
if (oL1 <= 0) {
oL1 = 0;
} else if (oL1 > outBox.clientWidth - mouseBox.clientWidth) {
oL1 = outBox.clientWidth - mouseBox.clientWidth;
}
console.log('oL1===', oL1);
that.setState({ blockX: oL1 });
};
mouseBox.ontouchend = function () {
that.cancelMove();
};
};
};
/**
* @name textChange
* @description 验证成功,信息变化
*/
textChange = () => {
// this.setState({ textMess: "验证成功", type: true });
history.push('/');
// alert('成功')
};
/**
* @name cancelMove
* @description 鼠标离开滑块,滑块停止滑动并复位
*/
cancelMove = () => {
let mouseBox = document.getElementById('mouse_img');
let mouseLeft = mouseBox.offsetLeft;
mouseBox.onmousemove = null;
if (mouseLeft !== 0)
if (
mouseLeft === this.x ||
(mouseLeft <= this.x + 3 && mouseLeft >= this.x - 3)
) {
//验证成功的不可逆操作
this.onmousemove = null;
mouseBox.onmousemove = null;
this.textChange();
} else {
this.init();
}
};
/**
* @name getRandomNumberByRange
* @description 获取随机数
*/
getRandomNumberByRange = (start, end) => {
return Math.round(Math.random() * (end - start) + start);
};
/**
* @name getImg
* @description 获取在线图片,图片资源与项目地址源不同导致跨域,需处理跨域
* @description 通过追加到 URL 的末尾获取灰度图像。?grayscale => https://picsum.photos/200/300?grayscale
*/
getImg = async () => {
const url = `/api-online-img/${canWidth}/${canHeight}/?image=${this.getRandomNumberByRange(
1,
100,
)}`;
try {
this.setState({ loading: true });
//不同源图片,这里是先把线上图片转为base64,再进行渲染,时间上稍微多了1-2秒
main(url, (base64) => {
//线上图片出错转用本地图片
this.setState({ imgPath: base64 ? base64 : imgSrc }, () => {
this.setState({ loading: false });
this.drawInit();
});
});
} catch (error) {
console.log('err==', error);
}
};
/**
* @name draw
* @description 画图公用方法
*/
draw = (ctx, x = 0, y = 0, w = 0, operation) => {
let r = canLitR;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.arc(x + w / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
ctx.lineTo(x + w, y);
ctx.arc(x + w + r - 2, y + w / 2, r, 1.21 * PI, 2.78 * PI);
ctx.lineTo(x + w, y + w);
ctx.lineTo(x, y + w);
ctx.arc(x + r - 2, y + w / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
ctx.lineTo(x, y);
ctx.lineWidth = 2;
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
ctx.stroke();
ctx.globalCompositeOperation = 'destination-over';
operation === 'fill' ? ctx.fill() : ctx.clip();
};
/**
* @name drawInit
* @description 画图前处理
*/
drawInit = async () => {
const { canvasRand, blockRand, imgPath } = this.state;
const mycanvas = document.getElementById(canvasRand);
const myblock = document.getElementById(blockRand);
myblock.width = canWidth; //等宽获取整个图片
const canvas_ctx = mycanvas.getContext('2d');
const block_ctx = myblock.getContext('2d');
//清空画布
canvas_ctx.clearRect(0, 0, canWidth, canHeight);
block_ctx.clearRect(0, 0, canWidth, canHeight);
this.img = document.createElement('img'); //创建小图片滑块
// 随机位置创建拼图形状
this.x = this.getRandomNumberByRange(
canLitL + 10,
canWidth - (canLitL + 10),
);
this.y = this.getRandomNumberByRange(
10 + canLitR * 2,
canHeight - (canLitL + 10),
);
// console.log("this.x,this.y===",this.x)
//渲染图片
this.img.onload = async () => {
canvas_ctx.drawImage(this.img, 0, 0, canWidth, canHeight);
block_ctx.drawImage(this.img, 0, 0, canWidth, canHeight);
let _y = this.y - canLitR * 2 - 1; //小拼图实际的坐标
let ImgData = block_ctx.getImageData(
this.x - 4,
_y - 1,
canLitL,
canLitL,
);
myblock.width = canLitL; //小拼图的宽,隐藏抠图位置图片
block_ctx.putImageData(ImgData, 0, _y);
};
this.img.src = imgPath; //图片路径
this.draw(canvas_ctx, this.x, this.y - 2, canLitWidth, 'fill');
this.draw(block_ctx, this.x, this.y, canLitWidth, 'clip');
};
render() {
const { textMess, type, blockX, loading, canvasRand, blockRand } =
this.state;
return (
<div>
<h2 text="滑动拼图验证" />
<div className={styles.block}>
<div>
<div className={styles.outDiv}>
<Spin spinning={loading}>
<div className={styles.outDivNext}>
<Icon
type="redo"
className={styles.redos}
onClick={this.init}
/>
<canvas
id={canvasRand}
className={styles.outDivNoborder}
></canvas>
<canvas
id={blockRand}
className={styles.outDivLitBlock}
style={{ left: blockX }}
></canvas>
</div>
{/**滑块 */}
<div id="out_mouse_img" className={styles.outBkock}>
<div
id="mouse_img"
className={styles.moveBkock}
style={{
cursor: type ? 'default' : null,
marginLeft: blockX,
}}
onMouseLeave={this.cancelMove}
>
{type ? (
<Icon type="check-circle" className={styles.icon_check} />
) : (
<Icon type="arrow-right" className={styles.icon_check} />
)}
</div>
{/**蓝色背景 */}
<div
id="colorbg_img"
className={styles.posBkockColor}
style={{ width: blockX }}
>
{type && <div style={{ color: '#fff' }}>{textMess}</div>}
</div>
{/**默认背景 */}
<div className={styles.posBkockDefault}>
{!type && <div>{blockX === 0 && textMess}</div>}
</div>
</div>
</Spin>
</div>
</div>
</div>
</div>
);
}
}
export default BlockImgMove;
.block {
&:extend(.block);
}
.flex {
&:extend(.flex);
}
.outDivNoborder {
position: absolute;
top: 0;
left: 0;
width: 310px;
height: 155px;
// background-size: 100% 100%;
// background-repeat: no-repeat;
}
.outDiv {
position: relative;
width: 310px;
height: 195px;
border: 1px solid #ccc;
overflow: hidden;
// background-size: 100% 100%;
// background-repeat: no-repeat;
}
.outDivNext {
width: 310px;
height: 155px;
}
.outDivLitBlock {
position: absolute;
top: 0;
left: 0;
}
/**滑块*/
.outBkock {
position: relative;
width: 310px;
height: 40px;
// border: 1px solid #ccc;
}
.moveBkock {
position: relative;
display: flex;
align-items: center;
justify-content: center;
cursor: move;
width: 38px;
height: 38px;
background: #fff;
z-index: 3;
}
.posBkockDefault,
.posBkockColor {
display: flex;
align-items: center;
justify-content: center;
background: #e2e2e2;
position: absolute;
width: 100%;
height: 100%;
top: 0;
z-index: 1;
-webkit-user-select: none;
user-select: none;
}
.posBkockColor {
width: 0;
background: white;
z-index: 2;
}
.icon_check {
font-size: 18px;
z-index: 1 !important;
}
.redos {
position: absolute;
right: 5px;
top: 5px;
z-index: 100;
font-size: 20px;
color: #fff !important;
}
/**
* @desc 线上图片转base64
*/
function getBase64Image(img) {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, img.width, img.height);
const dataURL = canvas.toDataURL('image/png'); // 可选其他值 image/jpeg
return dataURL;
}
function main(src, cb) {
const image = new Image();
image.src = src; // 处理缓存
image.crossOrigin = '*'; // 支持跨域图片
image.onload = () => {
const base64 = getBase64Image(image);
cb && cb(base64);
return base64;
};
image.onerror = (error) => {
const base64 = '';
console.log('error=base64=00=', error);
cb && cb(base64);
return base64;
};
}
export { main };