欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue項(xiàng)目登錄模塊滑塊拼圖驗(yàn)證功能實(shí)現(xiàn)代碼(純前端)

 更新時(shí)間:2024年07月26日 11:28:36   作者:Doraemon*  
滑塊驗(yàn)證作為一種反機(jī)器人的工具,也會不斷發(fā)展和演進(jìn),以適應(yīng)不斷變化的威脅,這篇文章主要給大家介紹了vue項(xiàng)目登錄模塊滑塊拼圖驗(yàn)證功能實(shí)現(xiàn)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

在當(dāng)今互聯(lián)網(wǎng)時(shí)代,隨著技術(shù)的不斷進(jìn)步,傳統(tǒng)的驗(yàn)證碼驗(yàn)證方式已經(jīng)無法滿足對安全性和用戶體驗(yàn)的需求。為了應(yīng)對日益狡猾的機(jī)器人和惡意攻擊,許多網(wǎng)站和應(yīng)用程序開始引入圖形驗(yàn)證碼,其中一種備受歡迎的形式就是圖片旋轉(zhuǎn)驗(yàn)證功能。這項(xiàng)技術(shù)通過利用用戶交互、視覺識別和動(dòng)態(tài)效果,為用戶提供了一種全新、有趣且高效的驗(yàn)證方式。本文將深入探討如何實(shí)現(xiàn)這一引人注目的圖片旋轉(zhuǎn)驗(yàn)證功能,讓您輕松保護(hù)網(wǎng)站安全,同時(shí)提升用戶體驗(yàn)

效果展示

功能介紹:

在vue項(xiàng)目中將此驗(yàn)證彈框封裝成一個(gè)單獨(dú)的組件,完整代碼如下;

此功能中的圖是利用canvas技術(shù)隨機(jī)畫10個(gè)圖形拼接而成,然后就是畫缺口和缺口的內(nèi)陰影。

拖動(dòng)滑軌調(diào)整小圖移動(dòng)位置,完成驗(yàn)證功能,驗(yàn)證失敗會自動(dòng)刷新再次驗(yàn)證,點(diǎn)擊“刷新”也可以收到刷新圖案,這是一個(gè)由純前端實(shí)現(xiàn)的驗(yàn)證功能;

完整代碼—組件封裝

  <!-- 滑塊拼圖驗(yàn)證模塊 -->
