由于项目需求,需要自定义滑块组件,支持以下功能:
支持鼠标和触摸操作
支持拖拽控制
支持步长设置
支持禁用状态
响应式更新
支持事件触发(startDrag、input、change、stopDrag)
以下为示例:
template部分:
html
<template>
<!-- 滑块容器,绑定鼠标按下和触摸开始事件 -->
<div ref="sliderRef" class="slider" @mousedown="startDrag" @touchstart="startDrag">
<!-- 滑块轨道,根据滑块位置动态设置宽度 -->
<div class="slider-track" :style="{ width: trackWidth + '%' }"></div>
<!-- 滑块按钮,显示当前值的位置,拖拽时添加特殊样式 -->
<div
ref="thumb"
class="slider-thumb"
:class="{ 'is-dragging': state.dragging }"
:style="{ left: state.thumbLeft + '%' }"
></div>
</div>
</template>
js部分:
javascript
<script setup>
import { ref, reactive, watch, computed } from 'vue'
// 滑块容器的引用,用于获取DOM元素的位置信息
const sliderRef = ref(null)
// 定义组件属性
const props = defineProps({
// 当前滑块的值
value: {
type: Number,
default: 0,
},
// 滑块最小值
min: {
type: Number,
default: 0,
},
// 滑块最大值
max: {
type: Number,
default: 100,
},
// 步长,每次移动的最小单位
step: {
type: Number,
default: 1,
},
// 是否禁用滑块
disabled: {
type: Boolean,
default: false,
},
})
// 计算轨道宽度,等于滑块按钮的左边距百分比
const trackWidth = computed(() => {
return state.thumbLeft
})
// 根据props.value计算滑块按钮应该在的左边距百分比
const calculateThumbLeft = computed(() => {
// 将value限制在min和max范围内
const clampedValue = Math.max(props.min, Math.min(props.value, props.max))
// 将值转换为百分比位置
return ((clampedValue - props.min) / (props.max - props.min)) * 100
})
// 滑块组件的状态
const state = reactive({
dragging: false, // 是否正在拖拽
thumbLeft: calculateThumbLeft.value, // 滑块按钮的左边距百分比
})
// 定义组件触发的事件
const emit = defineEmits(['startDrag', 'input', 'change', 'stopDrag'])
// 监听props.value的变化,更新滑块位置
watch(
() => props.value,
(newVal, oldVal) => {
if (newVal !== oldVal) {
state.thumbLeft = calculateThumbLeft.value
}
},
)
/**
* 根据滑块位置计算对应的值
* @param {number} thumbLeft - 滑块的左边距百分比
* @returns {number} 对应的值
*/
const calculateValue = (thumbLeft) => {
// 将百分比转换为实际值
const value = props.min + (thumbLeft / 100) * (props.max - props.min)
// 按照步长取整
const stepValue = Math.round(value / props.step) * props.step
// 将值限制在min和max范围内
return Math.max(props.min, Math.min(stepValue, props.max))
}
/**
* 开始拖拽事件处理函数
* @param {Event} event - 鼠标按下或触摸开始事件
*/
const startDrag = (event) => {
// 如果滑块被禁用,则不执行拖拽操作
if (props.disabled) return
// 设置拖拽状态为true
state.dragging = true
// 添加鼠标移动和鼠标松开事件监听器
document.addEventListener('mousemove', onDrag)
document.addEventListener('mouseup', stopDrag)
// 添加触摸移动和触摸结束事件监听器(移动端支持)
document.addEventListener('touchmove', onDrag)
document.addEventListener('touchend', stopDrag)
// 阻止默认行为,防止选中文本
event.preventDefault()
// 触发startDrag事件
emit('startDrag')
}
/**
* 拖拽过程中的事件处理函数
* @param {Event} event - 鼠标移动或触摸移动事件
*/
const onDrag = (event) => {
// 如果没有在拖拽状态,则直接返回
if (!state.dragging) return
if (sliderRef.value) {
// 获取滑块容器的位置和尺寸信息
const sliderRect = sliderRef.value.getBoundingClientRect()
// 获取鼠标或触摸点的X坐标
const clientX = event.clientX || event.touches[0].clientX
// 计算滑块按钮的新位置(百分比)
let newLeft = ((clientX - sliderRect.left) / sliderRect.width) * 100
// 将位置限制在0%到100%之间
newLeft = Math.max(0, Math.min(newLeft, 100))
// 更新滑块按钮的位置
state.thumbLeft = newLeft
// 根据新位置计算对应的值并触发input事件
const newValue = calculateValue(newLeft)
emit('input', newValue)
}
}
/**
* 停止拖拽事件处理函数
*/
const stopDrag = () => {
// 设置拖拽状态为false
state.dragging = false
// 移除事件监听器
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag)
document.removeEventListener('touchmove', onDrag)
document.removeEventListener('touchend', stopDrag)
// 触发change事件,传递最终值
emit('change', calculateValue(state.thumbLeft))
// 触发stopDrag事件
emit('stopDrag')
}
</script>
css部分:
css
<style scoped>
/* 滑块容器基础样式 */
.slider {
position: relative;
width: 200px;
height: 3px;
background: rgba(0, 0, 0, 0.3);
user-select: none;
opacity: 1;
transition: opacity 0.3s;
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.3);
border-radius: 2px;
}
/* 禁用状态下的滑块样式 */
.slider.disabled {
opacity: 0.5;
}
/* 滑块轨道样式 */
.slider-track {
position: absolute;
height: 3px;
background: rgba(0, 0, 0, 0.3);
border-radius: 2px;
}
/* 滑块按钮样式 */
.slider-thumb {
position: absolute;
top: -6.5px;
width: 16px;
height: 16px;
background: rgb(255, 0, 0);
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.3);
border-radius: 50%;
user-select: none;
transform: translateX(-50%);
transition:
width 0.1s ease,
height 0.1s ease,
top 0.1s ease,
box-shadow 0.1s ease;
/* 拖拽状态下的样式 */
&.is-dragging {
box-shadow: 0 0 0 4px rgba(255, 0, 0, 0.3);
}
}
</style>
最终效果:
(初始状态)

(拖动状态)

以上部分也可以根据实际需求进行修改。