滑块组件代码:
<template>
<div class="sliderBox">
<div class="signBox flex align-center">
<div class="sign hasmove flex align-center space-between column" :style="{left: sliderLeft + 'px'}">
<div>{{ sliderVal }}</div>
<div class="circular" @mousedown.prevent="handleMousedown($event, sliderVal)"></div>
</div>
</div>
<div class="lineBox flex align-center">
<div class="line flex-1" v-for="item in data.length - 1" :key="item"></div>
</div>
<div class="scaleBox flex space-between align-center">
<div class="scale" v-for="item in data" :key="item">{{ item }}</div>
</div>
</div>
</template>
<script lang="ts" setup name="Slider">
import { ref, watch } from 'vue'
const emit = defineEmits();
const data = [200, 1000, 2000, 3000, 4000, 14000, 26000]
const props = defineProps({
modelValue: {
type: Number,
default: ''
}
})
const sliderLeft = ref(0)
const sliderVal = ref(props.modelValue)
// 根据值计算当前值的位置
const handlePosition = (val) => {
let left = 0
for (let i = 0; i < data.length; i++) {
if (val >= data[i]) {
if (val >= data[i + 1]) left += 100
if (val < data[i + 1]) {
const num = val - data[i]
left += num / (data[i + 1] - data[i]) * 100
}
}
}
return left
}
sliderLeft.value = handlePosition(sliderVal.value)
// 根据鼠标移动距离获取对应数值
const handleGetVal = (num) => {
let val = 0
const step = Math.floor(num / 100)
const range = data.slice(step, step + 2)
val = num === 600 ? 26000 : range[0] + (range[1] - range[0]) * (num % 100 / 100)
return val
}
watch(() => props.modelValue, (newValue, oldValue) => {
sliderVal.value = newValue
sliderLeft.value = handlePosition(newValue)
}, { immediate: true })
let startX = 0
let left = 0
const containerWidth = 600
// 鼠标按下
const handleMousedown = (event, num) => {
startX = event.clientX;
left = sliderLeft.value;
document.addEventListener('mousemove', handleDragstart);
document.addEventListener('mouseup', handleDragend);
}
// 鼠标开始移动
const handleDragstart = (event) => {
const diffX = event.clientX - startX;
sliderLeft.value = Math.min(Math.max(0, left + diffX), containerWidth);
sliderVal.value = handleGetVal(sliderLeft.value)
}
// 鼠标移动结束
const handleDragend = (event) => {
document.removeEventListener('mousemove', handleDragstart);
document.removeEventListener('mouseup', handleDragend);
emit('update:modelValue', sliderVal.value)
}
</script>
<style lang="scss" scoped>
@import "@/styles/theme.scss";
.sliderBox {
padding: 40px 120px;
width: 880px;
box-sizing: border-box;
user-select: none;
.lineBox {
width: 600px;
border-radius: 12px;
margin: 0 auto;
background: #090D0F;
.line {
height: 12px;
}
}
.scaleBox {
margin-top: 20px;
.scale {
width: 40px;
text-align: center;
}
}
.signBox {
width: 600px;
height: 60px;
position: relative;
left: 10px;
.hasmove {
font-weight: 600;
z-index: 99;
}
.sign {
position: absolute;
top: 15px;
height: 60px;
width: 18px;
color: $primary-color;
cursor: pointer;
.circular {
width: 18px;
height: 28px;
border-radius: 18px;
background: $primary-color;
}
}
}
}
</style>
父组件中使用:
<template>
<Slider v-model="sliderNum" />
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import Slider from "@/components/Slider/index.vue";
const sliderNum = ref(2000);
// 测试
watch(() => sliderNum.value, (newValue, oldValue) => {
console.log('sliderNum', newValue, oldValue);
})
setTimeout(() => {
sliderNum.value = 4000
}, 2000)
</script>
<style scoped lang="scss">
</style>
实现效果: