1.参考地址:https://www.cnblogs.com/huang...
2.效果:
imageimage

3.逻辑:底部滑块的拖动控制小拼图移动,当其左边距与切割位置x坐标一致,或在一定范围内,即为正确;难点在于切割出来拼图,这一步参考上面链接地址;

4.项目地址:https://gitee.com/beiysd/reac...

5.部分源码如下:

/**

 * @name BlockImgMove

 * @description 滑动拼图验证

 */

import React, { Component } from "react";

import Title from "@/component/Title";

import { Icon, Spin } from "antd";

import { main } from "@/utils/base64";

import styles from "./styles.module.less";

const imgSrc = require("@/assets/img/10355.jpg");

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.onmousedown = function (ev) {

 let ev00 = ev || window.event;

 let px = ev00.pageX; //初始位置,对于整个页面来说,光标点击位置

 let oL = this.offsetLeft; //初始位置,对于有定位的父级,元素边框侧与父级边框侧的距离 初始为0

 mouseBox.onmousemove = function (evs) {

 if (that.state.type) {

 return;

 }

 let ev01 = evs || window.event;

 let px1 = ev01.pageX; //滑动后,当前鼠标所在位置

 let oL1 = px1 - px + oL; //距初始位置移动的距离

 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.onmouseup = function () {

 that.cancelMove();

 };

 };

 };

 /**

 * @name textChange

 * @description 验证成功,信息变化

 */

 textChange = () => {

 this.setState({ textMess: "验证成功", type: true });

 };

 /**

 * @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 获取在线图片,图片资源与项目地址源不同导致跨域,需处理跨域

 */

 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));

 //渲染图片

 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 - 5, _y - 3, canLitL, canLitL);

 myblock.width = canLitL;//小拼图的宽,隐藏抠图位置图片

 block_ctx.putImageData(ImgData, 0, _y);

 };

 this.img.src = imgPath; //图片路径

 this.draw(canvas_ctx, this.x, this.y, canLitWidth, "fill");

 this.draw(block_ctx, this.x, this.y, canLitWidth, "clip");

 };

 render() {

 const { textMess, type, blockX, loading, canvasRand, blockRand } = this.state;

 return (

 <div>

 <h3>BlockImgMove</h3>

 <div className={styles.block}>

 <Title text="滑动拼图验证" />

 <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>

 </Spin>

 </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>

 </div>

 </div>

 </div>

 );

 }

}

export default BlockImgMove;

Beiysd
32 声望1 粉丝

BUG