使用uniapp,实现根据时间倒计时执行进度条变化

一、功能清单(已全部实现)

2️⃣ v-model 控制开始 / 暂停 / 继续

  • v-model:running 控制进度条运行状态
  • 外部随时暂停 / 恢复

3️⃣ 可作为公共组件(uniapp / Vue2)

  • props + emit 规范
  • 可直接放到 components/ProgressBar/index.vue
  • 也可无痛迁移到 uni_modules

4️⃣ 主题化(高度 / 颜色 / icon 可配置)

  • 渐变色可配置
  • 危险阈值可配置
  • icon 可配置

二、组件使用方式示例(先看用法)

vue 复制代码
<ProgressBar
  v-model:running="running"
  :totalTime="180"
  icon="⚡"
  :dangerTime="15"
  :height="20"
/>

<button @click="running = !running">
  {{ running ? '暂停' : '继续' }}
</button>

三、完整公共组件(每一行都有注释

📁 components/ProgressBar/index.vue

vue 复制代码
<template>
  <!-- 整体容器,作为相对定位基准 -->
  <view class="progress-wrapper">

    <!-- 跟随进度移动的图标 -->
    <view
      class="progress-icon"
      :style="{ left: iconLeft + '%' }"
    >
      {{ icon }}
    </view>

    <!-- 进度条轨道 -->
    <view
      class="progress-track"
      :style="{ height: height + 'rpx' }"
    >
      <!-- 实际进度条 -->
      <view
        class="progress-bar"
        :class="{ danger: isDanger }"
        :style="{ width: progress + '%', background: barGradient }"
      >
        <!-- 高光流动效果 -->
        <view class="progress-shine"></view>
      </view>
    </view>

    <!-- 倒计时文本 -->
    <view class="time-text">
      {{ formatTime(remainTime) }}
    </view>

  </view>
</template>

<script>
export default {
  name: "ProgressBar",

  /* ================= props ================= */
  props: {
    // 总时长(秒)
    totalTime: {
      type: Number,
      default: 180,
    },

    // v-model:running 控制运行状态
    running: {
      type: Boolean,
      default: false,
    },

    // 危险时间阈值(秒)
    dangerTime: {
      type: Number,
      default: 15,
    },

    // 进度条高度(rpx)
    height: {
      type: Number,
      default: 20,
    },

    // 图标内容
    icon: {
      type: String,
      default: "⚡",
    },

    // 正常渐变色
    gradient: {
      type: String,
      default: "linear-gradient(90deg,#ff6a00,#ff9f00,#ff6a00)",
    },

    // 危险状态渐变色
    dangerGradient: {
      type: String,
      default: "linear-gradient(90deg,#ff3b30,#ff6a00)",
    },
  },

  /* ================= emits ================= */
  emits: ["update:running", "finish"],

  data() {
    return {
      // 剩余时间(毫秒)
      remainTime: this.totalTime * 1000,

      // requestAnimationFrame id
      rafId: null,

      // 开始时间戳
      startTime: 0,
    };
  },

  /* ================= 计算属性 ================= */
  computed: {
    // 当前进度百分比(0 → 100)
    progress() {
      const elapsed = this.totalTime * 1000 - this.remainTime;
      return Math.min((elapsed / (this.totalTime * 1000)) * 100, 100);
    },

    // 图标位置(防止超出边界)
    iconLeft() {
      return Math.min(Math.max(this.progress, 0), 100);
    },

    // 是否进入危险状态
    isDanger() {
      return this.remainTime / 1000 <= this.dangerTime;
    },

    // 当前进度条渐变色
    barGradient() {
      return this.isDanger ? this.dangerGradient : this.gradient;
    },
  },

  /* ================= 监听 ================= */
  watch: {
    // 外部控制 running
    running(val) {
      val ? this.start() : this.stop();
    },
  },

  mounted() {
    // 初始 running 为 true 则直接启动
    if (this.running) {
      this.start();
    }
  },

  beforeDestroy() {
    // 组件销毁时清理动画
    cancelAnimationFrame(this.rafId);
  },

  /* ================= 方法 ================= */
  methods: {
    // 启动倒计时
    start() {
      // 防止重复启动
      if (this.rafId) return;

      // 记录开始时间
      this.startTime = Date.now();

      // 帧循环
      const tick = () => {
        const elapsed = Date.now() - this.startTime;

        // 计算剩余时间
        this.remainTime = Math.max(
          this.remainTime - elapsed,
          0
        );

        // 更新时间基准
        this.startTime = Date.now();

        // 继续 or 结束
        if (this.remainTime > 0 && this.running) {
          this.rafId = requestAnimationFrame(tick);
        } else {
          this.finish();
        }
      };

      this.rafId = requestAnimationFrame(tick);
    },

    // 暂停倒计时
    stop() {
      cancelAnimationFrame(this.rafId);
      this.rafId = null;
    },

    // 结束
    finish() {
      this.stop();
      this.$emit("update:running", false);
      this.$emit("finish");
    },

    // 格式化时间 mm:ss
    formatTime(ms) {
      const s = Math.floor(ms / 1000);
      const m = Math.floor(s / 60);
      const sec = s % 60;
      return `${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
    },
  },
};
</script>

<style scoped lang="scss">
/* 外层容器 */
.progress-wrapper {
  position: relative;
  padding-top: 40rpx;
}

/* 进度条轨道 */
.progress-track {
  background: rgba(255, 255, 255, 0.15);
  border-radius: 999rpx;
  overflow: hidden;
}

/* 主进度条 */
.progress-bar {
  height: 100%;
  border-radius: 999rpx;
  position: relative;
  transition: width 0.08s linear;
}

/* 流动条纹 */
.progress-bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background-size: 40rpx 40rpx;
  background-image: linear-gradient(
    -45deg,
    rgba(255,255,255,.25) 25%,
    transparent 25%,
    transparent 50%,
    rgba(255,255,255,.25) 50%,
    rgba(255,255,255,.25) 75%,
    transparent 75%
  );
  animation: stripe 2.5s linear infinite;
}

/* 危险状态加速 */
.progress-bar.danger::after {
  animation-duration: 1.2s;
}

/* 高光 */
.progress-shine {
  position: absolute;
  left: -40%;
  width: 40%;
  height: 100%;
  background: linear-gradient(
    120deg,
    transparent,
    rgba(255,255,255,.6),
    transparent
  );
  animation: shine 1.2s infinite;
}

/* 图标 */
.progress-icon {
  position: absolute;
  top: 0;
  transform: translate(-50%, -100%);
  font-size: 36rpx;
  transition: left 0.08s linear;
  animation: float 1.6s ease-in-out infinite;
}

/* 时间文字 */
.time-text {
  margin-top: 20rpx;
  text-align: center;
  font-size: 26rpx;
  color: #ff7a18;
}

/* 动画定义 */
@keyframes stripe {
  to { background-position: 80rpx 80rpx; }
}
@keyframes shine {
  to { left: 120%; }
}
@keyframes float {
  50% { transform: translate(-50%, -120%); }
}
</style>
相关推荐
2501_915909066 小时前
如何保护 iOS IPA 文件中资源与文件的安全,图片、JSON重命名
android·ios·小程序·uni-app·json·iphone·webview
集成显卡7 小时前
Bun v1.3.6 发布:内置 Tarball 归档支持、JSONC 解析、Bundle 分析增强等重磅更新!
javascript·新版本·bun.js
奔跑的web.7 小时前
TypeScript Enum 类型入门:从基础到实战
前端·javascript·typescript
盐真卿7 小时前
python2
java·前端·javascript
梦梦代码精8 小时前
BuildingAI vs Dify vs 扣子:三大开源智能体平台架构风格对比
开发语言·前端·数据库·后端·架构·开源·推荐算法
seabirdssss8 小时前
《bootstrap is not defined 导致“获取配置详情失败”?一次前端踩坑实录》
前端·bootstrap·html
kgduu9 小时前
js之表单
开发语言·前端·javascript
摘星编程10 小时前
React Native for OpenHarmony 实战:Picker 选择器组件详解
javascript·react native·react.js
摘星编程10 小时前
React Native for OpenHarmony 实战:VirtualizedList 虚拟化列表
javascript·react native·react.js
谢尔登10 小时前
Vue3 响应式系统——computed 和 watch
前端·架构