当前位置: 移动技术网 > IT编程>开发语言>JavaScript > react的滑动图片验证码组件的示例代码

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

2019年05月28日  | 移动技术网IT编程  | 我要评论
业务需求,需要在系统登陆的时候,使用“滑动图片验证码”,来验证操作的不是机器人。 效果图 使用方式 在一般的页面组件引用即可。onreload这个函数一般

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

效果图

使用方式

在一般的页面组件引用即可。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;
}

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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网