zoukankan      html  css  js  c++  java
  • 前端滑动验证+拼图滑动验证效果

    相信大家都玩过B站,B站在登陆的时候有个拼图滑动验证,今天就整合一下前端实现的滑动验证

    拖动滑动验证(无背景图片)

    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>拖动滑块验证</title>
    </head>
    <body>
      <meta charset="utf8">
        <style>
            /* 滑动控件容器,灰色背景 */
            #dragContainer {
                position: relative;
                display: inline-block;
                background: #e8e8e8;
                width: 300px;
                height: 33px;
                border: 2px solid #e8e8e8;
            }
            /* 滑块左边部分,绿色背景 */
            #dragBg {
                position: absolute;
                background-color: #7ac23c;
                width: 0px;
                height: 100%;
            }
            /* 滑动验证容器文本 */
            #dragText {
                position: absolute;
                width: 100%;
                height: 100%;
                /* 文字水平居中 */
                text-align: center;
                /* 文字垂直居中,这里不能用百分比,因为百分比是相对原始line-height的,而非div高度 */
                line-height: 33px;
                /* 文本不允许选中 */
                user-select: none;
                -webkit-user-select: none;
            }
            /* 滑块 */
            #dragHandler {
                position: absolute;
                width: 40px;
                height: 100%;
                cursor: move;
            }
            /* 滑块初始背景 */
            .dragHandlerBg {
                background: #fff no-repeat center url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg==");
            }
            /* 验证成功时的滑块背景 有√*/
            .dragHandlerOkBg {
                background: #fff no-repeat center url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg==");
            }
        </style>
        <script>
           //加载(事件会在页面加载完成后触发)
            window.onload = function() {
                //获取滑动控件容器,灰色背景
                var dragContainer = document.getElementById("dragContainer");
                //获取滑块左边部分,绿色背景
                var dragBg = document.getElementById("dragBg");
                //获取滑动验证容器文本
                var dragText = document.getElementById("dragText");
                //获取滑块
                var dragHandler = document.getElementById("dragHandler");
    
                //滑块的最大偏移量                 =     滑动验证容器文本长度                  -      滑块长度
                var maxHandlerOffset = dragContainer.clientWidth - dragHandler.clientWidth;
                //是否验证成功的标记
                var isVertifySucc = false;
                
                initDrag();
     
                function initDrag() {
                    //在滑动验证容器文本写入“拖动滑块验证”
                    dragText.textContent = "拖动滑块验证";
                    //给滑块添加鼠标按下监听
                    dragHandler.addEventListener("mousedown", onDragHandlerMouseDown);
                }
                 
               //选中滑块
                function onDragHandlerMouseDown() {
                    //鼠标移动监听
                    document.addEventListener("mousemove", onDragHandlerMouseMove);
                    //鼠标松开监听
                    document.addEventListener("mouseup",  onDragHandlerMouseUp);
                }
                 
               //滑块移动
                function onDragHandlerMouseMove() {
                    /*
                    html元素不存在width属性,只有clientWidth
                    offsetX是相对当前元素的,clientX和pageX是相对其父元素的
                    */
                   //滑块移动量
                    var left = event.clientX - dragHandler.clientWidth / 2;
                    //
                    if(left < 0) {
                        left = 0;
                     //如果滑块移动量   > 滑块的最大偏移量 ,则调用验证成功函数
                    } else if(left > maxHandlerOffset) {
                        left = maxHandlerOffset;
                        verifySucc();
                    }
                    //滑块移动量
                    dragHandler.style.left = left + "px";
                    //绿色背景的长度
                    dragBg.style.width = dragHandler.style.left;
                }
                
               //松开滑块函数
                function onDragHandlerMouseUp() {
                    //移除鼠标移动监听
                    document.removeEventListener("mousemove", onDragHandlerMouseMove);
                    //移除鼠标松开监听
                    document.removeEventListener("mouseup", onDragHandlerMouseUp);
                    //初始化滑块移动量
                    dragHandler.style.left = 0;
                    //初始化绿色背景
                    dragBg.style.width = 0;
                }
    
                //验证成功
                function verifySucc() {
                    //成功标记,不可回弹
                    isVertifySucc = false;
                    //容器文本的文字改为白色“验证通过”字体
                    dragText.textContent = "验证通过";
                    dragText.style.color = "white";
                    //验证通过的滑块背景
                    dragHandler.setAttribute("class", "dragHandlerOkBg");
                    //移除鼠标按下监听
                    dragHandler.removeEventListener("mousedown", onDragHandlerMouseDown);
                    //移除 鼠标移动监听
                    document.removeEventListener("mousemove", onDragHandlerMouseMove);
                    //移除鼠标松开监听
                    document.removeEventListener("mouseup", onDragHandlerMouseUp);
                    // 匹配成功以后写入你要跳转的页面
                    window.location.href="成功页面.html";
                };
            }
        </script>
    </head>
    <body>
        <div id="dragContainer"><!-- 容器初始背景 -->
            <div id="dragBg"></div><!-- 绿色背景 -->
            <div id="dragText"></div><!-- 滑动容器文本 -->
            <div id="dragHandler" class="dragHandlerBg"></div>
        </div> <!--    滑块         滑块初始背景 -->
    </body>
    </html>

    效果图:

    图片滑动验证

     css

    .block {
      position: absolute;
      left: 0;
      top: 0;
    }
     
    .sliderContainer {
      position: relative;
      text-align: center;
       310px;
      height: 40px;
      line-height: 40px;
      margin-top: 15px;
      background: #f7f9fa;
      color: #45494c;
      border: 1px solid #e4e7eb;
    }
     
    .sliderContainer_active .slider {
      height: 38px;
      top: -1px;
      border: 1px solid #1991FA;
    }
     
    .sliderContainer_active .sliderMask {
      height: 38px;
      border- 1px;
    }
     
    .sliderContainer_success .slider {
      height: 38px;
      top: -1px;
      border: 1px solid #52CCBA;
      background-color: #52CCBA !important;
    }
     
    .sliderContainer_success .sliderMask {
      height: 38px;
      border: 1px solid #52CCBA;
      background-color: #D2F4EF;
    }
     
    .sliderContainer_success .sliderIcon {
      background-position: 0 0 !important;
    }
     
    .sliderContainer_fail .slider {
      height: 38px;
      top: -1px;
      border: 1px solid #f57a7a;
      background-color: #f57a7a !important;
    }
     
    .sliderContainer_fail .sliderMask {
      height: 38px;
      border: 1px solid #f57a7a;
      background-color: #fce1e1;
    }
     
    .sliderContainer_fail .sliderIcon {
      top: 14px;
      background-position: 0 -82px !important;
    }
    .sliderContainer_active .sliderText, .sliderContainer_success .sliderText, .sliderContainer_fail .sliderText {
      display: none;
    }
     
    .sliderMask {
      position: absolute;
      left: 0;
      top: 0;
      height: 40px;
      border: 0 solid #1991FA;
      background: #D1E9FE;
    }
     
    .slider {
      position: absolute;
      top: 0;
      left: 0;
       40px;
      height: 40px;
      background: #fff;
      box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
      cursor: pointer;
      transition: background .2s linear;
    }
     
    .slider:hover {
      background: #1991FA;
    }
     
    .slider:hover .sliderIcon {
      background-position: 0 -13px;
    }
     
    .sliderIcon {
      position: absolute;
      top: 15px;
      left: 13px;
       14px;
      height: 12px;
      background: url(http://cstaticdun.126.net//2.6.3/images/icon_light.f13cff3.png) 0 -26px;
      background-size: 34px 471px;
    }
     
    .refreshIcon {
      position: absolute;
      right: 0;
      top: 0;
       34px;
      height: 34px;
      cursor: pointer;
      background: url(http://cstaticdun.126.net//2.6.3/images/icon_light.f13cff3.png) 0 -437px;
      background-size: 34px 471px;
    }

    js

    (function (window) {
      const l = 42, // 滑块边长
        r = 9, // 滑块半径
        w = 310, // canvas宽度
        h = 155, // canvas高度
        PI = Math.PI
      const L = l + r * 2 + 3 // 滑块实际边长
     
      function getRandomNumberByRange (start, end) {
        return Math.round(Math.random() * (end - start) + start)
      }
     
      function createCanvas (width, height) {
        const canvas = createElement('canvas')
        canvas.width = width
        canvas.height = height
        return canvas
      }
     
      function createImg (onload) {
        const img = createElement('img')
        img.crossOrigin = "Anonymous"
        img.onload = onload
        img.onerror = () => {
          img.src = getRandomImg()
        }
        img.src = getRandomImg()
        return img
      }
     
      function createElement (tagName, className) {
        const elment = document.createElement(tagName)
        elment.className = className
        return elment
      }
     
      function addClass (tag, className) {
        tag.classList.add(className)
      }
     
      function removeClass (tag, className) {
        tag.classList.remove(className)
      }
     
      function getRandomImg () {
        return 'https://picsum.photos/300/150/?image=' + getRandomNumberByRange(0, 1084)
      }
     
      function draw (ctx, x, y, operation) {
        ctx.beginPath()
        ctx.moveTo(x, y)
        ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI)
        ctx.lineTo(x + l, y)
        ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI)
        ctx.lineTo(x + l, y + l)
        ctx.lineTo(x, y + l)
        ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true)
        ctx.lineTo(x, y)
        ctx.lineWidth = 2
        ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'
        ctx.stroke()
        ctx[operation]()
        ctx.globalCompositeOperation = 'overlay'
      }
     
      function sum (x, y) {
        return x + y
      }
     
      function square (x) {
        return x * x
      }
     
      class jigsaw {
        constructor ({ el, onSuccess, onFail, onRefresh }) {
          el.style.position = el.style.position || 'relative'
          this.el = el
          this.onSuccess = onSuccess
          this.onFail = onFail
          this.onRefresh = onRefresh
        }
     
        init () {
          this.initDOM()
          this.initImg()
          this.bindEvents()
        }
     
        initDOM () {
          const canvas = createCanvas(w, h) // 画布
          const block = canvas.cloneNode(true) // 滑块
          const sliderContainer = createElement('div', 'sliderContainer')
          const refreshIcon = createElement('div', 'refreshIcon')
          const sliderMask = createElement('div', 'sliderMask')
          const slider = createElement('div', 'slider')
          const sliderIcon = createElement('span', 'sliderIcon')
          const text = createElement('span', 'sliderText')
     
          block.className = 'block'
          text.innerHTML = '向右滑动填充拼图'
     
          const el = this.el
          el.appendChild(canvas)
          el.appendChild(refreshIcon)
          el.appendChild(block)
          slider.appendChild(sliderIcon)
          sliderMask.appendChild(slider)
          sliderContainer.appendChild(sliderMask)
          sliderContainer.appendChild(text)
          el.appendChild(sliderContainer)
     
          Object.assign(this, {
            canvas,
            block,
            sliderContainer,
            refreshIcon,
            slider,
            sliderMask,
            sliderIcon,
            text,
            canvasCtx: canvas.getContext('2d'),
            blockCtx: block.getContext('2d')
          })
        }
     
        initImg () {
          const img = createImg(() => {
            this.draw()
            this.canvasCtx.drawImage(img, 0, 0, w, h)
            this.blockCtx.drawImage(img, 0, 0, w, h)
            const y = this.y - r * 2 - 1
            const ImageData = this.blockCtx.getImageData(this.x - 3, y, L, L)
            this.block.width = L
            this.blockCtx.putImageData(ImageData, 0, y)
          })
          this.img = img
        }
     
        draw () {
          // 随机创建滑块的位置
          this.x = getRandomNumberByRange(L + 10, w - (L + 10))
          this.y = getRandomNumberByRange(10 + r * 2, h - (L + 10))
          draw(this.canvasCtx, this.x, this.y, 'fill')
          draw(this.blockCtx, this.x, this.y, 'clip')
        }
     
        clean () {
          this.canvasCtx.clearRect(0, 0, w, h)
          this.blockCtx.clearRect(0, 0, w, h)
          this.block.width = w
        }
     
        bindEvents () {
          this.el.onselectstart = () => false
          this.refreshIcon.onclick = () => {
            this.reset()
            typeof this.onRefresh === 'function' && this.onRefresh()
          }
     
          let originX, originY, trail = [], isMouseDown = false
     
          const handleDragStart = function (e) {
            originX = e.clientX || e.touches[0].clientX
            originY = e.clientY || e.touches[0].clientY
            isMouseDown = true
          }
     
          const handleDragMove = (e) => {
            if (!isMouseDown) return false
            const eventX = e.clientX || e.touches[0].clientX
            const eventY = e.clientY || e.touches[0].clientY
            const moveX = eventX - originX
            const moveY = eventY - originY
            if (moveX < 0 || moveX + 38 >= w) return false
            this.slider.style.left = moveX + 'px'
            const blockLeft = (w - 40 - 20) / (w - 40) * moveX
            this.block.style.left = blockLeft + 'px'
     
            addClass(this.sliderContainer, 'sliderContainer_active')
            this.sliderMask.style.width = moveX + 'px'
            trail.push(moveY)
          }
     
          const handleDragEnd = (e) => {
            if (!isMouseDown) return false
            isMouseDown = false
            const eventX = e.clientX || e.changedTouches[0].clientX
            if (eventX == originX) return false
            removeClass(this.sliderContainer, 'sliderContainer_active')
            this.trail = trail
            const { spliced, verified } = this.verify()
            if (spliced) {
              if (verified) {
                addClass(this.sliderContainer, 'sliderContainer_success')
                typeof this.onSuccess === 'function' && this.onSuccess()
              } else {
                addClass(this.sliderContainer, 'sliderContainer_fail')
                this.text.innerHTML = '再试一次'
                this.reset()
              }
            } else {
              addClass(this.sliderContainer, 'sliderContainer_fail')
              typeof this.onFail === 'function' && this.onFail()
              setTimeout(() => {
                this.reset()
              }, 1000)
            }
          }
          this.slider.addEventListener('mousedown', handleDragStart)
          this.slider.addEventListener('touchstart', handleDragStart)
          document.addEventListener('mousemove', handleDragMove)
          document.addEventListener('touchmove', handleDragMove)
          document.addEventListener('mouseup', handleDragEnd)
          document.addEventListener('touchend', handleDragEnd)
        }
     
        verify () {
          const arr = this.trail // 拖动时y轴的移动距离
          const average = arr.reduce(sum) / arr.length
          const deviations = arr.map(x => x - average)
          const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length)
          const left = parseInt(this.block.style.left)
          return {
            spliced: Math.abs(left - this.x) < 10,
            verified: stddev !== 0, // 简单验证下拖动轨迹,为零时表示Y轴上下没有波动,可能非人为操作
          }
        }
     
        reset () {
          this.sliderContainer.className = 'sliderContainer'
          this.slider.style.left = 0
          this.block.style.left = 0
          this.sliderMask.style.width = 0
          this.clean()
          this.img.src = getRandomImg()
        }
     
      }
     
      window.jigsaw = {
        init: function (opts) {
          return new jigsaw(opts).init()
        }
      }
    }(window))

     引用页面:

    <link rel="stylesheet" href="../css/hdyz.css">
    <script src="../js/myyz.js"></script>
    <div id="captcha"></div>
    <script type="text/javascript">
      jigsaw.init({
        el: document.getElementById('captcha'),
      })
    </script>

    效果图:

     谢谢观看!

  • 相关阅读:
    轻重搭配
    EF的优缺点
    使用bootstrap-select有时显示“Nothing selected”
    IIS发布 HTTP 错误 500.21
    js添加的元素无法触发click事件
    sql server查看表是否死锁
    sql server把一个库表的某个字段更新到另一张表的相同字段
    SQLSERVER排查CPU占用高的情况
    SQL server中如何按照某一字段中的分割符将记录拆成多条
    LINQ to Entities does not recognize the method 'System.DateTime AddDays(Double)' method, and this method cannot be translated into a store expression.
  • 原文地址:https://www.cnblogs.com/huangting/p/11285131.html
Copyright © 2011-2022 走看看