vue3项目移动端实现进度条可手动滑动控制进度和点击控制进度
最终效果
完整代码:
<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 = truestartX.value = e.touches[0].clientXconst thumbRect = thumbRef.value?.getBoundingClientRect()thumbStartLeft.value = thumbRect?.left || 0e.preventDefault()}function handleTouchMove(e: TouchEvent) {if (!isDragging.value) returnconst trackRect = trackRef.value?.getBoundingClientRect()const trackWidth = trackRect?.width || 0let moveX = e.touches[0].clientX - startX.valuelet newLeft = thumbStartLeft.value + moveX - (trackRect?.left || 0)// 限制在轨道范围内newLeft = Math.max(0, Math.min(newLeft, trackWidth))// 计算最接近的刻度位置const percent = newLeft / trackWidthconst 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>
使用:
<NumberSlider />import NumberSlider from '@/components/m/numberSlider.vue';