DoubleSlider双向滑块
实现了一个双滑块选择器。用户可以通过拖动左右两个滑块来选择一个区间,也可以点击选择器的条来整体移动所选区间。(后续会扩展更多功能如:鼠标直接选中某一区间)
技术栈:vue3、js
主要代码
DoubleSlider.vue
<template>
<div class="a_slider">
<div
class="a_slider_bar"
ref="sliderBarRef"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="clickBar"
></div>
<div
class="a_min_bar"
:style="{
left: props.modelValue.left + '%',
width: props.modelValue.width + '%',
}"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="clickBar"
></div>
<div
class="a_slider__button"
v-diy-drag
:style="{ left: props.modelValue.left + '%' }"
id="dragButtonLeft"
ref="dragButtonLeftRef"
></div>
<div
class="a_slider__button"
v-diy-drag
:style="{
left: props.modelValue.left + props.modelValue.width + '%',
}"
id="dragButtonRight"
ref="dragButtonRightRef"
></div>
</div>
</template>
<script setup>
import { reactive, onMounted, onBeforeUnmount, ref } from "vue";
const emits = defineEmits(["update:modelValue", "change"]);
const sliderBarRef = ref();
const dragButtonLeftRef = ref();
const dragButtonRightRef = ref();
let drag = ref(false);
const handleMouseDown = () => (drag.value = false);
const handleMouseMove = () => (drag.value = true);
const vDiyDrag = {
beforeMount(el) {
const onMouseDown = (e) => {
const id = e.target.id;
if (id !== "dragButtonLeft" && id !== "dragButtonRight") {
return;
}
const initialMouseX = e.clientX;
const initialDomX = el.offsetLeft;
const clientWidth = sliderBarRef.value.clientWidth;
let minX = 0,
maxX = 0;
// 按钮宽度占比
const buttonWidth = (6 / clientWidth) * 100;
const dragButtonLeft = {
minX: 0,
maxX:
parseFloat(dragButtonRightRef.value.style.left) -
buttonWidth,
};
const dragButtonRight = {
minX:
parseFloat(dragButtonLeftRef.value.style.left) +
buttonWidth,
maxX: 100,
};
if (id === "dragButtonLeft") {
minX = dragButtonLeft.minX;
maxX = dragButtonLeft.maxX;
} else {
minX = dragButtonRight.minX;
maxX = dragButtonRight.maxX;
}
const onMouseMove = (e) => {
const deltaX = e.clientX - initialMouseX;
let newX = ((initialDomX + deltaX) / clientWidth) * 100;
// 边界控制
if (newX < minX) newX = minX;
else if (newX > maxX) newX = maxX;
el.style.left = newX + "%";
change();
};
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return false;
};
el.addEventListener("mousedown", onMouseDown);
onBeforeUnmount(() => {
el.removeEventListener("mousedown", onMouseDown);
});
},
};
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {
left: 0,
width: 100,
};
},
},
});
const state = reactive({});
onMounted(() => {});
onBeforeUnmount(() => {});
function clickBar(e) {
console.log(drag.value);
const clientWidth = sliderBarRef.value.clientWidth;
const offsetX = (e.offsetX / clientWidth) * 100;
const rightButton = parseFloat(dragButtonRightRef.value.style.left);
const leftButton = parseFloat(dragButtonLeftRef.value.style.left);
const middle = (rightButton + leftButton) / 2;
let diff = offsetX - middle;
if (diff > 0) {
if (rightButton + diff > 100) {
diff = 100 - rightButton;
}
// 右边按钮
dragButtonRightRef.value.style.left = rightButton + diff + "%";
// 左边按钮
dragButtonLeftRef.value.style.left = leftButton + diff + "%";
} else if (diff < 0) {
if (leftButton + diff < 0) {
diff = 0 - leftButton;
}
// 左边按钮
dragButtonLeftRef.value.style.left = leftButton + diff + "%";
// 右边按钮
dragButtonRightRef.value.style.left = rightButton + diff + "%";
}
change();
}
function change() {
let newValue = {
left: parseFloat(dragButtonLeftRef.value.style.left),
width:
parseFloat(dragButtonRightRef.value.style.left) -
parseFloat(dragButtonLeftRef.value.style.left),
};
emits("update:modelValue", newValue);
emits("change", newValue);
}
</script>
<style lang="less" scoped>
.a_slider {
position: relative;
width: 100%;
.a_slider_bar {
// position: absolute;
width: 100%;
height: 8px;
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.4);
cursor: crosshair;
}
.a_min_bar {
pointer-events: none;
position: absolute;
top: 5px;
width: 100%;
height: 12px;
background: rgba(16, 29, 47, 1);
border: 1px solid rgba(255, 255, 255, 0.4);
transform: translateY(-50%);
}
.a_slider__button {
position: absolute;
top: 5px;
width: 6px;
height: 18px;
background: rgba(0, 38, 101, 1);
border-radius: 2px;
cursor: ew-resize;
box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 1);
transform: translateY(-50%);
}
.a_slider__button:hover {
background: rgba(54, 124, 232, 1);
}
}
</style>
效果图:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)