别再裸写 uni-app 进度条了!从丑陋到优雅,我的踩坑与顿悟全记录*

请坐好,泡杯咖啡☕️,我要开始我的故事了。


😎 别再裸写 uni-app 进度条了!从丑陋到优雅,我的踩坑与顿悟全记录

嘿,各位奋斗在一线的兄弟姐妹们,大家好。

今天不聊高大上的架构,也不谈深奥的算法,咱们来聊一个每个项目里几乎都会用到,但你可能从未真正“榨干”过它的组件——progress进度条。

你可能会想:“切,一个进度条有啥好聊的?”

别急,就在上个项目中,这个看似不起眼的小东西,差点让我栽了个大跟头,但也正是在填坑的过程中,让我对“用户体验”和“组件设计”有了全新的认识。故事要从两个非常常见的需求说起…

故事的开始:两个“平平无奇”的需求

我当时接手的项目里有两个功能:

  1. 一个多文件上传管理器:需要清晰地展示每个文件的上传进度。
  2. 一个新手引导流程:用户每完成一步,顶部的总进度条就要前进一点。

听起来是不是很简单?我当时也是这么想的,甚至觉得半天就能搞定,然后愉快地摸鱼。然而,现实很快就给了我一记响亮的耳光。👋

请添加图片描述

请添加图片描述

场景一:文件上传进度条——“PM,听我解释!”

第一个需求,文件上传器。UI设计得很好看,我的任务就是让它动起来。用 v-for 循环文件列表,每个文件项里放一个 progress 组件,百分比绑定到各自的进度值。

几分钟后,基础版就出来了:

// js部分,模拟上传进度
this.uploadInterval = setInterval(() => {
    this.fileList.forEach(file => {
        if (file.progress < 100) {
            file.progress += Math.random() * 10;
            if (file.progress > 100) file.progress = 100;
        }
    });
}, 500);
<!-- 模板部分 -->
<progress :percent="file.progress" show-info="true" />

看起来没毛病,对吧?然而,PM(产品经理)幽幽地飘了过来,指着屏幕说:“这个进度数字能不能统一一下?你看,有时候是整数,有时候一位小数,我想要始终显示两位小数,比如 10% 也要显示成 10.00%,这样显得专业。”

我心里一沉:“这…show-info好像没这个配置啊。”

第一次踩坑:试图“硬刚”show-info

我的第一反应是去改数据源。

// 错误尝试 ❌
file.progress = (file.progress + Math.random() * 10).toFixed(2);

结果,进度条直接不动了!为什么?因为 toFixed(2) 返回的是个字符串,而 progress 组件的 :percent 属性需要的是 Number 类型。GG。

顿悟时刻:该放手时就放手!💡

我为什么要死磕 show-info 呢?它满足不了我,我就干掉它,自己写一个!

这个思路一打开,瞬间豁然开朗。

  1. 隐藏默认文本:给 <progress> 组件加上 :show-info="false"
  2. 创建自定义文本:在 <progress> 旁边放一个我们自己的 <text> 组件。
  3. 格式化:在 <text> 组件里,为所欲为!{{ file.progress.toFixed(2) }}%

最终的优雅代码长这样:

<!-- template -->
<view class="progress-wrapper">
  <!-- 隐藏掉它自带的,格式不可控的文本 -->
  <progress :percent="file.progress" :show-info="false" ... />
  <!-- 用我们自己的文本组件,随心所欲地格式化 -->
  <text class="progress-text">{{ file.progress.toFixed(2) }}%</text>
</view>
/* style */
.progress-wrapper {
  display: flex;
  align-items: center;
}
.progress-bar {
  flex-grow: 1; /* 让进度条占据所有剩余空间 */
}
.progress-text {
  width: 60px; /* 固定宽度,防止数字跳动时布局晃动 */
  text-align: right;
  font-family: 'Courier New', monospace; /* 等宽字体,数字跳动更好看 */
}

当我把这个版本给PM看时,他露出了满意的微笑。那一刻我悟了:官方组件是用来服务我们的,当它的默认行为成为束缚时,果断放弃,组合基础组件来自定义,才是王道! 😉

场景二:新手引导进度条——“我的按钮怎么被锁死了?!”

搞定了第一个,我信心满满地 سراغ (sāragh - 波斯语:走向) 第二个需求。一个总共3步的新手引导,用户每点一次“下一步”,进度条就从 0% -> 33.33% -> 66.67% -> 100%。

