亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

react的滑動圖片驗證碼組件的示例代碼

2024-05-06 16:48:31
字體:
來源:轉載
供稿:網友

業務需求,需要在系統登陸的時候,使用“滑動圖片驗證碼”,來驗證操作的不是機器人。

效果圖

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品午夜一区二区欲梦| 91精品国产综合久久香蕉922| 国产97色在线|日韩| 国内伊人久久久久久网站视频| 久热99视频在线观看| 精品国产自在精品国产浪潮| 国产欧美亚洲视频| 欧美性猛交xxxx黑人猛交| 国产精品爽爽爽爽爽爽在线观看| 91在线观看免费高清完整版在线观看| 亚洲国产精彩中文乱码av| 精品中文字幕视频| 亚洲aⅴ日韩av电影在线观看| 久久久精品视频成人| 69av在线播放| 亚洲国产又黄又爽女人高潮的| 热久久视久久精品18亚洲精品| 久久久久久久久国产精品| 国产精品流白浆视频| 国产91精品久久久久久| 日韩在线观看高清| 91sao在线观看国产| 亚洲美女免费精品视频在线观看| 亚洲黄色在线看| 国产精品久久97| 欧美日本在线视频中文字字幕| 欧美在线视频播放| 91人人爽人人爽人人精88v| 亚洲欧美日韩成人| 亚洲精品美女视频| 久久91精品国产91久久久| 久久av中文字幕| 亚洲欧美一区二区激情| 久久中文字幕在线视频| 日韩av在线高清| 久久成人精品一区二区三区| 欧美精品videos| 国产福利视频一区| 国产一区二区三区在线免费观看| 亚洲精品国偷自产在线99热| 2024亚洲男人天堂| 精品国产一区二区三区久久| 国产69精品99久久久久久宅男| 神马久久久久久| 国产精品免费一区二区三区都可以| 成人国产亚洲精品a区天堂华泰| 精品国产乱码久久久久久婷婷| 日韩av在线影视| 久久精品视频亚洲| 日韩电影中文字幕| 欧美视频裸体精品| 在线精品国产成人综合| 国产成人aa精品一区在线播放| 亚洲精品国产精品久久清纯直播| 久久精品中文字幕电影| 亚洲精品动漫久久久久| 欧美疯狂性受xxxxx另类| 欧美激情在线狂野欧美精品| 国外日韩电影在线观看| 亚洲在线观看视频| 亚洲一区二区中文字幕| 在线播放日韩av| 日本欧美国产在线| 久久伊人色综合| 韩国国内大量揄拍精品视频| 国产精品久久久久久av下载红粉| 亚洲毛片在线观看.| 国产精品美女无圣光视频| 欧美日韩在线视频一区二区| 精品久久久香蕉免费精品视频| 中文字幕精品在线| 日韩av在线网页| 2019中文在线观看| 色综合91久久精品中文字幕| 成人黄色激情网| 岛国av一区二区三区| 国产欧美日韩免费| 欧美在线视频一区| 最近日韩中文字幕中文| 国产成人精品亚洲精品| 国产一级揄自揄精品视频| 日韩成人在线播放| 91av在线视频观看| 久久久这里只有精品视频| 国产97色在线|日韩| 久久手机免费视频| 欧美性xxxxx极品娇小| 国产成人在线一区二区| 久久全国免费视频| 日韩美女主播视频| 欧美成人精品xxx| 在线精品播放av| 久久久久久中文| 亚洲天堂影视av| 久久香蕉国产线看观看网| 亚洲美女喷白浆| 亚洲精品乱码久久久久久按摩观| 国产欧美韩国高清| 亚洲大胆人体在线| 免费99精品国产自在在线| 日韩精品有码在线观看| 亚洲乱码一区av黑人高潮| 国产精品高潮呻吟久久av黑人| 亚洲男人天堂九九视频| 国产成人av在线| 最好看的2019年中文视频| 一区二区三区视频免费在线观看| 狠狠久久亚洲欧美专区| 国产在线a不卡| 国产一级揄自揄精品视频| 日韩高清欧美高清| 欧美成人亚洲成人| 欧美日韩国产123| 欧美激情第三页| 欧美性感美女h网站在线观看免费| 96精品视频在线| 国产精品久久久久久久久久三级| 国产狼人综合免费视频| 91亚洲精品久久久| 国产精品99导航| 国产亚洲欧美日韩精品| 综合网日日天干夜夜久久| 午夜精品久久久久久99热| 日韩网站在线观看| 91系列在线播放| 一区二区亚洲欧洲国产日韩| 国产精品久久久久久久久借妻| 亚洲美女www午夜| 国内精品小视频在线观看| 亚洲美女免费精品视频在线观看| 亚洲石原莉奈一区二区在线观看| 国产精品一香蕉国产线看观看| 久久午夜a级毛片| 久久精品国产成人精品| 国产精品一区二区三区在线播放| 一区二区三区黄色| 亚洲美女av网站| 日本成人免费在线| 成人av.网址在线网站| 亚洲一区二区三区在线免费观看| 国产精品丝袜久久久久久不卡| 国产这里只有精品| 国产精品久久综合av爱欲tv| 亚洲欧美精品suv| 欧美性生活大片免费观看网址| 亚洲欧洲中文天堂| 国产精品综合不卡av| 欧美激情精品久久久久久久变态| 日韩中文字幕在线视频播放| 亚洲一区二区少妇| 国产一区二区三区视频在线观看| 欧美午夜丰满在线18影院| 日韩av第一页| 久久噜噜噜精品国产亚洲综合| 一道本无吗dⅴd在线播放一区| 69久久夜色精品国产7777| 在线成人激情视频| 国产97色在线|日韩| 欧美www视频在线观看| 国产精品成熟老女人| 亚洲自拍高清视频网站| 亚洲激情第一页| 欲色天天网综合久久| 精品电影在线观看|