<template>
  <div>
    <!-- <div @click="changeBtn" class="btn">開始驗(yàn)證</div> -->
    <div></div>
    <!-- 本體部分 -->
    <div v-show="shoWData" :class="['vue-puzzle-vcode', { show_: show }]" @mousedown="onCloseMouseDown"
      @mouseup="onCloseMouseUp" @touchstart="onCloseMouseDown" @touchend="onCloseMouseUp">
      <div class="vue-auth-box_" @mousedown.stop @touchstart.stop>
        <div class="auth-body_" :style="`height: ${canvasHeight}px`">
          <!-- 主圖,有缺口 -->
          <canvas style="border-radius: 10px" ref="canvas1" :width="canvasWidth" :height="canvasHeight"
            :style="`width:${canvasWidth}px;height:${canvasHeight}px`" />
          <!-- 成功后顯示的完整圖 -->
          <canvas ref="canvas3" :class="['auth-canvas3_', { show: isSuccess }]" :width="canvasWidth"
            :height="canvasHeight" :style="`width:${canvasWidth}px;height:${canvasHeight}px`" />
          <!-- 小圖 -->
          <canvas :width="puzzleBaseSize" class="auth-canvas2_" :height="canvasHeight" ref="canvas2" :style="`width:${puzzleBaseSize}px;height:${canvasHeight}px;transform:translateX(${styleWidth -
            sliderBaseSize -
            (puzzleBaseSize - sliderBaseSize) *
            ((styleWidth - sliderBaseSize) /
              (canvasWidth - sliderBaseSize))}px)`
            " />

          <div :class="['info-box_', { show: infoBoxShow }, { fail: infoBoxFail }]">
            {{ infoText }}
          </div>
          <div :class="['flash_', { show: !isSuccess }]" :style="`transform: translateX(${isSuccess
            ? `${canvasWidth + canvasHeight * 0.578}px`
            : `-${canvasHeight * 0.578}px`
            }) skew(-30deg, 0);`
            "></div>
          <img class="reset_" @click="reset" :src="resetSvg" />
        </div>
        <div class="auth-control_">
          <div class="range-box" :style="`height:${sliderBaseSize}px`">
            <div class="range-text">{{ sliderText }}</div>
            <div class="range-slider" ref="range-slider" :style="`width:${styleWidth}px`">
              <div :class="['range-btn', { isDown: mouseDown }]" :style="`width:${sliderBaseSize}px`"
                @mousedown="onRangeMouseDown($event)" @touchstart="onRangeMouseDown($event)">
                <!-- 按鈕內(nèi)部樣式 -->
                <div></div>
                <div></div>
                <div></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import resetSvg from "@/assets/images/pc/login/Vector.png";
export default {
  props: {
    canvasWidth: { type: Number, default: 350 }, // 主canvas的寬
    canvasHeight: { type: Number, default: 200 }, // 主canvas的高
    // 是否出現(xiàn),由父級控制
    show: { type: Boolean, default: true },
    puzzleScale: { type: Number, default: 1 }, // 拼圖塊的大小縮放比例
    sliderSize: { type: Number, default: 50 }, // 滑塊的大小
    range: { type: Number, default: 10 }, // 允許的偏差值
    // 所有的背景圖片
    imgs: {
      type: Array
    },
    successText: {
      type: String,
      default: "驗(yàn)證通過!"
    },
    failText: {
      type: String,
      default: "驗(yàn)證失敗,請重試"
    },
    sliderText: {
      type: String,
      default: "拖動(dòng)滑塊完成拼圖驗(yàn)證"
    },
    shoWData: {
      type: Boolean,
      default: false
    }
  },

  data() {
    return {
      verSuccess: false,
      isShow: false,
      mouseDown: false, // 鼠標(biāo)是否在按鈕上按下
      startWidth: 50, // 鼠標(biāo)點(diǎn)下去時(shí)父級的width
      startX: 0, // 鼠標(biāo)按下時(shí)的X
      newX: 0, // 鼠標(biāo)當(dāng)前的偏移X
      pinX: 0, // 拼圖的起始X
      pinY: 0, // 拼圖的起始Y
      loading: false, // 是否正在加在中,主要是等圖片onload
      isCanSlide: false, // 是否可以拉動(dòng)滑動(dòng)條
      error: false, // 圖片加在失敗會出現(xiàn)這個(gè),提示用戶手動(dòng)刷新
      infoBoxShow: false, // 提示信息是否出現(xiàn)
      infoText: "", // 提示等信息
      infoBoxFail: false, // 是否驗(yàn)證失敗
      timer1: null, // setTimout1
      closeDown: false, // 為了解決Mac上的click BUG
      isSuccess: false, // 驗(yàn)證成功
      imgIndex: -1, // 用于自定義圖片時(shí)不會隨機(jī)到重復(fù)的圖片
      isSubmting: false, // 是否正在判定,主要用于判定中不能點(diǎn)擊重置按鈕
      resetSvg,
    };
  },
  /** 生命周期 **/
  mounted() {
    // document.body.appendChild(this.$el);
    document.addEventListener("mousemove", this.onRangeMouseMove, { passive: false });
    document.addEventListener("mouseup", this.onRangeMouseUp, { passive: false });
    document.addEventListener("touchmove", this.onRangeMouseMove, { passive: false });
    document.addEventListener("touchend", this.onRangeMouseUp, { passive: false });
    if (this.show) {
      document.body.classList.add("vue-puzzle-overflow");
      this.reset();
    }
    // if (this.shoWData) {
    //   this.isShow = this.shoWData;
    //   console.log('我收到了驗(yàn)證!');
    // }
  },

  beforeDestroy() {
    clearTimeout(this.timer1);
    document.removeEventListener("mousemove", this.onRangeMouseMove, { passive: false });
    document.removeEventListener("mouseup", this.onRangeMouseUp, { passive: false });
    document.removeEventListener("touchmove", this.onRangeMouseMove, { passive: false });
    document.removeEventListener("touchend", this.onRangeMouseUp, { passive: false });
  },

  /** 監(jiān)聽 **/
  watch: {
    show(newV) {
      // 每次出現(xiàn)都應(yīng)該重新初始化
      if (newV) {
        document.body.classList.add("vue-puzzle-overflow");
        this.reset();
      } else {
        this.isSubmting = false;
        this.isSuccess = false;
        this.infoBoxShow = false;
        document.body.classList.remove("vue-puzzle-overflow");
      }
    },
  },

  /** 計(jì)算屬性 **/
  computed: {
    // styleWidth是底部用戶操作的滑塊的父級,就是軌道在鼠標(biāo)的作用下應(yīng)該具有的寬度
    styleWidth() {
      const w = this.startWidth + this.newX - this.startX;
      return w < this.sliderBaseSize
        ? this.sliderBaseSize
        : w > this.canvasWidth
          ? this.canvasWidth
          : w;
    },
    // 圖中拼圖塊的60 * 用戶設(shè)定的縮放比例計(jì)算之后的值 0.2~2
    puzzleBaseSize() {
      return Math.round(
        Math.max(Math.min(this.puzzleScale, 2), 0.2) * 52.5 + 6
      );
    },
    // 處理一下sliderSize,弄成整數(shù),以免計(jì)算有偏差
    sliderBaseSize() {
      return Math.max(
        Math.min(
          Math.round(this.sliderSize),
          Math.round(this.canvasWidth * 0.5)
        ),
        10
      );
    }
  },

  /** 方法 **/
  methods: {
    changeBtn() {
      this.isShow = true;
    },
    // 關(guān)閉
    onClose() {
      if (!this.mouseDown && !this.isSubmting) {
        clearTimeout(this.timer1);
      }
    },
    onCloseMouseDown() {
      this.closeDown = true;
      this.isShow = false;
      this.init(true);
      //給父組件傳一個(gè)狀態(tài)
      this.$emit('submit', 'F')
    },
    onCloseMouseUp() {
      if (this.closeDown) {
        this.onClose();
      }
      this.closeDown = false;
    },
    // 鼠標(biāo)按下準(zhǔn)備拖動(dòng)
    onRangeMouseDown(e) {
      if (this.isCanSlide) {
        this.mouseDown = true;
        this.startWidth = this.$refs["range-slider"].clientWidth;
        this.newX = e.clientX || e.changedTouches[0].clientX;
        this.startX = e.clientX || e.changedTouches[0].clientX;
      }
    },
    // 鼠標(biāo)移動(dòng)
    onRangeMouseMove(e) {
      if (this.mouseDown) {
        // e.preventDefault();
        this.newX = e.clientX || e.changedTouches[0].clientX;
      }
    },
    // 鼠標(biāo)抬起
    onRangeMouseUp() {
      if (this.mouseDown) {
        this.mouseDown = false;
        this.submit();
      }
    },
    /**
     * 開始進(jìn)行
     * @param withCanvas 是否強(qiáng)制使用canvas隨機(jī)作圖
     */
    init(withCanvas) {
      // 防止重復(fù)加載導(dǎo)致的渲染錯(cuò)誤
      if (this.loading && !withCanvas) {
        return;
      }
      this.loading = true;
      this.isCanSlide = false;
      const c = this.$refs.canvas1;
      const c2 = this.$refs.canvas2;
      const c3 = this.$refs.canvas3;
      const ctx = c.getContext("2d", { willReadFrequently: true });
      const ctx2 = c2.getContext("2d", { willReadFrequently: true });
      const ctx3 = c3.getContext("2d", { willReadFrequently: true });
      const isFirefox = navigator.userAgent.indexOf("Firefox") >= 0 && navigator.userAgent.indexOf("Windows") >= 0; // 是windows版火狐
      const img = document.createElement("img");
      ctx.fillStyle = "rgba(255,255,255,1)";
      ctx3.fillStyle = "rgba(255,255,255,1)";
      ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
      ctx2.clearRect(0, 0, this.canvasWidth, this.canvasHeight);

      // 取一個(gè)隨機(jī)坐標(biāo),作為拼圖塊的位置
      this.pinX = this.getRandom(this.puzzleBaseSize, this.canvasWidth - this.puzzleBaseSize - 20); // 留20的邊距
      this.pinY = this.getRandom(20, this.canvasHeight - this.puzzleBaseSize - 20); // 主圖高度 - 拼圖塊自身高度 - 20邊距
      img.crossOrigin = "anonymous"; // 匿名,想要獲取跨域的圖片
      img.onload = () => {
        const [x, y, w, h] = this.makeImgSize(img);
        ctx.save();
        // 先畫小圖
        this.paintBrick(ctx);
        ctx.closePath();
        if (!isFirefox) {
          ctx.shadowOffsetX = 0;
          ctx.shadowOffsetY = 0;
          ctx.shadowColor = "#000";
          ctx.shadowBlur = 0;
          //ctx.globalAlpha = 0.4;
          ctx.fill();
          ctx.clip();
        } else {
          ctx.clip();
          ctx.save();
          ctx.shadowOffsetX = 0;
          ctx.shadowOffsetY = 0;
          ctx.shadowColor = "#000";
          ctx.shadowBlur = 0;
          //ctx.globalAlpha = 0.3;
          ctx.fill();
          ctx.restore();
        }

        ctx.drawImage(img, x, y, w, h);
        ctx3.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
        ctx3.drawImage(img, x, y, w, h);

        // 設(shè)置小圖的內(nèi)陰影
        ctx.globalCompositeOperation = "source-atop";

        this.paintBrick(ctx);

        ctx.arc(
          this.pinX + Math.ceil(this.puzzleBaseSize / 2),
          this.pinY + Math.ceil(this.puzzleBaseSize / 2),
          this.puzzleBaseSize * 1.2,
          0,
          Math.PI * 2,
          true
        );
        ctx.closePath();
        ctx.shadowColor = "rgba(255, 255, 255, .8)";
        ctx.shadowOffsetX = -1;
        ctx.shadowOffsetY = -1;
        ctx.shadowBlur = Math.min(Math.ceil(8 * this.puzzleScale), 12);
        ctx.fillStyle = "#ffffaa";
        ctx.fill();

        // 將小圖賦值給ctx2
        const imgData = ctx.getImageData(
          this.pinX - 3, // 為了陰影 是從-3px開始截取,判定的時(shí)候要+3px
          this.pinY - 20,
          this.pinX + this.puzzleBaseSize + 5,
          this.pinY + this.puzzleBaseSize + 5
        );
        ctx2.putImageData(imgData, 0, this.pinY - 20);

        // ctx2.drawImage(c, this.pinX - 3,this.pinY - 20,this.pinX + this.puzzleBaseSize + 5,this.pinY + this.puzzleBaseSize + 5, 
        // 0, this.pinY - 20, this.pinX + this.puzzleBaseSize + 5, this.pinY + this.puzzleBaseSize + 5);

        // 清理
        ctx.restore();
        ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);

        // 畫缺口
        ctx.save();
        this.paintBrick(ctx);
        ctx.globalAlpha = 1;
        ctx.fillStyle = "#ffffff";
        ctx.fill();
        ctx.restore();

        // 畫缺口的內(nèi)陰影
        ctx.save();
        ctx.globalCompositeOperation = "source-atop";
        this.paintBrick(ctx);
        ctx.arc(
          this.pinX + Math.ceil(this.puzzleBaseSize / 2),
          this.pinY + Math.ceil(this.puzzleBaseSize / 2),
          this.puzzleBaseSize * 1.2,
          0,
          Math.PI * 2,
          true
        );
        ctx.shadowColor = "#ffffff";
        ctx.shadowOffsetX = 2;
        ctx.shadowOffsetY = 2;
        ctx.shadowBlur = 16;
        ctx.fill();
        ctx.restore();

        // 畫整體背景圖
        ctx.save();
        ctx.globalCompositeOperation = "destination-over";
        ctx.drawImage(img, x, y, w, h);
        ctx.restore();

        this.loading = false;
        this.isCanSlide = true;
      };
      img.onerror = () => {
        this.init(true); // 如果圖片加載錯(cuò)誤就重新來,并強(qiáng)制用canvas隨機(jī)作圖
      };

      if (!withCanvas && this.imgs && this.imgs.length) {
        let randomNum = this.getRandom(0, this.imgs.length - 1);
        if (randomNum === this.imgIndex) {
          if (randomNum === this.imgs.length - 1) {
            randomNum = 0;
          } else {
            randomNum++;
          }
        }
        this.imgIndex = randomNum;
        img.src = this.imgs[randomNum];
      } else {
        img.src = this.makeImgWithCanvas();
      }
    },
    // 工具 - 范圍隨機(jī)數(shù)
    getRandom(min, max) {
      return Math.ceil(Math.random() * (max - min) + min);
    },
    // 工具 - 設(shè)置圖片尺寸cover方式貼合canvas尺寸 w/h
    makeImgSize(img) {
      const imgScale = img.width / img.height;
      const canvasScale = this.canvasWidth / this.canvasHeight;
      let x = 0,
        y = 0,
        w = 0,
        h = 0;
      if (imgScale > canvasScale) {
        h = this.canvasHeight;
        w = imgScale * h;
        y = 0;
        x = (this.canvasWidth - w) / 2;
      } else {
        w = this.canvasWidth;
        h = w / imgScale;
        x = 0;
        y = (this.canvasHeight - h) / 2;
      }
      return [x, y, w, h];
    },
    // 繪制拼圖塊的路徑
    paintBrick(ctx) {
      const moveL = Math.ceil(15 * this.puzzleScale); // 直線移動(dòng)的基礎(chǔ)距離
      ctx.beginPath();
      ctx.moveTo(this.pinX, this.pinY);
      ctx.lineTo(this.pinX + moveL, this.pinY);
      ctx.arcTo(
        this.pinX + moveL,
        this.pinY - moveL / 2,
        this.pinX + moveL + moveL / 2,
        this.pinY - moveL / 2,
        moveL / 2
      );
      ctx.arcTo(
        this.pinX + moveL + moveL,
        this.pinY - moveL / 2,
        this.pinX + moveL + moveL,
        this.pinY,
        moveL / 2
      );
      ctx.lineTo(this.pinX + moveL + moveL + moveL, this.pinY);
      ctx.lineTo(this.pinX + moveL + moveL + moveL, this.pinY + moveL);
      ctx.arcTo(
        this.pinX + moveL + moveL + moveL + moveL / 2,
        this.pinY + moveL,
        this.pinX + moveL + moveL + moveL + moveL / 2,
        this.pinY + moveL + moveL / 2,
        moveL / 2
      );
      ctx.arcTo(
        this.pinX + moveL + moveL + moveL + moveL / 2,
        this.pinY + moveL + moveL,
        this.pinX + moveL + moveL + moveL,
        this.pinY + moveL + moveL,
        moveL / 2
      );
      ctx.lineTo(
        this.pinX + moveL + moveL + moveL,
        this.pinY + moveL + moveL + moveL
      );
      ctx.lineTo(this.pinX, this.pinY + moveL + moveL + moveL);
      ctx.lineTo(this.pinX, this.pinY + moveL + moveL);

      ctx.arcTo(
        this.pinX + moveL / 2,
        this.pinY + moveL + moveL,
        this.pinX + moveL / 2,
        this.pinY + moveL + moveL / 2,
        moveL / 2
      );
      ctx.arcTo(
        this.pinX + moveL / 2,
        this.pinY + moveL,
        this.pinX,
        this.pinY + moveL,
        moveL / 2
      );
      ctx.lineTo(this.pinX, this.pinY);
    },
    // 用canvas隨機(jī)生成圖片
    makeImgWithCanvas() {
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d", { willReadFrequently: true });
      canvas.width = this.canvasWidth;
      canvas.height = this.canvasHeight;
      ctx.fillStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
        100,
        255
      )},${this.getRandom(100, 255)})`;
      ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
      // 隨機(jī)畫10個(gè)圖形
      for (let i = 0; i < 12; i++) {
        ctx.fillStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
          100,
          255
        )},${this.getRandom(100, 255)})`;
        ctx.strokeStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
          100,
          255
        )},${this.getRandom(100, 255)})`;

        if (this.getRandom(0, 2) > 1) {
          // 矩形
          ctx.save();
          ctx.rotate((this.getRandom(-90, 90) * Math.PI) / 180);
          ctx.fillRect(
            this.getRandom(-20, canvas.width - 20),
            this.getRandom(-20, canvas.height - 20),
            this.getRandom(10, canvas.width / 2 + 10),
            this.getRandom(10, canvas.height / 2 + 10)
          );
          ctx.restore();
        } else {
          // 圓
          ctx.beginPath();
          const ran = this.getRandom(-Math.PI, Math.PI);
          ctx.arc(
            this.getRandom(0, canvas.width),
            this.getRandom(0, canvas.height),
            this.getRandom(10, canvas.height / 2 + 10),
            ran,
            ran + Math.PI * 1.5
          );
          ctx.closePath();
          ctx.fill();
        }
      }
      return canvas.toDataURL("image/png");
    },
    // 開始判定
    submit() {
      this.isSubmting = true;
      // 偏差 x = puzzle的起始X - (用戶真滑動(dòng)的距離) + (puzzle的寬度 - 滑塊的寬度) * (用戶真滑動(dòng)的距離/canvas總寬度)
      // 最后+ 的是補(bǔ)上slider和滑塊寬度不一致造成的縫隙
      const x = Math.abs(
        this.pinX -
        (this.styleWidth - this.sliderBaseSize) +
        (this.puzzleBaseSize - this.sliderBaseSize) *
        ((this.styleWidth - this.sliderBaseSize) /
          (this.canvasWidth - this.sliderBaseSize)) -
        3
      );
      if (x < this.range) {
        // 成功
        this.infoText = this.successText;
        this.infoBoxFail = false;
        this.infoBoxShow = true;
        this.isCanSlide = false;
        this.isSuccess = false;
        // 成功后準(zhǔn)備關(guān)閉
        clearTimeout(this.timer1);
        this.timer1 = setTimeout(() => {
          // 成功的回調(diào)
          this.isSubmting = false;
          this.isShow = false;
          this.verSuccess = true;
          this.$emit('submit', 'F', this.verSuccess);
          this.reset();
        }, 800);
      } else {
        // 失敗
        this.infoText = this.failText;
        this.infoBoxFail = true;
        this.infoBoxShow = true;
        this.isCanSlide = false;
        // 失敗的回調(diào)
        // this.$emit("fail", x);
        // 800ms后重置
        clearTimeout(this.timer1);
        this.timer1 = setTimeout(() => {
          this.isSubmting = false;
          this.reset();
        }, 800);
      }
    },
    // 重置 - 重新設(shè)置初始狀態(tài)
    resetState() {
      this.infoBoxFail = false;
      this.infoBoxShow = false;
      this.isCanSlide = false;
      this.isSuccess = false;
      this.startWidth = this.sliderBaseSize; // 鼠標(biāo)點(diǎn)下去時(shí)父級的width
      this.startX = 0; // 鼠標(biāo)按下時(shí)的X
      this.newX = 0; // 鼠標(biāo)當(dāng)前的偏移X
    },

    // 重置
    reset() {
      if (this.isSubmting) {
        debugger
        return;
      }
      this.resetState();
      this.init();
    }
  }
};
</script>
<style lang="scss" scoped>
.btn {
  cursor: pointer;
  background-color: #6aa0ff;
  width: 80px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  color: #fff;
}

