欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

react的滑动图片验证码组件的示例代码

程序员文章站 2022-04-10 09:56:04
业务需求,需要在系统登陆的时候,使用“滑动图片验证码”,来验证操作的不是机器人。 效果图 使用方式 在一般的页面组件引用即可。onreload这个函数一般...

业务需求,需要在系统登陆的时候,使用“滑动图片验证码”,来验证操作的不是机器人。

效果图

react的滑动图片验证码组件的示例代码

使用方式

在一般的页面组件引用即可。onreload这个函数一般是用来请求后台图片的。

class app extends component {
  state = {
    url: ""
  }

  componentdidmount() {
    this.setstate({ url: getimage() })
  }

  onreload = () => {
    this.setstate({ url: getimage() })
  }
  render() {
    return (
      <div>
        <imagecode
          imageurl={this.state.url}
          onreload={this.onreload}
          onmatch={() => {
            console.log("code is match")
          }}
        />
      </div>
    )
  }
}

上代码

// index.js
/**
 * @name imagecode
 * @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 "./styles.css"

const icosuccess = require("./icons/success.png")
const icoerror = require("./icons/error.png")
const icoreload = require("./icons/reload.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 imagecode extends react.component {
  static defaultprops = {
    imageurl: "",
    imagewidth: 500,
    imageheight: 300,
    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
  }

  componentdidupdate(prevprops) {
    // 当父组件传入新的图片后,开始渲染
    if (!!this.props.imageurl && prevprops.imageurl !== this.props.imageurl) {
      this.renderimage()
    }
  }

  renderimage = () => {
    // 初始化状态
    this.setstate({ status: status_loading })

    // 创建一个图片对象,主要用于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")

      // 让两个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()
    }
  }

  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]

    return (
      <div classname="image-code" style={{ width: imagewidth }}>
        <div classname="image-container" style={{ height: imageheight, backgroundimage: `url("${imageurl}")` }}>
          <canvas
            ref="shadowcanvas"
            classname="canvas"
            width={fragmentsize}
            height={fragmentsize}
            style={{ left: offsetx + "px", top: offsety + "px" }}
          />
          <canvas
            ref="fragmentcanvas"
            classname="canvas"
            width={fragmentsize}
            height={fragmentsize}
            style={{ top: offsety + "px", left: currx + "px" }}
          />

          <div classname={showtips ? "tips-container--active" : "tips-container"}>
            <i classname="tips-ico" style={{ backgroundimage: `url("${tips.ico}")` }} />
            <span classname="tips-text">{tips.text}</span>
          </div>
        </div>

        <div classname="reload-container">
          <div classname="reload-wrapper" onclick={this.onreload}>
            <i classname="reload-ico" style={{ backgroundimage: `url("${icoreload}")` }} />
            <span classname="reload-tips">刷新验证</span>
          </div>
        </div>

        <div classname="slider-wrpper" onmousemove={this.onmoving} onmouseleave={this.onmoveend}>
          <div classname="slider-bar">按住滑块,拖动完成拼图</div>
          <div
            classname="slider-button"
            onmousedown={this.onmovestart}
            onmouseup={this.onmoveend}
            style={{ left: currx + "px", backgroundimage: `url("${icoslider}")` }}
          />
        </div>
      </div>
    )
  }
}

export default imagecode
// styles.css

.image-code {
  padding: 10px;
  user-select: none;
}

.image-container {
  position: relative;
  background-color: #ddd;
}

.canvas {
  position: absolute;
  top: 0;
  left: 0;
}

.reload-container {
  margin: 20px 0;
}

.reload-wrapper {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}

.reload-ico {
  width: 20px;
  height: 20px;
  margin-right: 10px;
  background: center/cover no-repeat;
}

.reload-tips {
  font-size: 14px;
  color: #666;
}

.slider-wrpper {
  position: relative;
  margin: 10px 0;
}

.slider-bar {
  padding: 10px;
  font-size: 14px;
  text-align: center;
  color: #999;
  background-color: #ddd;
}

.slider-button {
  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;
}

/* 提示信息 */
.tips-container,
.tips-container--active {
  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;
}

.tips-container--active {
  visibility: visible;
  opacity: 1;
}

.tips-ico {
  width: 20px;
  height: 20px;
  margin-right: 10px;
  background: center/cover no-repeat;
}

.tips-text {
  color: #666;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。