zoukankan      html  css  js  c++  java
  • JS滑动验证原理

    滑动验证考察的知识点:

    1 JavaScript事件:

    onmousedown:鼠标按下事件
    onmousemove:鼠标移动事件
    onmouseup:鼠标抬起事件
    onMouseLeave:鼠标离开事件
     
    2 CSS中 canvas 图片裁剪

    效果图:

     代码实现:

      1 import React, { Component } from 'react';
      2 import { Icon, Spin } from 'antd';
      3 import './index.less';
      4 import SlideImgOnePng from '@/assets/images/slide_img_one.png';
      5 import SlideImgTwoPng from '@/assets/images/slide_img_two.png';
      6 import SlideImgThreePng from '@/assets/images/slide_img_three.png';
      7 import SlideImgFourPng from '@/assets/images/slide_img_four.png';
      8 import SlideImgFivePng from '@/assets/images/slide_img_five.png';
      9 import MyIcon from '@/global';
     10 import classNames from 'classnames';
     11 
     12 // const canWidth = 345; // 容器宽
     13 // const canHeight = 215; // 容器高
     14 const canWidth = 300; // 容器宽
     15 const canHeight = 160; // 容器高
     16 const canLitWidth = 42; // 图片滑块宽
     17 const canLitR = 10; // 滑块附带小圆半径
     18 const canLitL = canLitWidth + canLitR * 2 + 3; // 小拼图实际边长
     19 
     20 // eslint-disable-next-line prefer-destructuring
     21 const PI = Math.PI; // 圆周率
     22 
     23 const time = 500; // 延迟时间
     24 
     25 class BlockMove extends Component {
     26   constructor(props) {
     27     super(props);
     28     console.log('BlockMove', props);
     29     this.state = {
     30       blockX: 0, // 小拼图X轴坐标
     31       textMess: '向右移动拼接图片,完成验证',
     32       type: 'process', // 验证状态,false为验证失败,true验证成功
     33       loading: false, // 加载状态
     34       canvasRand: '',
     35       blockRand: '',
     36       imgPath: '',
     37       defaultImgList: [
     38         SlideImgOnePng,
     39         SlideImgTwoPng,
     40         SlideImgThreePng,
     41         SlideImgFourPng,
     42         SlideImgFivePng,
     43       ],
     44       limitEmit: true,
     45     };
     46 
     47     this.y = 0; // 小拼图达到的Y轴坐标
     48     this.x = 0; // 小拼图达到的X轴坐标
     49     this.img = null;
     50     this.limitEmit = true; // 触发频率
     51   }
     52 
     53   componentDidMount() {
     54     this.props.onRef(this);
     55     this.onMouseDown();
     56     this.init();
     57   }
     58 
     59   init = () => {
     60     this.y = 0;
     61     this.x = 0;
     62     this.setState(
     63       {
     64         type: 'process',
     65         blockX: 0,
     66         textMess: '向右移动拼接图片,完成验证',
     67         canvasRand: `canvas${this.getRandomNumberByRange(0, 100)}`,
     68         blockRand: `block${this.getRandomNumberByRange(101, 200)}`,
     69         limitEmit: true,
     70       },
     71       () => {
     72         this.getImg();
     73       },
     74     );
     75   };
     76 
     77   /**
     78    * @name onMouseDown
     79    * @description 监听鼠标点击
     80    */
     81   onMouseDown = () => {
     82     const outBox = document.getElementById('out_mouse_img');
     83     const mouseBox = document.getElementById('mouse_img');
     84     const that = this;
     85 
     86     // 鼠标按下操作
     87     mouseBox.onmousedown = function (ev) {
     88       console.log(ev);
     89       const ev00 = ev || window.event;
     90       const px = ev00.pageX; // 初始位置,对于整个页面来说,光标点击位置
     91       const oL = this.offsetLeft; // 初始位置,对于有定位的父级,元素边框侧与父级边框侧的距离 初始为0
     92       console.log(px, oL);
     93 
     94       // 鼠标移动操作
     95       document.onmousemove = function (evs) {
     96         console.log(evs);
     97         if (that.state.type === 'success') {
     98           mouseBox.onmousedown = null;
     99           document.onmousemove = null;
    100           return;
    101         }
    102         const ev01 = evs || window.event;
    103         const px1 = ev01.pageX; // 滑动后,当前鼠标所在位置
    104         let oL1 = px1 - px + oL; // 距初始位置移动的距离
    105         if (oL1 <= 0) {
    106           oL1 = 0;
    107         } else if (oL1 > outBox.clientWidth - mouseBox.clientWidth) {
    108           oL1 = outBox.clientWidth - mouseBox.clientWidth;
    109         }
    110         that.setState({ blockX: oL1 });
    111       };
    112 
    113       // 鼠标放开
    114       document.onmouseup = function () {
    115         console.log('onmouseup');
    116         // document.onmousemove = null;
    117         // document.onmouseup = null;
    118         that.cancelMove();
    119       };
    120     };
    121   };
    122 
    123   /**
    124    * @name 验证成功
    125    * @description 验证成功,信息变化
    126    */
    127   validateSuccess = () => {
    128     const { limitEmit } = this.state;
    129     console.log(limitEmit);
    130     if (limitEmit) {
    131       setTimeout(() => {
    132         this.props.onSuccess();
    133       }, time);
    134     }
    135     this.setState({
    136       textMess: '验证成功',
    137       type: 'success',
    138       limitEmit: false,
    139     });
    140   };
    141 
    142   /**
    143    * @name 取消滑动
    144    * @description 鼠标离开滑块,滑块停止滑动并复位
    145    */
    146   cancelMove = () => {
    147     const mouseBox = document.getElementById('mouse_img');
    148     const mouseLeft = mouseBox.offsetLeft;
    149     console.log(mouseLeft, this.x);
    150     document.onmousemove = null;
    151     document.onmouseup = null;
    152     if (mouseLeft !== 0) {
    153       if (mouseLeft === this.x || (mouseLeft <= this.x + 3 && mouseLeft >= this.x - 3)) {
    154         this.validateSuccess();
    155       } else {
    156         console.log('init');
    157         this.setState({
    158           type: 'fail',
    159           textMess: '验证失败',
    160         });
    161         setTimeout(() => {
    162           this.init();
    163         }, time);
    164       }
    165     }
    166   };
    167 
    168   // 鼠标移开
    169   onLeaveBlock = () => {
    170     const mouseBox = document.getElementById('mouse_img');
    171     const mouseLeft = mouseBox.offsetLeft;
    172     console.log('onLeaveBlock', mouseLeft, this.x);
    173     if (mouseLeft !== 0) {
    174       if (mouseLeft === this.x || (mouseLeft <= this.x + 3 && mouseLeft >= this.x - 3)) {
    175         document.onmousemove = null;
    176         document.onmouseup = null;
    177         this.validateSuccess();
    178       }
    179     } else {
    180       console.log('leaveMouse');
    181     }
    182   };
    183 
    184   /**
    185    * @name getRandomNumberByRange
    186    * @description 获取随机数
    187    */
    188   getRandomNumberByRange = (start, end) => {
    189     return Math.round(Math.random() * (end - start) + start);
    190   };
    191 
    192   /**
    193    * @name getImg
    194    * @description 获取图片资源
    195    * @description
    196    */
    197   getImg = async () => {
    198     const { defaultImgList } = this.state;
    199     const index = Math.floor(Math.random() * defaultImgList.length);
    200     this.setState(
    201       {
    202         imgPath: defaultImgList[index],
    203       },
    204       () => {
    205         this.drawInit();
    206       },
    207     );
    208   };
    209 
    210   /**
    211    * @name drawInit
    212    * @description 画图前处理
    213    */
    214   drawInit = async () => {
    215     const { canvasRand, blockRand, imgPath } = this.state;
    216     const mycanvas = document.getElementById(canvasRand);
    217     const myblock = document.getElementById(blockRand);
    218     myblock.width = canWidth; // 等宽获取整个图片
    219     const canvasCtx = mycanvas.getContext('2d');
    220     const blockCtx = myblock.getContext('2d');
    221     // 清空画布
    222     canvasCtx.clearRect(0, 0, canWidth, canHeight);
    223     blockCtx.clearRect(0, 0, canWidth, canHeight);
    224     // 创建小图片滑块
    225     this.img = document.createElement('img');
    226     // 随机位置创建拼图形状
    227     this.x = this.getRandomNumberByRange(canLitL + 10, canWidth - (canLitL + 10));
    228     this.y = this.getRandomNumberByRange(10 + canLitR * 2, canHeight - (canLitL + 10));
    229     // console.log(this.x, this.y);
    230 
    231     // 渲染图片
    232     this.img.onload = async () => {
    233       canvasCtx.drawImage(this.img, 0, 0, canWidth, canHeight);
    234       blockCtx.drawImage(this.img, 0, 0, canWidth, canHeight);
    235       const yAxis = this.y - canLitR * 2 - 1; // 小拼图实际的坐标
    236       // console.log(yAxis);
    237       const ImgData = blockCtx.getImageData(this.x - 4, yAxis - 1, canLitL, canLitL);
    238       myblock.width = canLitL; // 小拼图的宽,隐藏抠图位置图片
    239       blockCtx.putImageData(ImgData, 0, yAxis);
    240     };
    241     this.img.src = imgPath; // 图片路径
    242 
    243     this.draw(canvasCtx, this.x, this.y - 2, canLitWidth, 'fill');
    244     this.draw(blockCtx, this.x, this.y, canLitWidth, 'clip');
    245   };
    246 
    247   /**
    248    * @name draw
    249    * @description 画图公用方法
    250    */
    251   draw = (ctx, x = 0, y = 0, w = 0, operation) => {
    252     const r = canLitR;
    253     ctx.beginPath();
    254     ctx.moveTo(x, y);
    255     ctx.arc(x + w / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
    256     ctx.lineTo(x + w, y);
    257     ctx.arc(x + w + r - 2, y + w / 2, r, 1.21 * PI, 2.78 * PI);
    258     ctx.lineTo(x + w, y + w);
    259     ctx.lineTo(x, y + w);
    260     ctx.arc(x + r - 2, y + w / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
    261     ctx.lineTo(x, y);
    262     ctx.lineWidth = 2;
    263     ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
    264     ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
    265     ctx.stroke();
    266     ctx.globalCompositeOperation = 'destination-over';
    267 
    268     // eslint-disable-next-line no-unused-expressions
    269     operation === 'fill' ? ctx.fill() : ctx.clip();
    270   };
    271 
    272   render() {
    273     console.log(this.state);
    274     const { textMess, type, blockX, loading, canvasRand, blockRand } = this.state;
    275     return (
    276       <div className="outDiv">
    277         <Spin spinning={loading}>
    278           <div className="outDivNext">
    279             <MyIcon type="icon-refresh" className="my-icon-refresh" onClick={this.init} />
    280             <canvas id={canvasRand} className="outDivNoborder"></canvas>
    281             <canvas id={blockRand} className="outDivLitBlock" style={{ left: blockX }}></canvas>
    282           </div>
    283 
    284           {/** 移动滑块 */}
    285           <div id="out_mouse_img" className="outBkock" onMouseLeave={this.onLeaveBlock}>
    286             <div
    287               id="mouse_img"
    288               className={classNames('moveBlock', type === 'fail' && 'move-fail-btn')}
    289               style={{ marginLeft: blockX }}
    290             >
    291               {type === 'success' && <MyIcon type="icon-right-arrow" className="icon_check" />}
    292 
    293               {type === 'process' && <MyIcon type="icon-more" className="icon_check" />}
    294 
    295               {type === 'fail' && <MyIcon type="icon-fail" className="icon_check" />}
    296             </div>
    297 
    298             {/** 默认背景 */}
    299             <div className="default-btn">
    300               {type === 'process' && <div>{blockX === 0 && textMess}</div>}
    301             </div>
    302 
    303             {/** 移动之后背景 */}
    304             <div
    305               className={classNames('default-btn move-btn', type === 'fail' && 'fail-btn')}
    306               style={{  blockX }}
    307             >
    308               {type !== 'process' && <div style={{ color: '#fff' }}>{textMess}</div>}
    309             </div>
    310           </div>
    311         </Spin>
    312       </div>
    313     );
    314   }
    315 }
    316 export default BlockMove;
    View Code

    推荐阅读:

    网易易盾参考效果图:https://dun.163.com/trial/jigsaw

    CSS canvas图片裁剪原理:https://www.cnblogs.com/huanglei-/p/8568405.html

    JS实现拖动滑块验证:https://blog.csdn.net/tel13259437538/article/details/79822694

  • 相关阅读:
    CSS BEM 命名规范简介
    React 端的编程范式
    在React应用程序中用RegEx测试密码强度
    React 中获取数据的 3 种方法及它们的优缺点
    vue props传值常见问题
    如何理解vue中的v-model?
    利用jQuery not()方法选取除某个元素外的所有元素
    初识Nest.js
    react-绑定this并传参的三种方式
    Angular怎么防御xss攻击?
  • 原文地址:https://www.cnblogs.com/terrymin/p/14968572.html
Copyright © 2011-2022 走看看