参考:
- Antdv Slider 滑动输入条
- Element Plus Progress 进度条
开发时遇到一个需求,一个进度条控制多个视频播放器。正常使用一些组件库自带的组件就好了——antdv
的slider
但是使用change
事件的话,使用拖拽进度点改变进度条value
就会频繁触发,所以考虑使用afterChange
- afterChange:与
mouseup
触发时机一致,把当前值作为参数传入。
该API纯在如下bug:
该事件触发后,鼠标点击其他区域,会再次触发该事件。gitHub也有这个issue,但是^3
和^4
版本都没有进行修复。于是自己封装了一个简易的进度条,相比组件库中的灵活性更高。代码放在最下面。
效果图
进度条与视频
-
对于进度条的时间显示,不能使用计时器控制,而是应该获取视频的当前时间回显到进度条中,不然时间是无法对齐的,总会存在误差。
-
js的浮点数计算不准确,视频时间的精度是小数点后很多位,不能使用计时器累加
组件代码
<template>
<div id="progress-component" class="progress-component">
<!-- 进度条 -->
<div class="progress-bar-box" @click="handleClick" @mouseup="handleMouseUp">
<div
class="progress-bar"
:style="{
width: (currentValue / totalValue) * 100 + '%',
backgroundColor: barColor,
}"
></div>
</div>
<!-- 拖拽点 -->
<div
id="progress-handle"
class="progress-handle"
:style="{
left: (currentValue / totalValue) * 100 - 0.35 + '%',
}"
@mousedown="handleDragStart"
></div>
</div>
</template>
<script setup>
const props = defineProps({
value: {
type: Number,
default: 0,
required: true,
},
totalValue: {
type: Number,
default: 100,
required: true,
},
barColor: {
type: String,
default: "#91caff",
},
barBoxColor: {
type: String,
default: "rgba(0, 0, 0, 0.04)",
},
handleShadowColor: {
type: String,
default: "#91caff",
},
});
const emit = defineEmits(["update:value", "afterChange", "change"]);
const currentValue = ref(parseFloat(props.value.toFixed(1)));
watch(
() => props.value,
(now) => {
currentValue.value = now;
}
);
const isDragging = ref(false);
const progressBarWidth = ref(0);
const progressBarRect = ref(null);
// 鼠标点击更新进度值
const handleClick = (event) => {
progressBarRect.value = event.currentTarget.getBoundingClientRect();
updateProgress(event.clientX);
emit("change", parseInt(currentValue.value));
};
const handleMouseUp = (event) => {
progressBarRect.value = event.currentTarget.getBoundingClientRect();
updateProgress(event.clientX);
emit("afterChange", parseInt(currentValue.value));
};
// 开始拖动
const handleDragStart = (event) => {
event.stopPropagation();
isDragging.value = true;
progressBarRect.value =
event.currentTarget.parentElement.getBoundingClientRect();
document.addEventListener("mousemove", handleDragMove);
document.addEventListener("mouseup", handleDragEnd);
// 禁用文本选择
document.body.style.userSelect = "none";
};
// 拖动时更新进度值
const handleDragMove = (event) => {
if (isDragging.value) {
updateProgress(event.clientX);
}
};
// 结束拖动
const handleDragEnd = () => {
isDragging.value = false;
document.removeEventListener("mousemove", handleDragMove);
document.removeEventListener("mouseup", handleDragEnd);
// 恢复文本选择
document.body.style.userSelect = "";
// 在拖动结束时触发 afterChange 事件
emit("afterChange", parseInt(currentValue.value));
};
// 更新进度值的函数
const updateProgress = (clientX) => {
const rect = progressBarRect.value;
const percentage = Math.min(
Math.max((clientX - rect.left) / rect.width, 0),
1
);
// 根据 totalValue 计算实际进度值并四舍五入
let newProgress = percentage * props.totalValue;
emit("update:value", newProgress);
currentValue.value = newProgress;
};
// 监听窗口大小变化,重新计算 progress-bar 的宽度
const handleResize = () => {
const progressBar = document.querySelector(".progress-bar-box");
progressBarWidth.value = progressBar.offsetWidth;
};
onMounted(() => {
// 获取 progress-bar 的宽度
const progressBar = document.querySelector(".progress-bar-box");
progressBarWidth.value = progressBar.offsetWidth;
window.addEventListener("resize", handleResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize);
});
</script>
<style scoped lang="less">
.progress-component {
width: 100%;
height: 12px;
position: relative;
margin: 4px 0;
padding: 4px 0;
.progress-bar-box {
height: 4px;
width: 100%;
position: absolute;
bottom: 0;
cursor: pointer;
// background-color: rgba(0, 0, 0, 0.04);
background-color: v-bind("barBoxColor");
border-radius: 4px;
transition: background-color 0.2s;
z-index: 50;
.point {
width: 6px;
height: 6px;
background: #333336;
border: 1px solid #939393;
border-radius: 50%;
position: absolute;
}
.point-1 {
left: 0;
}
.point-2 {
right: 0;
}
}
.progress-bar {
position: absolute;
height: 4px;
background-color: #91caff;
border-radius: 2px;
transition: background-color 0.2s;
z-index: 60;
}
.progress-handle {
position: absolute;
width: 8px;
height: 8px;
outline: none;
border-radius: 50%;
background-color: #ffffff;
position: absolute;
top: 6px;
z-index: 100;
cursor: pointer;
transition: box-shadow 0.2s;
box-shadow: 0 0 0 2px v-bind("handleShadowColor");
&:hover {
box-shadow: 0 0 0 4px v-bind("handleShadowColor");
}
}
}
</style>