vue2滑块验证

发布于:2025-09-03 ⋅ 阅读:(19) ⋅ 点赞:(0)

纯 Vue 2 实现的滑块拖动验证组件

效果说明

  • 拖动滑块到最右侧判定为验证成功

  • 支持自定义宽度、高度、颜色、提示文字

  • 可扩展轨迹分析或后端验证逻辑

Vue 2 滑块验证组件代码

SliderVerify.vue

注意:icon图标使用的是Element ui图标

<template>
  <div class="slider-container" :style="{ width: width + 'px', height: height + 'px' }">
    <div class="slider-track" :style="{ backgroundColor: trackColor, width: trackWidth + 'px' }"></div>
    <div
      class="slider-button"
      :style="{ left: buttonLeft + 'px', backgroundColor: buttonColor }"
      @mousedown="startDrag"
    >
      <span v-if="!isPassed"> <i class="el-icon-d-arrow-right"></i></span>
      <span v-else><i class="el-icon-check"></i></span>
    </div>
    <div class="slider-text">{{ isPassed ? successText : text }}</div>
  </div>
</template>

<script>
export default {
  name: 'SliderVerify',
  props: {
    width: { type: Number, default: 300 },
    height: { type: Number, default: 40 },
    text: { type: String, default: '请将滑块拖动到右侧' },
    successText: { type: String, default: '验证成功' },
    trackColor: { type: String, default: '#4caf50' },
    buttonColor: { type: String, default: '#fff' }
  },
  data() {
    return {
      isDragging: false,
      startX: 0,
      buttonLeft: 0,
      trackWidth: 0,
      isPassed: false
    }
  },
  methods: {
    startDrag(e) {
      if (this.isPassed) return
      this.isDragging = true
      this.startX = e.clientX - this.buttonLeft
      document.addEventListener('mousemove', this.onDrag)
      document.addEventListener('mouseup', this.endDrag)
    },
    onDrag(e) {
      if (!this.isDragging) return
      let moveX = e.clientX - this.startX
      const maxX = this.width - this.height
      moveX = Math.max(0, Math.min(moveX, maxX))
      this.buttonLeft = moveX
      this.trackWidth = moveX + this.height
    },
    endDrag() {
      this.isDragging = false
      document.removeEventListener('mousemove', this.onDrag)
      document.removeEventListener('mouseup', this.endDrag)

      const maxX = this.width - this.height
      if (this.buttonLeft >= maxX - 5) {
        this.isPassed = true
        this.$emit('pass')
      } else {
        this.buttonLeft = 0
        this.trackWidth = 0
      }
    }
  }
}
</script>

<style scoped>
.slider-container {
  position: relative;
  background: #d3d3d3;
  border-radius: 4px;
  user-select: none;
}
.slider-track {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  transition: width 0.2s;
  border-radius: 4px;
}
.slider-button {
  position: absolute;
  top: 0;
  width: 40px;
  height: 100%;
  text-align: center;
  line-height: 40px;
  font-size: 18px;
  cursor: pointer;
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
  z-index: 2;
}
.slider-text {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  line-height: 40px;
  text-align: center;
  font-size: 14px;
  color: #666;
  z-index: 1;
}
</style>

使用方式

在父组件中:

<SliderVerify @pass="handleVerifySuccess" />
<script>
import SliderVerify from "@/components/Login/SliderVerify.vue";
export default {
  components: {
    SliderVerify
  },
  data() {
    return {
    };
  },
  methods: {
    handleVerifySuccess() {
    console.log('验证通过!')
    // 可执行登录、提交等操作
  }
  },
};
</script>

可扩展方向(以下内容仅提供思路)

  • ✅ 加入拖动轨迹分析(防机器人)

  • ✅ 拖动完成后调用后端接口验证

  • ✅ 添加“重置”按钮或自动重置逻辑

  • ✅ 支持移动端触摸事件(touchstart, touchmove, touchend

1. 加入拖动轨迹分析(防机器人)

目的:判断用户是否是人类,通过拖动轨迹的“自然性”来识别。

实现思路:
  • onDrag 方法中记录每一次拖动的时间戳和位置:

    data() {
      return {
        dragTrace: [] // [{x: 123, time: 123456789}]
      }
    },
    methods: {
      onDrag(e) {
        const now = Date.now()
        this.dragTrace.push({ x: e.clientX, time: now })
        // 原有拖动逻辑...
      }
    }
    
  • 拖动完成后分析轨迹:

    endDrag() {
      // 轨迹分析:速度是否过快、是否有停顿、是否线性过于完美
      const isHumanLike = this.analyzeTrace(this.dragTrace)
      if (!isHumanLike) {
        alert('行为异常,请重试')
        this.resetSlider()
        return
      }
      // 原有验证逻辑...
    },
    analyzeTrace(trace) {
      if (trace.length < 5) return false
      const speeds = []
      for (let i = 1; i < trace.length; i++) {
        const dx = Math.abs(trace[i].x - trace[i - 1].x)
        const dt = trace[i].time - trace[i - 1].time
        speeds.push(dx / dt)
      }
      const avgSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length
      return avgSpeed < 2 && speeds.some(s => s < 0.5) // 有停顿、有波动
    }
    

2. 拖动完成后调用后端接口验证

目的:让后端参与验证,提升安全性。

实现方式:
  • endDrag 中加入 API 请求:
    async endDrag() {
      const maxX = this.width - this.height
      if (this.buttonLeft >= maxX - 5) {
        try {
          const res = await fetch('/api/verify-slider', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ trace: this.dragTrace })
          })
          const result = await res.json()
          if (result.success) {
            this.isPassed = true
            this.$emit('pass')
          } else {
            alert('验证失败,请重试')
            this.resetSlider()
          }
        } catch (err) {
          console.error('验证接口异常', err)
          this.resetSlider()
        }
      } else {
        this.resetSlider()
      }
    }
    

3. 添加“重置”按钮或自动重置逻辑

目的:用户拖错了可以重新尝试。

实现方式:
  • 添加按钮:

    <button v-if="!isPassed" @click="resetSlider" class="reset-btn">重置</button>
    
  • 方法定义:

    methods: {
      resetSlider() {
        this.buttonLeft = 0
        this.trackWidth = 0
        this.dragTrace = []
        this.isPassed = false
      }
    }
    
  • 自动重置(比如 3 秒后):

    if (!this.isPassed) {
      setTimeout(() => this.resetSlider(), 3000)
    }
    

4. 支持移动端触摸事件(touchstart, touchmove, touchend

目的:让组件在手机上也能正常使用。

实现方式:
  • 添加事件监听:

    <div
      class="slider-button"
      @mousedown="startDrag"
      @touchstart="startTouch"
      @touchmove="onTouchMove"
      @touchend="endTouch"
    >
    
  • 方法定义:

    methods: {
      startTouch(e) {
        this.isDragging = true
        this.startX = e.touches[0].clientX - this.buttonLeft
      },
      onTouchMove(e) {
        if (!this.isDragging) return
        const moveX = e.touches[0].clientX - this.startX
        const maxX = this.width - this.height
        this.buttonLeft = Math.max(0, Math.min(moveX, maxX))
        this.trackWidth = this.buttonLeft + this.height
        this.dragTrace.push({ x: e.touches[0].clientX, time: Date.now() })
      },
      endTouch() {
        this.isDragging = false
        this.endDrag() // 复用原有逻辑
      }
    }

网站公告

今日签到

点亮在社区的每一天
去签到