业务需求,需要在系统登陆的时候,使用“滑动图片验证码”,来验证操作的不是机器人。
效果图
使用方式
在一般的页面组件引用即可。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 "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 "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; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
华山资源网 Design By www.eoogi.com
广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
华山资源网 Design By www.eoogi.com
暂无评论...
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
2024年11月17日
2024年11月17日
- 【雨果唱片】中国管弦乐《鹿回头》WAV
- APM亚流新世代《一起冒险》[FLAC/分轨][106.77MB]
- 崔健《飞狗》律冻文化[WAV+CUE][1.1G]
- 罗志祥《舞状元 (Explicit)》[320K/MP3][66.77MB]
- 尤雅.1997-幽雅精粹2CD【南方】【WAV+CUE】
- 张惠妹.2007-STAR(引进版)【EMI百代】【WAV+CUE】
- 群星.2008-LOVE情歌集VOL.8【正东】【WAV+CUE】
- 罗志祥《舞状元 (Explicit)》[FLAC/分轨][360.76MB]
- Tank《我不伟大,至少我能改变我。》[320K/MP3][160.41MB]
- Tank《我不伟大,至少我能改变我。》[FLAC/分轨][236.89MB]
- CD圣经推荐-夏韶声《谙2》SACD-ISO
- 钟镇涛-《百分百钟镇涛》首批限量版SACD-ISO
- 群星《继续微笑致敬许冠杰》[低速原抓WAV+CUE]
- 潘秀琼.2003-国语难忘金曲珍藏集【皇星全音】【WAV+CUE】
- 林东松.1997-2039玫瑰事件【宝丽金】【WAV+CUE】