博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于react的滑动图片验证码组件
阅读量:6465 次
发布时间:2019-06-23

本文共 9346 字,大约阅读时间需要 31 分钟。

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

效果图

图片描述

使用方式

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

class App extends Component {    state = {        url: ""    }    componentDidMount() {        this.setState({ url: getImage() })    }    onReload = () => {        this.setState({ url: getImage() })    }    render() {        return (            
{ console.log("code is match") }} />
) }}

上代码

// 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 (            
{tips.text}
刷新验证
按住滑块,拖动完成拼图
) }}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;}

转载地址:http://arkko.baihongyu.com/

你可能感兴趣的文章
golang copy函数
查看>>
《你有多少问题要请示》精华集粹
查看>>
深度 | 机器学习敲门砖:任何人都能看懂的TensorFlow介绍【转】
查看>>
leveldb学习:DBimpl
查看>>
MySQL存储引擎--MYSIAM和INNODB引擎区别
查看>>
[Recompose] Stream Props to React Children with RxJS
查看>>
打印图片
查看>>
SHOW CREATE DATABASE Syntax
查看>>
rsync常见问题及解决办法
查看>>
AKM项目轶事之GBS同事转入GDC
查看>>
MySQL日期 专题
查看>>
C#中禁止程序多开
查看>>
分布式缓存Redis使用以及原理
查看>>
[LeetCode] Number of 1 Bits 位操作
查看>>
数据结构与算法JavaScript描述——队列
查看>>
练习二:结对练习
查看>>
JSON中JObject和JArray,JValue序列化(Linq)
查看>>
杂七杂八
查看>>
Activity竟然有两个onCreate方法,可别用错了
查看>>
Linux经常使用命令(十六) - whereis
查看>>