react的滑动图片验证码组件的示例代码
程序员文章站
2022-07-04 21:42:20
业务需求,需要在系统登陆的时候,使用“滑动图片验证码”,来验证操作的不是机器人。
效果图
使用方式
在一般的页面组件引用即可。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; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。