滑动输入条、进度条

参考:

开发时遇到一个需求,一个进度条控制多个视频播放器。正常使用一些组件库自带的组件就好了——antdvslider

但是使用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>
  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值