使用canvas实现图片滑动验证

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用canvas实现图片滑动验证相关的知识,希望对你有一定的参考价值。

参考技术A 验证码本质是一种区分真实用户和僵尸程序的一种图灵测试。一些黑客会利用机器技术在某些业务应用中进行刷票(多次点击投票)、发广告、窃取客户密码等恶意操作,企业应用通过验证码确定操作者身份为真实用户,才允许进行下一步操作。具体来说,在注册、发帖、发评论、投票、提交密码前添加验证码,可以防止批量注册、发广告、刷票和破解密码等恶意操作。

滑块验证码是验证码的一种,是由图片验证码衍生后的产物。由于滑块验证码安全性更高,趣味性更强,所以多数网站或APP都选择了滑块验证码。比如说之前的b站。

那么滑块验证码的原理是什么呢?为什么拖拽到拼图处,就能判断出是人为操作还是机器人呢?
其实滑块验证码,不仅仅只是完成拼图,前端用户看不见的是——验证码后台针对用户产生的行为轨迹数据进行机器学习建模,结合访问频率、地理位置、历史记录等多个维度信息,快速、准确的返回人机判定结果,故而机器识别+模拟不易通过。

滑块验证需要前后端配合,本文就讲一下前端如何用canvas实现。

每次的拼图照片都是随机产生的,由后端提供

拼图缺口的x,y坐标用random方法随机产生

画完拼图,接下来要画滑块。这里需要用到第二个canvas,利用绝对定位使两个canva位置重合

保证滑块和拼图缺口的图案相同

增加操作条的dom到页面上,给按钮绑定onMouseDown事件

这就实现了简单的前端效果,实际操作中前端需要与后端通信,由后端进行验证返回结果。但其实滑块验证也不够安全,很容易模拟绕过,所以现在的b站也换成了中文验证码。

vue 图片滑动登录

前言

   最近在研究图片滑动解锁 登录,说是要用阿里的那个验证,但是还是想自己手写下这个Demo

  效果图是这样的:

   技术图片

本来是想用canvas 来实现的,但是类,后来还想用css 和图片来代替canvas

 

其实思路就这样的:

 

   那个缺陷的滑块位置 是随机的 根据 图片的宽高 产生 随机 数当然是定位 : left,top.,然后距离最左边的距离 moveToLeft,

  最后滑动的距离和这个距离作比较,看看是否相等 。。然后就好了......

  vue 中滑动开始 start 开始计算时间 - 》 想右滑动的距离等于滑块滑动的距离  

 

 思路其实也不难,就放上代码吧:

<template>
   <section class="code_bg">
      <div class="slide-box">
          <div class="slide-img-block">
            <div class="slide-img-div">
                <div class="slide-img-nopadding">
                  <img class="slide-img" id="slideImg" :src="img">
                  <div class="slide-block" id="slideBlock"></div>
                  <div class="slide-box-shadow" id="cutBlock"></div>
                </div>
            </div>     
          </div>   
          <div class="scroll-background  slide-img-hint-info" id="slideHintInfo" style="height: 0px;">
              <div class="slide-img-hint">
                  <div class="scroll-background slide-icon" id="slideIcon"></div>
                  <div class="slide-text">
                    <span class="slide-text-type greenColor" id="slideType">验证通过:</span>
                    <span class="slide-text-content" id="slideContent">用时0.4s</span>
                  </div>
              </div>
          </div>
          <i class="iconfont icon-refresh" @click="resImg"></i>
          <!-- 滑块 -->
          <div class="slideBox" ref="slideBox">
              <div class=‘slide‘  
                @touchstart="touchStart($event)"
                @touchmove="touchMove($event)"
                @touchend="touchEnd($event)" :style="{‘left‘:moveToLeft+‘px‘}" ref="slide">
                <i class="iconfont "></i></div>
              <div class="slideBg" :style="{‘width‘:moveToLeft+‘px‘}"></div>
              <div class="textBg" v-show="moveToLeft>0">拖动左边滑块完成上方拼图</div>
              <span class="default"  v-show="moveToLeft===0">拖动左边滑块完成上方拼图</span>
          </div>
      </div>
   </section>
</template>

