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


Posted in Javascript onFebruary 27, 2019

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

效果图

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

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

Javascript 相关文章推荐
Jquery 组合form元素为json格式,asp.net反序列化
Jul 09 Javascript
Jquery截取中文字符串的实现代码
Dec 22 Javascript
Jquery uploadify图片上传插件无法上传的解决方法
Dec 16 Javascript
js写的方法实现上传图片之后查看大图
Mar 05 Javascript
自己使用js/jquery写的一个定制对话框控件
May 02 Javascript
JS中如何实现点击a标签返回页面顶部的问题
Jan 19 Javascript
vue中keep-alive的用法及问题描述
May 15 Javascript
Vue中使用 setTimeout() setInterval()函数的问题
Sep 13 Javascript
JavaScript检查数据中是否存在相同的元素(两种方法)
Oct 07 Javascript
微信小程序使用npm支持踩坑
Nov 07 Javascript
微信小程序返回上一级页面的实现代码
Jun 19 Javascript
vue从后台渲染文章列表以及根据id跳转文章详情详解
Dec 14 Vue.js
原生JS forEach()和map()遍历的区别、兼容写法及jQuery $.each、$.map遍历操作
Feb 27 #jQuery
js/jquery遍历对象和数组的方法分析【forEach,map与each方法】
Feb 27 #jQuery
spring+angular实现导出excel的实现代码
Feb 27 #Javascript
react native 原生模块桥接的简单说明小结
Feb 26 #Javascript
JavaScript实现获取两个排序数组的中位数算法示例
Feb 26 #Javascript
小程序hover-class点击态效果实现
Feb 26 #Javascript
JS实现计算小于非负数n的素数的数量算法示例
Feb 26 #Javascript
You might like
PHP处理Json字符串解码返回NULL的解决方法
2014/09/01 PHP
PHP中常用的字符串格式化函数总结
2014/11/19 PHP
PHP连接MYSQL数据库实例代码
2016/01/20 PHP
PHP使用mysql与mysqli连接Mysql数据库用法示例
2016/07/07 PHP
jQuery.autocomplete 支持中文输入(firefox)修正方法
2011/03/10 Javascript
JS+ACTIVEX实现网页选择本地目录路径对话框
2013/03/18 Javascript
原生js实现的贪吃蛇网页版游戏完整实例
2015/05/18 Javascript
只需五句话搞定JavaScript作用域(经典)
2016/07/26 Javascript
JS弹出窗口的运用与技巧大全
2016/11/01 Javascript
微信小程序之圆形进度条实现思路
2018/02/22 Javascript
Nodejs模块载入运行原理
2018/02/23 NodeJs
在vue-cli项目中使用bootstrap的方法示例
2018/04/21 Javascript
angular6根据environments配置文件更改开发所需要的环境的方法
2019/03/06 Javascript
javascript设计模式 ? 单例模式原理与应用实例分析
2020/04/09 Javascript
JavaScript undefined及null区别实例解析
2020/07/21 Javascript
基于JQuery和DWR实现异步数据传递
2020/10/16 jQuery
TensorFLow用Saver保存和恢复变量
2018/03/10 Python
python调用动态链接库的基本过程详解
2019/06/19 Python
Python实现计算对象的内存大小示例
2019/07/10 Python
opencv python如何实现图像二值化
2020/02/03 Python
Restful_framework视图组件代码实例解析
2020/11/17 Python
CSS3属性box-shadow使用详细教程
2012/01/21 HTML / CSS
英国助听器购物网站:Hearing Direct
2018/08/21 全球购物
莱德杯高尔夫欧洲官方商店:Ryder Cup Shop
2019/08/14 全球购物
Static Nested Class 和 Inner Class的不同
2013/11/28 面试题
万户网络JAVA程序员岗位招聘笔试试卷
2013/01/08 面试题
中英文自我评价常用句型
2013/12/19 职场文书
秘书专业自荐信范文
2013/12/26 职场文书
金融学专业大学生职业生涯规划
2014/03/07 职场文书
聚美优品恶搞广告词
2014/03/14 职场文书
小学校长竞聘演讲稿
2014/05/16 职场文书
大学生自荐材料范文
2014/12/30 职场文书
2015年七一建党节活动总结
2015/03/20 职场文书
拾金不昧表扬稿大全
2015/05/05 职场文书
pytorch 如何使用batch训练lstm网络
2021/05/28 Python
详解pytorch创建tensor函数
2022/03/22 Python