第二次踩坑:停不下来的“闪烁”动画

很快,我用 computed 属性计算出百分比,绑定到进度条上。

computed: {
    progressPercent() {
      return (this.currentStep / this.totalSteps) * 100;
    }
}

但效果很廉价。每次点击“下一步”,进度条都会“唰”地一下归零,然后再从头播放动画到新的位置。这感觉不像是“前进”,倒像是“重置再开始”。体验非常割裂。

第二次顿悟:active-mode,被忽视的宝藏属性!

我又一头扎进了文档。这次,一个不起眼的属性进入了我的视野:active-mode

  • backwards(默认值):每次进度更新,动画都从头开始
  • forwards:动画会从上次的终点继续

我的天,这不就是我想要的吗?!我立刻加上 active-mode="forwards"

<progress :percent="progressPercent" active-mode="forwards" ... />

效果立竿见影!进度条如丝般顺滑地从 33% 流动到 66%,完美!我感觉自己又行了!😎

终极巨坑:按钮永久冻结 🥶

为了防止用户在动画播放时疯狂点击“下一步”,我加了个 isAnimating 的状态锁。

// 点击“下一步”时
nextStep() {
    this.isAnimating = true; // 锁定按钮
    this.currentStep++;
},

// 监听动画结束事件
onStepAnimationEnd() {
    this.isAnimating = false; // 解锁按钮
}
<progress @activeend="onStepAnimationEnd" ... />
<button @click="nextStep" :disabled="isAnimating">下一步</button>

在微信开发者工具里,一切完美。我心想,这波稳了,可以下班了。

然后,测试同学的消息就来了:“你这H5上点第二下就卡死了啊!”、“App上也是,按钮点不了了!”

我的心瞬间凉了半截。怎么会?我跑去一看,果然,在H5和App上,点击“下一步”后,按钮直接被 disabled,再也无法点击。isAnimating 变成了 true 就再也没回来。

最终顿悟:千万别把身家性命压在平台API上!

我发疯一样地检查代码,逻辑天衣无缝。最后,我把目光投向了文档里 activeend 事件的那一行,后面跟着一行小字:

微信小程序、京东小程序

看到了吗?!@activeend 事件只在部分小程序平台生效!在H5和App里,它根本就不会被触发!我的解锁逻辑建立在一个不存在的事件上,按钮自然就被永久锁死了。

这是一个惨痛的教训:在做跨端开发时,永远不要盲目相信某个API在所有平台表现一致!一定要仔细看文档的平台兼容性说明!

那么,正确的、跨所有平台的解决方案是什么?

答案是:自己动手,丰衣足食!

我们不能依赖一个不靠谱的事件,但我们知道一个确定的东西——动画的持续时间 duration

// 终极健壮版 JS
data() {
    return {
        // ...
        isAnimating: false,
        animationDuration: 40, // 进度增加1%所需毫秒数
    }
},
methods: {
    nextStep() {
        if (this.currentStep < this.totalSteps) {
            const oldPercent = (this.currentStep / this.totalSteps) * 100;
            const newPercent = ((this.currentStep + 1) / this.totalSteps) * 100;
            const totalAnimationTime = (newPercent - oldPercent) * this.animationDuration;

            this.isAnimating = true; // 立即锁定
            this.currentStep++;

            // 创建一个和动画时长完全相同的定时器,手动解锁
            setTimeout(() => {
                this.isAnimating = false;
            }, totalAnimationTime);
        }
    }
}

我用 setTimeout 创建了一个平台无关的、绝对可靠的“动画锁”。它不依赖任何事件,只相信我们自己设定的时间。这套代码部署上去后,所有平台表现完美。那一刻,我长舒了一口气,感觉自己又升华了。😌

总结一下我的收获

一个简单的进度条,带给我3个刻骨铭心的教训与顿悟:

  1. 优雅降级,主动掌控:当默认UI不满足需求时(如格式化文本),不要硬磕,而是隐藏它,用基础组件自己拼一个,掌控力100%。
  2. 深挖属性,提升体验:像 active-mode 这样的属性,是区分“能用”和“好用”的关键,能极大提升动画的质感。
  3. 敬畏平台,代码防守绝对不要依赖平台特定的API来实现核心交互逻辑! 优先选择 setTimeout 这类跨平台稳定可靠的方案进行手动控制。

希望我这个有点小波折的故事,能让你在未来的开发中,面对小组件也能多一分思考,少踩一些坑。

好了,今天就聊到这,下次再会!👋

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值