React/Umi中实现移动端滑动图片验证功能

简介: React/Umi中实现移动端滑动图片验证功能

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

图片路径自己搞定

相关文章
|
6月前
|
前端开发 Android开发 iOS开发
移动端自适应解决方案vw(以react为例)
移动端自适应解决方案vw(以react为例)
140 0
|
6月前
|
前端开发
React查询、搜索类功能的实现
React查询、搜索类功能的实现
60 0
|
17天前
|
移动开发 前端开发 JavaScript
React DnD:实现拖拽功能的终极方案?
本文首发于微信公众号“前端徐徐”,介绍了一个强大的 React 拖拽库——React DnD。React DnD 帮助开发者轻松创建复杂的拖拽界面,适用于 Trello 风格的应用、列表重排序、可拖拽的 UI 组件等场景。文章详细介绍了 React DnD 的基本信息、主要特点、使用场景及快速上手指南。
47 3
React DnD:实现拖拽功能的终极方案?
|
2月前
|
移动开发 前端开发
react项目配合diff实现文件对比差异功能
在React项目中,可以使用`diff`库实现文件内容对比差异功能。首先安装`diff`库,然后在组件中引入并使用`Diff.diffChars`或`Diff.diffLines`方法比较文本差异。通过循环遍历`diff`结果,可以生成不同样式的HTML元素来高亮显示文本差异。
107 1
react项目配合diff实现文件对比差异功能
|
2月前
|
前端开发
React 中购物车功能实现(全选多选功能实现)
React 中购物车功能实现(全选多选功能实现)
36 2
|
2月前
|
存储 移动开发 前端开发
初探react,用react实现一个todoList功能
该文章通过创建一个TodoList应用来介绍React的基础知识,包括环境搭建、组件创建、状态管理和事件处理,并演示了如何使用React Hooks来优化组件逻辑。
|
3月前
|
前端开发
|
3月前
|
存储 JavaScript 前端开发
探索React状态管理:Redux的严格与功能、MobX的简洁与直观、Context API的原生与易用——详细对比及应用案例分析
【8月更文挑战第31天】在React开发中,状态管理对于构建大型应用至关重要。本文将探讨三种主流状态管理方案:Redux、MobX和Context API。Redux采用单一存储模型,提供预测性状态更新;MobX利用装饰器语法,使状态修改更直观;Context API则允许跨组件状态共享,无需第三方库。每种方案各具特色,适用于不同场景,选择合适的工具能让React应用更加高效有序。
74 0
|
5月前
|
前端开发
react怎么做图片报错处理
react怎么做图片报错处理
68 1
|
6月前
|
前端开发
构建一个简单的React图片画廊应用
构建一个简单的React图片画廊应用
128 0