.vue-puzzle-vcode {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.3);
  z-index: 999;
  opacity: 1;
  pointer-events: none;
  transition: opacity 200ms;

  &.show_ {
    opacity: 1;
    pointer-events: auto;
  }
}

.vue-auth-box_ {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 20px;
  background: #fff;
  user-select: none;
  border-radius: 20px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);


  .auth-body_ {
    position: relative;
    overflow: hidden;
    border-radius: 3px;

    .loading-box_ {
      position: absolute;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      background-color: rgba(0, 0, 0, 0.8);
      z-index: 20;
      opacity: 1;
      transition: opacity 200ms;
      display: flex;
      align-items: center;
      justify-content: center;

      &.hide_ {
        opacity: 0;
        pointer-events: none;

        .loading-gif_ {
          span {
            animation-play-state: paused;
          }
        }
      }

      .loading-gif_ {
        flex: none;
        height: 5px;
        line-height: 0;

        @keyframes load {
          0% {
            opacity: 1;
            transform: scale(1.3);
          }

          100% {
            opacity: 0.2;
            transform: scale(0.3);
          }
        }

        span {
          display: inline-block;
          width: 5px;
          height: 100%;
          margin-left: 2px;
          border-radius: 50%;
          background-color: #888;
          animation: load 1.04s ease infinite;

          &:nth-child(1) {
            margin-left: 0;
          }

          &:nth-child(2) {
            animation-delay: 0.13s;
          }

          &:nth-child(3) {
            animation-delay: 0.26s;
          }

          &:nth-child(4) {
            animation-delay: 0.39s;
          }

          &:nth-child(5) {
            animation-delay: 0.52s;
          }
        }
      }
    }

    .info-box_ {
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
      height: 24px;
      line-height: 24px;
      text-align: center;
      overflow: hidden;
      font-size: 13px;
      background-color: #83ce3f;
      opacity: 0;
      transform: translateY(24px);
      transition: all 200ms;
      color: #fff;
      z-index: 10;

      &.show {
        opacity: 0.95;
        transform: translateY(0);
      }

      &.fail {
        background-color: #ce594b;
      }
    }

    .auth-canvas2_ {
      position: absolute;
      top: 0;
      left: 0;
      width: 60px;
      height: 100%;
      z-index: 2;
    }

    .auth-canvas3_ {
      position: absolute;
      top: 0;
      left: 0;
      opacity: 0;
      transition: opacity 600ms;
      z-index: 3;

      &.show {
        opacity: 1;
      }
    }

    .flash_ {
      position: absolute;
      top: 0;
      left: 0;
      width: 30px;
      height: 100%;
      background-color: rgba(255, 255, 255, 0.1);
      z-index: 3;

      &.show {
        transition: transform 600ms;
      }
    }

    .reset_ {
      position: absolute;
      top: 2px;
      right: 2px;
      width: 35px;
      height: auto;
      z-index: 12;
      cursor: pointer;
      transition: transform 200ms;
      transform: rotate(0deg);

      &:hover {
        transform: rotate(-90deg);
      }
    }
  }

  .auth-control_ {
    .range-box {
      position: relative;
      width: 100%;
      background-color: #eef1f8;
      margin-top: 20px;
      border-radius: 3px;
      // box-shadow: 0 0 8px rgba(240, 240, 240, 0.6) inset;
      box-shadow: inset -2px -2px 4px rgba(50, 130, 251, 0.1), inset 2px 2px 4px rgba(34, 73, 132, 0.2);
      border-radius: 43px;

      .range-text {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-size: 14px;
        color: #b7bcd1;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        text-align: center;
        width: 100%;
        /* 背景顏色線性漸變 */
        /* linear為線性漸變,也可以用下面的那種寫法。left top,right top指的是漸變方向,左上到右上 */
        /* color-stop函數(shù),第一個(gè)表示漸變的位置,0為起點(diǎn),0.5為中點(diǎn),1為結(jié)束點(diǎn);第二個(gè)表示該點(diǎn)的顏色。所以本次漸變?yōu)閮蛇吇疑虚g漸白色 */
        background: -webkit-gradient(linear, left top, right top, color-stop(0, #4d4d4d), color-stop(.4, #4d4d4d), color-stop(.5, white), color-stop(.6, #4d4d4d), color-stop(1, #4d4d4d));

        /* 設(shè)置為text,意思是把文本內(nèi)容之外的背景給裁剪掉 */
        -webkit-background-clip: text;
        /* 設(shè)置對象中的文字填充顏色 這里設(shè)置為透明 */
        -webkit-text-fill-color: transparent;
        /* 每隔2秒調(diào)用下面的CSS3動(dòng)畫 infinite屬性為循環(huán)執(zhí)行animate */
        -webkit-animation: animate 1.5s infinite;

      }

      /* 兼容寫法,要放在@keyframes前面 */
      @-webkit-keyframes animate {

        /* 背景從-100px的水平位置,移動(dòng)到+100px的水平位置。如果要移動(dòng)Y軸的,設(shè)置第二個(gè)數(shù)值 */
        from {
          background-position: -100px;
        }

        to {
          background-position: 100px;
        }
      }

      @keyframes animate {
        from {
          background-position: -100px;
        }

        to {
          background-position: 100px;
        }
      }

      .range-slider {
        position: absolute;
        height: 100%;
        width: 50px;
        /**background-color: rgba(106, 160, 255, 0.8);*/
        border-radius: 3px;

        .range-btn {
          position: absolute;
          display: flex;
          align-items: center;
          justify-content: center;
          right: 0;
          width: 50px;
          height: 100%;
          background-color: #fff;
          border-radius: 3px;
          /** box-shadow: 0 0 4px #ccc;*/
          cursor: pointer;
          box-shadow: inset 0px -2px 4px rgba(0, 36, 90, 0.2), inset 0px 2px 4px rgba(194, 219, 255, 0.8);
          border-radius: 50%;

          &>div {
            width: 0;
            height: 40%;

            transition: all 200ms;

            &:nth-child(2) {
              margin: 0 4px;
            }

            border: solid 1px #6aa0ff;
          }

          &:hover,
          &.isDown {
            &>div:first-child {
              border: solid 4px transparent;
              height: 0;
              border-right-color: #6aa0ff;
            }

            &>div:nth-child(2) {
              border-width: 3px;
              height: 0;
              border-radius: 3px;
              margin: 0 6px;
              border-right-color: #6aa0ff;
            }

            &>div:nth-child(3) {
              border: solid 4px transparent;
              height: 0;
              border-left-color: #6aa0ff;
            }
          }
        }
      }
    }
  }
}

.vue-puzzle-overflow {
  overflow: hidden !important;
}
</style>

總結(jié) 

到此這篇關(guān)于vue項(xiàng)目登錄模塊滑塊拼圖驗(yàn)證功能(純前端)的文章就介紹到這了,更多相關(guān)vue登錄模塊滑塊拼圖驗(yàn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue v-for中:key中item.id與Index使用的區(qū)別解析

    Vue v-for中:key中item.id與Index使用的區(qū)別解析

    這篇文章主要介紹了Vue v-for中:key中item.id與Index使用的區(qū)別解析,推薦使用【:key="item.id"】而不是將數(shù)組下標(biāo)當(dāng)做唯一標(biāo)識,前者能做到全部復(fù)用,本文給大家詳細(xì)講解,感興趣的朋友跟隨小編一起看看吧
    2024-02-02
  • vue.js模仿京東省市區(qū)三級聯(lián)動(dòng)的選擇組件實(shí)例代碼

    vue.js模仿京東省市區(qū)三級聯(lián)動(dòng)的選擇組件實(shí)例代碼

    選擇省市區(qū)是我們大家在填寫地址的時(shí)候經(jīng)常會遇到的一個(gè)功能,下面這篇文章主要給大家介紹了關(guān)于利用vue.js模仿實(shí)現(xiàn)京東省市區(qū)三級聯(lián)動(dòng)選擇組件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-11-11
  • 解決vue里a標(biāo)簽值解析變量,跳轉(zhuǎn)頁面,前面加默認(rèn)域名端口的問題

    解決vue里a標(biāo)簽值解析變量,跳轉(zhuǎn)頁面,前面加默認(rèn)域名端口的問題

    這篇文章主要介紹了解決vue里a標(biāo)簽值解析變量,跳轉(zhuǎn)頁面,前面加默認(rèn)域名端口的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-07-07
  • Vue項(xiàng)目中封裝組件的簡單步驟記錄

    Vue項(xiàng)目中封裝組件的簡單步驟記錄

    眾所周知組件(component)是vue.js最強(qiáng)大的功能之一,它可以實(shí)現(xiàn)功能的復(fù)用,以及對其他邏輯的解耦,下面這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目中封裝組件的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • vue?+?element-plus自定義表單驗(yàn)證(修改密碼業(yè)務(wù))的示例

    vue?+?element-plus自定義表單驗(yàn)證(修改密碼業(yè)務(wù))的示例

    這篇文章主要介紹了vue?+?element-plus自定義表單驗(yàn)證(修改密碼業(yè)務(wù)),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2025-04-04
  • Vue Object 的變化偵測實(shí)現(xiàn)代碼

    Vue Object 的變化偵測實(shí)現(xiàn)代碼

    這篇文章主要介紹了Vue Object的變化偵測實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • el-tree的實(shí)現(xiàn)葉子節(jié)點(diǎn)單選的示例代碼

    el-tree的實(shí)現(xiàn)葉子節(jié)點(diǎn)單選的示例代碼

    本文主要介紹了el-tree的實(shí)現(xiàn)葉子節(jié)點(diǎn)單選的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 關(guān)于Vue.nextTick()的正確使用方法淺析

    關(guān)于Vue.nextTick()的正確使用方法淺析

    最近在項(xiàng)目中遇到了一個(gè)需求,我們通過Vue.nextTick()來解決這一需求,但發(fā)現(xiàn)網(wǎng)上這方面的資料較少,所以自己來總結(jié)下,下面這篇文章主要給大家介紹了關(guān)于Vue.nextTick()正確使用方法的相關(guān)資料,需要的朋友可以參考下。
    2017-08-08
  • 前端Vue項(xiàng)目部署到服務(wù)器的全過程以及踩坑記錄

    前端Vue項(xiàng)目部署到服務(wù)器的全過程以及踩坑記錄

    使用Vue做前后端分離項(xiàng)目時(shí),通常前端是單獨(dú)部署,用戶訪問的也是前端項(xiàng)目地址,因此前端開發(fā)人員很有必要熟悉一下項(xiàng)目部署的流程,下面這篇文章主要給大家介紹了關(guān)于前端Vue項(xiàng)目部署到服務(wù)器的全過程以及踩坑記錄的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • vue回到頂部監(jiān)聽滾動(dòng)事件詳解

    vue回到頂部監(jiān)聽滾動(dòng)事件詳解

    這篇文章主要為大家詳細(xì)介紹了vue回到頂部監(jiān)聽滾動(dòng)事件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08

最新評論