vue3项目移动端实现进度条可手动滑动控制进度和点击控制进度

最终效果

完整代码:

css 复制代码
<template>
    <div class="slider-container">
      <div class="slider-track" ref="trackRef">
        <div 
          class="slider-thumb" 
          ref="thumbRef"
          @touchstart="handleTouchStart"
          @touchmove="handleTouchMove"
          @touchend="handleTouchEnd"
          :style="{ left: thumbPosition }"
        ></div>
        <div class="slider-progress" :style="{ width: progressWidth }"></div>
        <div 
          v-for="n in 10" 
          :key="n" 
          class="slider-mark"
          :style="{ left: `${(n - 1) * 100 / 9}%` }"
        ></div>
        <!-- 添加单独的点击区域 -->
        <div 
          v-for="n in 10" 
          :key="'click-' + n" 
          class="slider-click-area"
          :style="{ left: `${(n - 1) * 100 / 9}%` }"
          @click="jumpToValue(n)"
          @touchstart="jumpToValue(n)"
        ></div>
        <div 
          v-for="n in 10" 
          :key="'label-' + n" 
          class="slider-label"
          :style="{ left: `${(n - 1) * 100 / 9}%` }"
        >
          {{ n }}
        </div>
      </div>
      <div class="slider-value">当前值: {{ currentValue }}</div>
    </div>
  </template>
  
  <script lang="ts" setup>
  import { ref, computed } from 'vue'
  
  const trackRef = ref<HTMLDivElement | null>(null)
  const thumbRef = ref<HTMLDivElement | null>(null)
  const currentValue = ref(1)
  const isDragging = ref(false)
  const startX = ref(0)
  const thumbStartLeft = ref(0)
  
  const progressWidth = computed(() => {
    return `${(currentValue.value - 1) * 100 / 9}%`
  })
  
  const thumbPosition = computed(() => {
    return `${(currentValue.value - 1) * 100 / 9}%`
  })
  
  function handleTouchStart(e: TouchEvent) {
    isDragging.value = true
    startX.value = e.touches[0].clientX
    const thumbRect = thumbRef.value?.getBoundingClientRect()
    thumbStartLeft.value = thumbRect?.left || 0
    e.preventDefault()
  }
  
  function handleTouchMove(e: TouchEvent) {
    if (!isDragging.value) return
    
    const trackRect = trackRef.value?.getBoundingClientRect()
    const trackWidth = trackRect?.width || 0
    
    let moveX = e.touches[0].clientX - startX.value
    let newLeft = thumbStartLeft.value + moveX - (trackRect?.left || 0)
    
    // 限制在轨道范围内
    newLeft = Math.max(0, Math.min(newLeft, trackWidth))
    
    // 计算最接近的刻度位置
    const percent = newLeft / trackWidth
    const closestValue = Math.round(percent * 9) + 1
    
    // 只有当移动距离足够大时才更新值,避免抖动
    if (Math.abs(percent - (currentValue.value - 1)/9) > 0.03) {
      currentValue.value = Math.max(1, Math.min(10, closestValue))
    }
    
    e.preventDefault()
  }
  
  function handleTouchEnd() {
    // 滑动结束时确保对准最近的刻度
    currentValue.value = Math.round(currentValue.value)
    isDragging.value = false
  }
  
  function jumpToValue(value: number) {
    currentValue.value = value;
  }
  </script>
  
  <style scoped>
  .slider-container {
    /* padding: 20px; */
    width: 85%;
    max-width: 500px;
    margin: 0 auto;
  }
  
  .slider-track {
    position: relative;
    height: 4px;
    background-color: #e0e0e0;
    border-radius: 2px;
    margin: 30px 0 40px;
  }
  
  .slider-progress {
    position: absolute;
    height: 100%;
    background-color: #42b983;
    border-radius: 2px;
    pointer-events: none;
  }
  
  .slider-thumb {
    position: absolute;
    width: 24px;
    height: 24px;
    background-color: #42b983;
    border-radius: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    cursor: pointer;
    z-index: 4; /* 确保在最上层 */
    touch-action: none;
  }
  
  .slider-mark {
    position: absolute;
    width: 8px;
    height: 8px;
    background-color: #fff;
    border: 2px solid #e0e0e0;
    border-radius: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
    z-index: 1;
  }
  
  .slider-label {
    position: absolute;
    top: 20px;
    transform: translateX(-50%);
    font-size: 12px;
    color: #666;
    pointer-events: none; /* 标签本身不接受事件 */
    z-index: 2;
    user-select: none;
  }
  
  /* 新增的点击区域 */
  .slider-click-area {
    position: absolute;
    width: 30px;
    height: 40px;
    top: -10px;
    transform: translateX(-50%);
    z-index: 3; /* 在标签和滑块之间 */
    cursor: pointer;
  }
  </style>

使用:

html 复制代码
<NumberSlider />

import NumberSlider from '@/components/m/numberSlider.vue';