当前位置: 首页 > news >正文

Vue3 实现进度条组件

样式如下,代码如下
在这里插入图片描述

<script setup>
import { computed, defineEmits, defineProps, onMounted, ref, watch } from 'vue'

// 定义 props
const props = defineProps({
  // 初始百分比
  initialPercentage: {
    type: Number,
    default: 0,
  },
})

// 定义 emits
const emits = defineEmits(['drag-percentage-change'])

// 定义圆圈数量
const circleCount = 5
// 进度条容器引用
const progressBarContainer = ref(null)
// 可拖动圆圈的位置(百分比)
const draggableCircleLeftPercentage = ref(props.initialPercentage)
// 可拖动圆圈的位置样式
const draggableCircleLeft = computed(() => `${draggableCircleLeftPercentage.value}%`)
// 进度条直线宽度
const progressLineWidth = computed(() => '100%')
// 已完成部分直线宽度
const completedLineWidth = computed(() => `${draggableCircleLeftPercentage.value}%`)

// 圆圈位置计算
const circles = ref([])
onMounted(() => {
  if (progressBarContainer.value) {
    const containerWidth = progressBarContainer.value.offsetWidth
    for (let i = 0; i < circleCount; i++) {
      const leftPercentage = (i / (circleCount - 1)) * 100
      circles.value.push({
        left: `${leftPercentage}%`,
      })
    }
  }
})

// 开始拖动
const isDragging = ref(false)
const startDragging = (e) => {
  if (!progressBarContainer.value)
    return
  isDragging.value = true
  const containerRect = progressBarContainer.value.getBoundingClientRect()
  const startX = e.clientX

  const handleMouseMove = (e) => {
    if (isDragging.value) {
      const offsetX = e.clientX - containerRect.left
      const containerWidth = containerRect.width
      let newLeft = (offsetX / containerWidth) * 100
      // 取整并确保在 0 到 100 之间
      newLeft = Math.min(100, Math.max(0, Math.round(newLeft)))
      draggableCircleLeftPercentage.value = newLeft
      // 抛出事件,返回当前百分比
      emits('drag-percentage-change', newLeft)
    }
  }

  const handleMouseUp = () => {
    if (isDragging.value) {
      isDragging.value = false
      document.removeEventListener('mousemove', handleMouseMove)
      document.removeEventListener('mouseup', handleMouseUp)
    }
  }

  document.addEventListener('mousemove', handleMouseMove)
  document.addEventListener('mouseup', handleMouseUp)
}

// 点击进度条
const handleBarClick = (e) => {
  if (!progressBarContainer.value)
    return
  const containerRect = progressBarContainer.value.getBoundingClientRect()
  const offsetX = e.clientX - containerRect.left
  const containerWidth = containerRect.width
  let clickPercentage = (offsetX / containerWidth) * 100
  // 取整并确保在 0 到 100 之间
  clickPercentage = Math.min(100, Math.max(0, Math.round(clickPercentage)))
  draggableCircleLeftPercentage.value = clickPercentage
  // 抛出事件,返回当前百分比
  emits('drag-percentage-change', clickPercentage)
}

// 点击圆圈
const handleCircleClick = (index) => {
  const percentage = (index / (circleCount - 1)) * 100
  draggableCircleLeftPercentage.value = percentage
  emits('drag-percentage-change', percentage)
}

// 监听 initialPercentage 的变化
watch(() => props.initialPercentage, (newValue) => {
  // 取整并确保在 0 到 100 之间
  const validValue = Math.min(100, Math.max(0, Math.round(newValue)))
  draggableCircleLeftPercentage.value = validValue
})
</script>

<template>
  <div ref="progressBarContainer" class="progress-bar-container" @mousedown="handleBarClick">
    <div class="progress-line" :style="{ width: progressLineWidth }" />
    <div class="completed-line" :style="{ width: completedLineWidth }" />
    <div
      v-for="(circle, index) in circles"
      :key="index"
      class="progress-circle"
      :class="{ active: index * (100 / (circleCount - 1)) <= draggableCircleLeftPercentage }"
      :style="{ left: `calc(${circle.left} - 5px)` }"
      @click="handleCircleClick(index)"
    />
    <div
      class="draggable-circle"
      :style="{ left: `calc(${draggableCircleLeft} - 7px)` }"
      @mousedown="startDragging"
    />
  </div>
</template>

<style scoped>
.progress-bar-container {
  position: relative;
  width: 100%;
  height: 20px;
}

.progress-line {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  left: 0;
  width: 100%;
  height: 2px;
  background-color: gray;
  z-index: 1;
}

.completed-line {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  left: 0;
  height: 2px;
  background-color: white;
  z-index: 2;
}

.progress-circle {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: #808080;
  z-index: 3;
  cursor: pointer;
}

.progress-circle.active {
  background-color: white;
}

.draggable-circle {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background-color: white;
  z-index: 4;
  cursor: pointer;
}
</style>

<template>
  <div>
    <CircleProgressBar
      :initialPercentage="initialValue"
      @drag-percentage-change="handlePercentageChange"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import CircleProgressBar from './CircleProgressBar.vue';

const initialValue = ref(20); // 初始百分比为 20%

const handlePercentageChange = (percentage) => {
  console.log('当前百分比:', percentage);
  // 你可以在这里处理接收到的百分比
};
</script>

相关文章:

  • Vue3全栈架构实战:Nuxt3服务端渲染与模块化设计深度剖析
  • 前端使用正则表达式提取经纬度 度分秒值
  • Linux命令之jq命令处理JSON数据
  • 【Linux】虚拟机设置静态IP
  • 【leetcode hot 100 763】划分字母区间
  • 【愚公系列】《高效使用DeepSeek》058-选题策划
  • real_time_camera_audio_display_with_animation
  • 华为OD机试2025A卷 - 正整数到excel编号之间的转换(Java Python JS C++ C )
  • 分布式微服务系统架构第97集:JVM底层原理
  • P1006 [NOIP 2008 提高组] 传纸条 题解
  • HTML5 浏览器兼容性:让旧浏览器也能拥抱 HTML5
  • 如何使用Audacity快速拆分整轨音频文件
  • 在 Ubuntu 下通过 Docker 部署 Misskey 服务器
  • 用AI来了解用户都在关注的品牌问题是什么?
  • linux 下du 和 ls-alh 的区别
  • 【JavaScript】十五、事件对象与环境对象
  • 跳跃游戏的最优解法——贪心算法的智慧与实践
  • 关于动态卷积
  • windows下GCC编译器使用FFTW预编译版共享库使用
  • 优秀的python可视化案例
  • 启明星网站建设/外贸网络推广营销
  • 正能量软件不良网站下载/推广信息哪个平台好
  • 网站建设需要什么手续/友情链接导航
  • 美国做按摩广告的网站/百度关键词搜索排名多少钱
  • 公司做网站要注意什么/宁波网络营销怎么做
  • 广州市城市建设网站/武汉今日头条最新消息