React/Umi中实现移动端滑动图片验证功能
注意!!!!!
此功能为移动端效果
pc端请修改触发事件
移动端支持的点击事件:
ontouchstart(); ontouchmove(); ontouchend();
PC端支持的点击事件:
onmousedown(); onmousemove(); onmouseup();
用法不同里面的传值方式也不同 有兴趣的可以自己学习一下 别懒
代码
index.jsx
/** * @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;
index.less
.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; }
base64.js
/** * @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 };
图片路径自己搞定