zoukankan      html  css  js  c++  java
  • react使用ant design pro时的滑动图片组件

    react的滑动图片验证,是基于https://segmentfault.com/a/1190000018309458?utm_source=tag-newest做的修改,改动的主要有以下几点:

    1.将css的改为less,适配ant design

    2.将图片进行初次加载就执行裁剪的方法

    3.适配手机的滑动事件

    // index.js
    /**
     * @name Index
     * @desc 滑动拼图验证
     * @author darcrand
     * @version 2019-02-26
     *
     * @param {String} imageUrl 图片的路径
     * @param {Number} imageWidth 展示图片的宽带
     * @param {Number} imageHeight 展示图片的高带
     * @param {Number} fragmentSize 滑动图片的尺寸
     * @param {Function} onReload 当点击'重新验证'时执行的函数
     * @param {Function} onMath 匹配成功时执行的函数
     * @param {Function} onError 匹配失败时执行的函数
     */
    
    import React from "react";
    
    import stylecss from "./index.less"
    
    const icoSuccess = require("./icons/success.png")
    const icoError = require("./icons/error.png")
    const icoReload = require("./icons/refresh.png")
    const icoSlider = require("./icons/slider.png")
    
    const STATUS_LOADING = 0 // 还没有图片
    const STATUS_READY = 1 // 图片渲染完成,可以开始滑动
    const STATUS_MATCH = 2 // 图片位置匹配成功
    const STATUS_ERROR = 3 // 图片位置匹配失败
    
    const arrTips = [{ ico: icoSuccess, text: "匹配成功" }, { ico: icoError, text: "匹配失败" }]
    
    // 生成裁剪路径
    function createClipPath(ctx, size = 100, styleIndex = 0) {
      const styles = [
        [0, 0, 0, 0],
        [0, 0, 0, 1],
        [0, 0, 1, 0],
        [0, 0, 1, 1],
        [0, 1, 0, 0],
        [0, 1, 0, 1],
        [0, 1, 1, 0],
        [0, 1, 1, 1],
        [1, 0, 0, 0],
        [1, 0, 0, 1],
        [1, 0, 1, 0],
        [1, 0, 1, 1],
        [1, 1, 0, 0],
        [1, 1, 0, 1],
        [1, 1, 1, 0],
        [1, 1, 1, 1]
      ]
      const style = styles[styleIndex]
    
      const r = 0.1 * size
      ctx.save()
      ctx.beginPath()
      // left
      ctx.moveTo(r, r)
      ctx.lineTo(r, 0.5 * size - r)
      ctx.arc(r, 0.5 * size, r, 1.5 * Math.PI, 0.5 * Math.PI, style[0])
      ctx.lineTo(r, size - r)
      // bottom
      ctx.lineTo(0.5 * size - r, size - r)
      ctx.arc(0.5 * size, size - r, r, Math.PI, 0, style[1])
      ctx.lineTo(size - r, size - r)
      // right
      ctx.lineTo(size - r, 0.5 * size + r)
      ctx.arc(size - r, 0.5 * size, r, 0.5 * Math.PI, 1.5 * Math.PI, style[2])
      ctx.lineTo(size - r, r)
      // top
      ctx.lineTo(0.5 * size + r, r)
      ctx.arc(0.5 * size, r, r, 0, Math.PI, style[3])
      ctx.lineTo(r, r)
    
      ctx.clip()
      ctx.closePath()
    }
    
    class ImgCode extends React.Component {
      static defaultProps = {
        imageUrl: "",
        imageWidth: 400,
        imageHeight: 200,
        fragmentSize: 80,
        onReload: () => {},
        onMatch: () => {},
        onError: () => {}
      }
    
      state = {
        isMovable: false,
        offsetX: 0, //图片截取的x
        offsetY: 0, //图片截取的y
        startX: 0, // 开始滑动的 x
        oldX: 0,
        currX: 0, // 滑块当前 x,
        status: STATUS_LOADING,
        showTips: false,
        tipsIndex: 0
      }
    
      componentDidMount() {
        this.renderImage()
      }
    
      componentDidUpdate(prevProps) {
        // 当父组件传入新的图片后,开始渲染
        if (!!this.props.imageUrl && prevProps.imageUrl !== this.props.imageUrl) {
          this.renderImage()
        }
      }
    
      componentWillUnmount() {
        this.setState = (state, callback) => {
          return;
        };
      }
    
      renderImage = () => {
        // 初始化状态
        this.setState({ status: STATUS_LOADING,startX: 0, oldX: 0, currX: 0})
    
        // 创建一个图片对象,主要用于canvas.context.drawImage()
        const objImage = new Image()
    
        objImage.addEventListener("load", () => {
          const { imageWidth, imageHeight, fragmentSize } = this.props
    
          // 先获取两个ctx
          const ctxShadow = this.refs.shadowCanvas.getContext("2d")
          const ctxFragment = this.refs.fragmentCanvas.getContext("2d")
    
          ctxShadow.clearRect(0, 0, this.props.fragmentSize, this.props.fragmentSize)
          ctxFragment.clearRect(0, 0, this.props.fragmentSize, this.props.fragmentSize)
    
          // 让两个ctx拥有同样的裁剪路径(可滑动小块的轮廓)
          const styleIndex = Math.floor(Math.random() * 16)
          createClipPath(ctxShadow, fragmentSize, styleIndex)
          createClipPath(ctxFragment, fragmentSize, styleIndex)
    
          // 随机生成裁剪图片的开始坐标
          const clipX = Math.floor(fragmentSize + (imageWidth - 2 * fragmentSize) * Math.random())
          const clipY = Math.floor((imageHeight - fragmentSize) * Math.random())
          // 让小块绘制出被裁剪的部分
          ctxFragment.drawImage(objImage, clipX, clipY, fragmentSize, fragmentSize, 0, 0, fragmentSize, fragmentSize)
    
          // 让阴影canvas带上阴影效果
          ctxShadow.fillStyle = "rgba(0, 0, 0, 0.5)"
          ctxShadow.fill()
    
          // 恢复画布状态
          ctxShadow.restore()
          ctxFragment.restore()
    
          // 设置裁剪小块的位置
          this.setState({ offsetX: clipX, offsetY: clipY })
    
          // 修改状态
          this.setState({ status: STATUS_READY })
        })
    
        objImage.src = this.props.imageUrl
      }
    
      onMoveStart = e => {
        if (this.state.status !== STATUS_READY) {
          return
        }
    
        // 记录滑动开始时的绝对坐标x
        this.setState({ isMovable: true, startX: e.clientX })
      }
    
      onMoving = e => {
        if (this.state.status !== STATUS_READY || !this.state.isMovable) {
          return
        }
        const distance = e.clientX - this.state.startX
        let currX = this.state.oldX + distance
    
        const minX = 0
        const maxX = this.props.imageWidth - this.props.fragmentSize
        currX = currX < minX ? 0 : currX > maxX ? maxX : currX
    
        this.setState({ currX })
      }
    
      onMoveEnd = () => {
        if (this.state.status !== STATUS_READY || !this.state.isMovable) {
          return
        }
        // 将旧的固定坐标x更新
        this.setState(pre => ({ isMovable: false, oldX: pre.currX }))
    
        const isMatch = Math.abs(this.state.currX - this.state.offsetX) < 5
        if (isMatch) {
          this.setState(pre => ({ status: STATUS_MATCH, currX: pre.offsetX }), this.onShowTips)
          this.props.onMatch()
        } else {
          this.setState({ status: STATUS_ERROR }, () => {
            this.onReset()
            this.onShowTips()
          })
          this.props.onError()
        }
      }
    
      onPhoneMoveStart = e => {
        if (this.state.status !== STATUS_READY) {
          return
        }
    
        // 记录滑动开始时的绝对坐标x
        this.setState({ isMovable: true, startX: e.touches[0].pageX })
      }
    
      onPhoneMoving = e => {
        if (this.state.status !== STATUS_READY || !this.state.isMovable) {
          return
        }
        const distance = e.touches[0].pageX - this.state.startX
        let currX = this.state.oldX + distance
    
        const minX = 0
        const maxX = this.props.imageWidth - this.props.fragmentSize
        currX = currX < minX ? 0 : currX > maxX ? maxX : currX
    
        this.setState({ currX })
      }
    
      onPhoneMoveEnd = () => {
        if (this.state.status !== STATUS_READY || !this.state.isMovable) {
          return
        }
        // 将旧的固定坐标x更新
        this.setState(pre => ({ isMovable: false, oldX: pre.currX }))
    
        const isMatch = Math.abs(this.state.currX - this.state.offsetX) < 5
        if (isMatch) {
          this.setState(pre => ({ status: STATUS_MATCH, currX: pre.offsetX }), this.onShowTips)
          this.props.onMatch()
        } else {
          this.setState({ status: STATUS_ERROR }, () => {
            this.onReset()
            this.onShowTips()
          })
          this.props.onError()
        }
      }
    
      onReset = () => {
        const timer = setTimeout(() => {
          this.setState({ oldX: 0, currX: 0, status: STATUS_READY })
          clearTimeout(timer)
        }, 1000)
      }
    
      onReload = () => {
        if (this.state.status !== STATUS_READY && this.state.status !== STATUS_MATCH) {
          return
        }
        const ctxShadow = this.refs.shadowCanvas.getContext("2d")
        const ctxFragment = this.refs.fragmentCanvas.getContext("2d")
    
        // 清空画布
        ctxShadow.clearRect(0, 0, this.props.fragmentSize, this.props.fragmentSize)
        ctxFragment.clearRect(0, 0, this.props.fragmentSize, this.props.fragmentSize)
    
        this.setState(
          {
            isMovable: false,
            offsetX: 0, //图片截取的x
            offsetY: 0, //图片截取的y
            startX: 0, // 开始滑动的 x
            oldX: 0,
            currX: 0, // 滑块当前 x,
            status: STATUS_LOADING
          },
          this.props.onReload
        )
      }
    
      onShowTips = () => {
        if (this.state.showTips) {
          return
        }
    
        const tipsIndex = this.state.status === STATUS_MATCH ? 0 : 1
        this.setState({ showTips: true, tipsIndex })
        const timer = setTimeout(() => {
          this.setState({ showTips: false })
          clearTimeout(timer)
        }, 2000)
      }
    
      render() {
        const { imageUrl, imageWidth, imageHeight, fragmentSize } = this.props
        const { offsetX, offsetY, currX, showTips, tipsIndex } = this.state
        const tips = arrTips[tipsIndex]
    
        const icoSlider = require("./icons/slider.png")
    
    
        return (
          <div className={stylecss.imageCode} style={{  imageWidth }}>
            <div className={stylecss.imageContainer} style={{ height: imageHeight, backgroundImage: `url("${imageUrl}")` }}>
              <canvas
                ref="shadowCanvas"
                className={stylecss.canvas}
                width={fragmentSize}
                height={fragmentSize}
                style={{ left: offsetX + "px", top: offsetY + "px" }}
              />
              <canvas
                ref="fragmentCanvas"
                className={stylecss.canvas}
                width={fragmentSize}
                height={fragmentSize}
                style={{ top: offsetY + "px", left: currX + "px" }}
              />
    
              <div className={showTips ? stylecss.tipsContainerActive : stylecss.tipsContainer}>
                <i className={stylecss.tipsIco} style={{ backgroundImage: `url("${tips.ico}")` }} />
                <span className={stylecss.tipsText}>{tips.text}</span>
              </div>
            </div>
    
            <div className={stylecss.reloadContainer}>
              <div className={stylecss.reloadWrapper} onClick={this.onReload}>
                <i className={stylecss.reloadIco} style={{ backgroundImage: `url("${icoReload}")` }} />
                <span className={stylecss.reloadTips}>刷新验证</span>
              </div>
            </div>
    
            <div className={stylecss.sliderWrpper} onMouseMove={this.onMoving} onTouchMove={this.onPhoneMoving} onMouseLeave={this.onMoveEnd}>
              <div className={stylecss.sliderBar}>按住滑块,拖动完成拼图</div>
              <div
                className={stylecss.sliderButton}
                onTouchStart={this.onPhoneMoveStart}
                onTouchEnd={this.onPhoneMoveEnd}
                onMouseDown={this.onMoveStart}
                onMouseUp={this.onMoveEnd}
                style={{ left: currX + "px", backgroundImage: `url("${icoSlider}")` }}
              />
            </div>
          </div>
        )
      }
    }
    
    export default ImgCode

    样式

    .imageCode {
      //padding: 10px;
      user-select: none;
    }
    
    .imageContainer {
      position: relative;
      background-color: #ddd;
    }
    
    .canvas {
      position: absolute;
      top: 0;
      left: 0;
    }
    
    .reloadContainer {
      margin: 5px 0;
    }
    
    .reloadWrapper {
      display: inline-flex;
      align-items: center;
      cursor: pointer;
    }
    
    .reloadIco {
      width: 25px;
      height: 20px;
      margin-right: 10px;
      background: center/cover no-repeat;
    }
    
    .reloadTips {
      font-size: 14px;
      color: #666;
    }
    
    .sliderWrpper {
      position: relative;
      margin: 10px 0;
    }
    
    .sliderBar {
      //padding: 10px;
      font-size: 14px;
      text-align: center;
      color: #999;
      background-color: #ddd;
    }
    
    .sliderButton {
      position: absolute;
      top: 50%;
      left: 0;
      width: 50px;
      height: 50px;
      border-radius: 25px;
      transform: translateY(-50%);
      cursor: pointer;
      background: #fff center/80% 80% no-repeat;
      box-shadow: 0 2px 10px 0 #333;
    }
    
    /* 提示信息 */
    .tipsContainer,
    .tipsContainerActive {
      position: absolute;
      top: 50%;
      left: 50%;
      display: flex;
      align-items: center;
      padding: 10px;
      transform: translate(-50%, -50%);
      transition: all 0.25s;
      background: #fff;
      border-radius: 5px;
    
      visibility: hidden;
      opacity: 0;
    }
    
    .tipsContainerActive {
      visibility: visible;
      opacity: 1;
    }
    
    .tipsIco {
      width: 20px;
      height: 20px;
      margin-right: 10px;
      background: center/cover no-repeat;
    }
    
    .tipsText {
      color: #666;
    }

    使用页面

    state = {
        imageCodeKey: undefined,    //后台返回的redis的key值
        url:'',    //图片路径
        fileName:1     //图片名称
      };
    
    componentDidMount() {
        this.fetchImageCode();
      }
    
    
      onReload = () => {
        const {fileName} = this.state;
        this.getImage(fileName);
      }
    
      getImage=(fileName)=>{
        let url = `/image/`
        if(fileName>=5){
          fileName =1;
          url = url+'1.jpg'
        }else {
          fileName++
          url = url+fileName+'.jpg'
        }
        this.setState({fileName:fileName,url:url,imageCodeKey:undefined})
        return url
      }
    
      // 滑动成功
      sildeImageCode = () => {
        const { dispatch } = this.props;
        dispatch({
          type: 'login/slideImageCode',
          callback: res => {
            const { code, data } = res;
            if (code === API_RESPONSE_CODE.SUCCESS) {
              this.setState({
                imageCodeKey: data,
              });
            }
          },
        });
      };
    
      // 加载验证码
      fetchImageCode = () => {
        const {fileName} = this.state
        this.setState({
          imageCodeKey: undefined,
          url: this.getImage(fileName)
        });
      };

    <ImgCode imageUrl={url} onReload={this.onReload} onMatch={() => { this.sildeImageCode() }} />
  • 相关阅读:
    css知多少(10)——display
    css知多少(9)——float下篇
    centos-lynx
    linux-redis
    springMVC robots.txt 处理
    .net core
    docker快速入门+搭建javaweb环境
    maven+spring+springMVC+mybatis+dubbox
    微信群之社群经济的魔力
    maven项目部署打包
  • 原文地址:https://www.cnblogs.com/ly-lyq/p/10913012.html
Copyright © 2011-2022 走看看