<script>
export default {
  name: "slideBox",
  data() {
    return {
      msg: "滑动",
      moveToLeft: 0, //滑动距离
      starX: 0, //初始距离
      slideBoxWidth: 0,
      slideWidth: 0,
      resultX: "",
      slideBlock: "",
      cutBlock: "",
      imgWidth: "",
      imgHeight: "",
      slideIcon: "", // 图标
      slideType: "", // 失败
      slideContent: "", //正文
      slideHintInfo: "", //弹出
      isSuccess: true,
      startTamp: "", //开始时间
      endTamp: "", //结束时间
      timer: "", // 用时解开
      img: "", // 图片
      imgList: [
        "http://www.keaitupian.net/uploads/allimg/170120/1A12A959-0.jpg",
        "http://www.keaitupian.net/uploads/allimg/150409/15124TD2-3.jpg",
        "http://www.keaitupian.net/uploads/allimg/150409/15124U2E-0.jpg",
        "http://www.keaitupian.net/uploads/allimg/150409/15124RI8-4.jpg",
        "http://www.keaitupian.net/uploads/allimg/170120/1A5142649-1.jpg",
        "http://www.keaitupian.net/uploads/allimg/161207/1_161207173815_3.jpg",
        "http://www.keaitupian.net/uploads/allimg/161207/1_161207173815_4.jpg",
        "http://www.keaitupian.net/uploads/allimg/160804/09321051Z-0.jpg",
        "http://www.keaitupian.net/uploads/allimg/160804/0932105013-1.jpg"
      ]
    };
  },
  mounted() {
    let _this = this;
    setTimeout(() => {
      this.$loading.close();
    }, 500);
    _this.slideBoxWidth = _this.$refs.slideBox.clientWidth;
    _this.slideWidth = _this.$refs.slide.clientWidth;
    _this.cutBlock = document.getElementById("cutBlock"); // 裁剪区域
    _this.slideBlock = document.getElementById("slideBlock"); // 裁剪的图片
    _this.imgWidth = document.getElementById("slideImg").offsetWidth; // 图片宽
    _this.imgHeight = document.getElementById("slideImg").offsetHeight; // 图片高
    _this.slideIcon = document.getElementById("slideIcon"); // 正确、失败的图标
    _this.slideType = document.getElementById("slideType"); // 正确、失败
    _this.slideContent = document.getElementById("slideContent"); // 正确、失败的正文
    _this.slideHintInfo = document.getElementById("slideHintInfo"); // 弹出
    _this.resImg();
  },
  methods: {
    touchStart(e) {
      let _this = this;
      console.log("zzzz:" + e.targetTouches[0].pageX);
      let starX = e.targetTouches[0].pageX;
      _this.starX = starX;
      _this.startTamp = new Date().valueOf();
      if (_this.isSuccess) {
        _this.cutImg();
      }
    },
    touchMove(e) {
      let _this = this;
      console.log("yyyy:" + e.targetTouches[0].pageX);

      let ToLeft = e.targetTouches[0].pageX - _this.starX; //变化后的坐标减去初始坐标
      let slideBoxW = Math.floor(_this.slideBoxWidth - _this.slideWidth - 1); //计算大盒子宽度
      if (ToLeft < 0) {
        ToLeft = 0; //滑块不能超出大盒子左边
        _this.slideBlock.style.left = "0px";
      }
      if (ToLeft >= 0 && ToLeft <= slideBoxW) {
        _this.slideBlock.style.left = ToLeft + "px";
      }

      if (ToLeft > slideBoxW) {
        ToLeft = slideBoxW; //滑块不能超出大盒子右边
      }
      _this.moveToLeft = ToLeft;
      console.log("离开的" + _this.resultX);
    },
    touchEnd() {
      let _this = this;
      let ToLeft = _this.moveToLeft;
      if (_this.resultX > ToLeft - 4 && _this.resultX < ToLeft + 4) {
        _this.isSuccess = true;
        _this.endTamp = new Date().valueOf();
        _this.timer = ((_this.endTamp - _this.startTamp) / 1000).toFixed(1);
        // 裁剪图片(拼图的一块)
        _this.slideBlock.style.opacity = "0";
        // _this.slideBlock.style.transition = "opacity 0.6s";
        // 裁剪的区域(黑黑的那一块)
        _this.cutBlock.style.opacity = "0";
        _this.cutBlock.style.transition = "opacity 0.6s";
        // 正确弹出的图标
        _this.slideType.className = "slide-text-type greenColor";
        _this.slideType.innerHTML = "验证通过:";
        _this.slideContent.innerHTML = "用时" + _this.timer + "s";
        setTimeout(function() {
          _this.cutBlock.style.display = "none";
          _this.slideBlock.style.left = "0px";
          //_this.reToNewImg();
          _this.$toast({
            message: "验证通过"
          });
        }, 600);
        //_this.options.success && _this.options.success();
      } else {
        _this.isSuccess = false;
        // 设置样式
        // 裁剪图片(拼图的一块)
        _this.slideBlock.style.left = "0px";
        // 错误弹出的图标

        _this.slideType.className = "slide-text-type redColor";
        _this.slideType.innerHTML = "验证失败:";
        _this.slideContent.innerHTML = "拖动滑块将悬浮图像正确拼合";
        _this.slideBlock.style.left = "0px";
        _this.resImg();
      }
      // 设置样式
      _this.slideHintInfo.style.height = "22px";
      setTimeout(function() {
        _this.slideHintInfo.style.height = "0px";
      }, 1300);
      //离开的时候回到初始位置
      _this.moveToLeft = 0;
    },
    cutImg() {
      var _this = this;
      _this.cutBlock.style.display = "block";
      var cutWidth = _this.cutBlock.offsetWidth; // 裁剪区域宽
      var cutHeight = _this.cutBlock.offsetHeight; // 裁剪区域高
      // left
      _this.resultX = Math.floor(
        Math.random() * (_this.imgWidth - cutWidth * 2 - 4) + cutWidth
      );
      // top
      var cutTop = Math.floor(
        Math.random() * (_this.imgHeight - cutHeight * 2) + cutHeight
      );
      // 设置样式
      _this.cutBlock.style.cssText =
        "top:" +
        cutTop +
        "px;" +
        "left:" +
        _this.resultX +
        "px; display: block;";
      _this.slideBlock.style.top = cutTop + "px";
      _this.slideBlock.style.backgroundPosition =
        "-" + _this.resultX + "px -" + cutTop + "px";
      _this.slideBlock.style.opacity = "1";
    },
    resImg() {
      let _this = this;
      _this.isSuccess = true;
      let newImg = _this.imgList[Math.round(Math.random() * 8)];
      _this.img = newImg;
      _this.slideBlock.style.backgroundImage = "url(" + newImg + ")";
      _this.slideBlock.style.opacity = "0";
      _this.cutBlock.style.display = "none";
    }
  }
};
</script>

