请坐好,泡杯咖啡☕️,我要开始我的故事了。
😎 别再裸写 uni-app 进度条了!从丑陋到优雅,我的踩坑与顿悟全记录
嘿,各位奋斗在一线的兄弟姐妹们,大家好。
今天不聊高大上的架构,也不谈深奥的算法,咱们来聊一个每个项目里几乎都会用到,但你可能从未真正“榨干”过它的组件——progress
进度条。
你可能会想:“切,一个进度条有啥好聊的?”
别急,就在上个项目中,这个看似不起眼的小东西,差点让我栽了个大跟头,但也正是在填坑的过程中,让我对“用户体验”和“组件设计”有了全新的认识。故事要从两个非常常见的需求说起…
故事的开始:两个“平平无奇”的需求
我当时接手的项目里有两个功能:
- 一个多文件上传管理器:需要清晰地展示每个文件的上传进度。
- 一个新手引导流程:用户每完成一步,顶部的总进度条就要前进一点。
听起来是不是很简单?我当时也是这么想的,甚至觉得半天就能搞定,然后愉快地摸鱼。然而,现实很快就给了我一记响亮的耳光。👋
场景一:文件上传进度条——“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
呢?它满足不了我,我就干掉它,自己写一个!
这个思路一打开,瞬间豁然开朗。
- 隐藏默认文本:给
<progress>
组件加上:show-info="false"
。 - 创建自定义文本:在
<progress>
旁边放一个我们自己的<text>
组件。 - 格式化:在
<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个刻骨铭心的教训与顿悟:
- 优雅降级,主动掌控:当默认UI不满足需求时(如格式化文本),不要硬磕,而是隐藏它,用基础组件自己拼一个,掌控力100%。
- 深挖属性,提升体验:像
active-mode
这样的属性,是区分“能用”和“好用”的关键,能极大提升动画的质感。 - 敬畏平台,代码防守:绝对不要依赖平台特定的API来实现核心交互逻辑! 优先选择
setTimeout
这类跨平台稳定可靠的方案进行手动控制。
希望我这个有点小波折的故事,能让你在未来的开发中,面对小组件也能多一分思考,少踩一些坑。
好了,今天就聊到这,下次再会!👋