<style lang="scss" scoped>

// 上面的滑块
.code_bg {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 20;
  // 图片
  .slide-box {
    display: block;
    position: relative;
    top: 25%;
    background: #fff;
    padding: 5% 0;
    .slide-btn {
      height: 44px;
      width: 44px;
      background-position: 0 -84px;
      cursor: pointer;
      display: block;
      position: absolute;
      left: 0;
      top: -9px;
      -moz-box-shadow: none;
      box-shadow: none;
      border-radius: 13px;
      z-index: 399;
    }
    .icon-refresh {
      display: block;
      font-size: 0.4rem;
      margin-left: 6%;
      margin-top: 1%;
      color: #d0caec;
    }
    .slide-img-div {
      width: 100%;
      height: 3rem;
      padding: 0 5%;
      position: relative;
      border-left: 1px solid #fff;
      border-right: 1px solid #fff;
      img {
        width: 100%;
        height: 100%;
      }
      .slide-box-shadow {
        display: none;
        position: absolute;
        width: 0.8rem;
        height: 0.8rem;
        border-radius: 4px;
        background-color: rgba(0, 0, 0, 0.3);
        box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.8) inset;
      }
      .slide-block {
        opacity: 0;
        position: absolute;
        top: 0;
        left: 2px;
        width: 0.8rem;
        height: 0.8rem;
        border-radius: 0.08rem;
        background-repeat: no-repeat;
        background-attachment: scroll;
        border: 1px solid rgba(255, 255, 0, 0.8);
        background-size: 100% 3rem;
        box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.4),
          0 0 10px 0 rgba(90, 90, 90, 0.4);
        box-sizing: border-box;
        z-index: 10;
      }
      .slide-img-nopadding {
        position: relative;
        width: 100%;
        height: 100%;
      }
    }
    .slide-icon {
      float: left;
      height: 22px;
      width: 26px;
    }
    .slide-img-hint {
      -webkit-font-smoothing: subpixel-antialiased;
      font-size: 12px !important;
      line-height: 22px !important;
      margin: 0 auto;
      position: relative;
    }
    .slide-text {
      text-align: left !important;
      color: #4b3f33;
    }
    .slide-img-hint-info {
      height: 22px;
      width: 260px;
      background-position: 0 -674px;
      height: 0;
      overflow: hidden;
      position: absolute;
      bottom: 1px;
      transition: height 0.3s;
      z-index: 11;
    }
    .redColor {
      color: red;
    }
    .greenColor {
      color: green;
    }
  }
  // 滑块
  .slideBox {
    z-index: 20;
    height: 0.89rem;
    border: 1px solid #d5d3e2;
    margin: 0.1rem 0 0.2rem 0;
    line-height: 0.89rem;
    background: #d0caec;
    color: #fff;
    font-size: 16px;
    position: relative;
    top: 30%;
    width: 90%;
    left: 5%;
    .slide {
      height: .86rem;
      width: 0.8rem;
      background: #fff;
      position: absolute;
      top: 0px;
      left: 0;
      i {
        width: 0;
        height: 0;
        border-width: 10px;
        border-style: dashed dashed dashed solid;
        border-color: transparent transparent transparent #d0caec;
        position: absolute;
        top: 50%;
        left: 50%;
        margin: -0.2rem 0 0 -0.05rem;
      }
    }
    .slideBg {
      height: 0.89rem;
      background: linear-gradient(
        to top right,
        #cddc39 0%,
        #8bc34a 25%,
        #ffeb3b 100%
      );
    }
    .textBg {
      height: 0.89rem;
      width: 100%;
      text-align: center;
      position: absolute;
      top: 0;
    }
    .default {
      height: 0.89rem;
      position: absolute;
      top: 0;
      left: 15%;
      width: 70%;
      text-align: center;
    }
  }
}
</style>

 

好啦 ,就先这样吧

 

以上是关于使用canvas实现图片滑动验证的主要内容,如果未能解决你的问题,请参考以下文章

vue 图片滑动登录

图片-滑动-解锁-组件-vue-canvas

canvas实现验证码功能

Android canvas绘制的可滑动刻度尺

日常Geetest滑动验证码(三代canvas版)处理小结(以B站登录验证为例)

使用svg和canvas实现图片的剪切