1. 为什么加载与进度动画值得单独攒成一套收藏
做前端这些年,我删过无数个收藏夹,唯独这一类没舍得删:加载动画与进度条。原因很朴素——它们是项目里出现频率最高、复用成本最低、但视觉效果差距最悬殊的一类 UI 组件。同样一个"数据请求中"的场景,有人放一个灰色转圈,有人放一组流畅跳动的点阵,用户对产品档次的判断就在这零点几秒里完成了。
这份收藏是我两个晚上熬出来的,一共 17 个效果,覆盖了 CSS 加载动画的主流实现路径:纯transform旋转类、多元素错帧跳动类、SVGstroke-dasharray描边类、conic-gradient环形进度类,以及几条数值驱动的进度条方案。它们全部只用 HTML + CSS,部分需要 JS 参与数值更新,不依赖任何第三方动画库。适合刚学完 CSS 基础想练手的同学,也适合有一定经验、想给项目补一套轻量加载组件的开发者。
我选素材时有三个硬标准,先说清楚,免得你觉得这 17 个是随便凑的数:
- 能一行不写 JS 就不写。加载动画本身就是"临时占位",引入 JS 反而增加主线程负担,还可能和业务请求抢资源。
- 只动
transform和opacity。这两个属性走合成层,不触发重排重绘,低端机上掉帧概率最小。凡是靠改width、top、left做动画的方案,我一个都没收。 - 可拆卸。每个效果我都能独立抽成一个组件、一段样式,不会牵一发动全身。
提示:收藏这类素材的正确姿势不是"存起来就完事",而是把每个效果的动画函数曲线和时间参数抄进自己的笔记。真正能复用的不是颜色和尺寸,而是那串
cubic-bezier和时间节奏。
接下来我会先讲底层机制,再拆具体效果,最后讲进度条的数值驱动和避坑。内容偏实操,代码都经过我本机验证,你复制过去改改尺寸配色就能用。
1.1 加载动画和进度条其实是两个不同的心理问题
很多人把这两者混为一谈,都叫"loading",但它们在用户心理层面解决的是完全不同的焦虑。
加载动画(indeterminate)解决的是"系统还活着吗"。用户点了一个按钮,页面没反应,三秒之后他会开始怀疑是不是卡死了,然后疯狂重复点击。加载动画的作用就是持续释放"我在工作"的信号。它的核心指标只有一个:有没有在动。所以这类动画不需要精确,甚至可以故意做得"不精确",因为一旦你给了精确进度,反而会暴露真实耗时。
进度条(determinate)解决的是"还要等多久"。它必须给出一个可信的数字或比例,用户才能决定是继续等还是去倒杯水。它的核心指标是进度与实际加载的匹配度。我见过太多项目用假进度条——加载到 90% 卡住不动,这是最伤用户信任的做法,比不放进度条还糟糕。
理解这个区别,你在选型的时候就不会跑偏。请求接口用前者,文件上传、资源预载、多步骤表单用后者。下面这套 17 个效果,我在分类的时候也是按这个逻辑切的。
1.2 17 个效果的整体分类与选型思路
为了让你有个全局感,我先把它们按实现原理分个组。这张表我建议你先扫一眼,后面拆解时对照着看,遇到需要哪一类直接跳过去。
| 分组 | 数量 | 代表效果 | 核心技术 | 适用场景 |
|---|---|---|---|---|
| 形变旋转类 | 6 | 双环旋转、方块翻转、点阵脉动 | @keyframes+transform | 局部按钮、卡片内加载 |
| 描边线条类 | 6 | 环形描边、虚线追逐、描边扫光 | SVG +stroke-dasharray | 全屏等待、品牌感强的场景 |
| 数值进度类 | 5 | 条纹进度、环形进度、分段步进 | CSS 变量 +conic-gradient | 上传、下载、流程进度 |
这个分类不是随便分的,它对应着三种不同的渲染路径。形变旋转类靠 GPU 合成层,最轻;描边线条类依赖 SVG 路径计算,次之;数值进度类需要 JS 参与状态同步,最重。你项目里如果用得密集,优先选前两类。
选型上我还有一个经验:同一产品内不要混用超过两种动画语言。我见过一个后台系统,列表加载用圆环、按钮加载用三点跳、上传用条纹条,三个地方三种风格,用起来像三家公司的产品拼在一起。你要么全用圆环系,要么全用线性系,统一比炫酷重要得多。
2. 动手前必须理清的 CSS 动画底层机制
开始拆代码之前,有几个机制必须掰开揉碎讲。跳过这段直接复制代码,你大概率会遇到"为什么我的动画卡顿""为什么改个属性整个动画重来一遍"这种问题,然后回来翻文档,浪费的时间比现在读这一节多得多。
2.1 transition 与 animation 的分工别搞混
新手最容易犯的错,是拿transition去做循环动画。这两者的定位完全不同:
transition是状态过渡,它需要两个状态(起始态和目标态),由某个属性变化触发,比如 hover、class 切换。它天然只有一次,做完就停。animation是关键帧动画,它自己定义整个时间轴上的每一段,可以循环、可以反向、可以延迟,不依赖状态变化。
加载动画是无限循环的,所以必须是animation。那transition在哪用?在进度条上——当你用 JS 把--progress从 30 改成 60,宽度从 30% 变到 60%,这个过程用transition来做平滑补间,比手写关键帧自然得多。
我举个实际写过的例子,进度条部分:
.progress-bar { width: calc(var(--progress) * 1%); transition: width 0.4s cubic-bezier(0.22, 0.61, 0.36, 1); }注意这里的cubic-bezier(0.22, 0.61, 0.36, 1),这是一个"先快后慢"的缓出曲线。为什么不用默认的ease?因为默认ease在中段加速感太强,视觉上会有点"冲",而进度条要的是一种沉稳推进的感觉,缓出更合适。
2.2 关键帧百分比与时间函数怎么配合
@keyframes里的百分比是相对整个动画周期的,不是绝对时间。这是理解动画节奏的基础。举个例子,animation-duration是 1.2s,那么关键帧里的 50% 就是 0.6s 那一刻。
这就带来一个实用技巧:想让某段动作"快",不是去改百分比,而是改时间函数。比如:
@keyframes pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.35); opacity: 0.55; } } .dot { animation: pulse 1.4s ease-in-out infinite; }这里 0% 和 50% 之间是"放大"过程,ease-in-out让它在起点和终点都变慢、中间加速,出来的效果就是呼吸感。如果我把它换成linear,缩放会变成机械的匀速运动,看起来像机器在推拉,而不是"呼吸"。
我实测过,1.2s 到 1.6s 是呼吸类动画最舒服的区间。低于 1s 会显得焦躁,像在催用户;高于 2s 又像卡住了,用户会以为页面出问题。这个区间你也可以拿去用在按钮的加载态上。
2.3 只动 transform 和 opacity 的硬性理由
这一条我要强调三遍:加载动画只动transform和opacity只动transform和opacity。
浏览器渲染一帧要经过布局(Layout)、绘制(Paint)、合成(Composite)三个阶段。你改width、height、margin、top、left、font-size这些属性,会触发 Layout,浏览器得重新算一遍所有元素的位置,代价极高。改background-color、box-shadow这些会触发 Paint,代价中等。只有transform和opacity可以只走 Composite,在 GPU 上完成,代价极低。
我做过的对比测试:同样一个旋转动画,用rotate()在低端安卓机上稳定 60 帧,用left位移实现同样的视觉,帧率掉到 35 左右,而且手机明显发热。
还有个坑,box-shadow做脉动效果。看起来很炫,但因为每帧都要重绘阴影,CPU 占用会飙升。如果你非要用发光脉动,正确做法是把光做成一个独立的伪元素,用opacity去控制它的显隐,而不是动box-shadow本身。
注意:
will-change: transform不是万金油。它会让浏览器提前把元素提升为独立图层,但图层多了显存吃紧。加载动画上写一次就够了,别在几十个点阵元素上都写,反而更慢。
3. 17 个加载动画的逐个拆解与关键代码
这一节我按分组拆。每个效果我会给"核心思路 + 关键代码 + 我改过的参数"。你把代码复制进一个空 HTML 页面就能跑,不用装任何环境。
3.1 前 6 个:形变旋转类,轻量首选
这一类全部靠transform的rotate、scale、translate打天下,是加载动画里性能最好的一档,我优先推荐用在按钮内、卡片内这些出现频率高的位置。
第 1 个,经典双环反向旋转。两个圆环反向转,中间留空隙。核心是border的四边颜色不同,然后整体旋转:
.spinner-dual { width: 40px; height: 40px; border: 3px solid transparent; border-top-color: #4c8bf5; border-radius: 50%; animation: spin 0.9s linear infinite; } .spinner-dual::after { content: ''; position: absolute; inset: 5px; border: 3px solid transparent; border-bottom-color: #f5a623; border-radius: 50%; animation: spin 0.9s linear infinite reverse; } @keyframes spin { to { transform: rotate(360deg); } }inset: 5px是内圈的定位,比写top/left/right/bottom简洁。反向用reverse关键字,不重新写关键帧。0.9s 这个数值是我反复调出来的,快了像在抖,慢了像卡住。
第 2 个,脉冲圆点。单个点在缩放和透明之间来回,适合做"正在思考"的状态。代码就是上面提过的pulse关键帧,重点在于scale不要超过 1.4,否则视觉上会撑破容器。
第 3 个,三点跳动。三个点依次translateY上跳,关键在错帧:
.dot { animation: bounce 1.2s infinite ease-in-out; } .dot:nth-child(2) { animation-delay: 0.15s; } .dot:nth-child(3) { animation-delay: 0.3s; } @keyframes bounce { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-8px); } }60%到100%保持不动,是为了给动作留出"落回后静止"的停顿,这个停顿让整组动作有了节奏感。如果三个点均匀上跳,看起来就会很呆。
第 4 个,方块翻转。一个方块绕 X 轴或 Y 轴翻转:
.cube { animation: flip 1.8s infinite cubic-bezier(0.6, 0.05, 0.15, 0.95); } @keyframes flip { 0% { transform: perspective(120px) rotateX(0) rotateY(0); } 50% { transform: perspective(120px) rotateX(-180deg) rotateY(0); } 100% { transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); } }perspective()一定要加,否则翻转看起来是扁的,没有立体感。这个曲线我换过好几版,最后这个cubic-bezier出来的翻页感最像真实纸张。
第 5 个,波浪柱状条。五根竖条依次伸缩,像音浪。写法和三点跳动几乎一样,把translateY换成scaleY,然后把每根柱子的animation-delay依次加 0.1s。这里有个细节,transform-origin要设成bottom,不然柱子会从中间往两头撑。
第 6 个,九宫格脉动。3×3 的圆点,每个点的延迟按行列位置算出,形成斜向传播的波纹。延迟值我用的是(row + col) * 0.08s,出来的传播是对角线方向,比按行传播更有层次。
这六个里我个人最常用的是第一个和第三个。第一个稳,第二个活泼,覆盖了大部分场景。
3.2 中间 6 个:描边线条类,视觉质感更强
这一组开始上 SVG。SVG 的优势是路径任意,能做出圆形之外的形状,而且描边可以精确控制。代价是需要一点点路径知识。
第 7 个,SVG 环形描边旋转。这是最经典的一种,用stroke-dasharray和stroke-dashoffset配合:
.ring-path { fill: none; stroke: #4c8bf5; stroke-width: 4; stroke-linecap: round; stroke-dasharray: 126 188; /* 126 是可见长度,188 是周长补足 */ animation: dash 1.5s ease-in-out infinite; } @keyframes dash { 0% { stroke-dashoffset: 0; } 50% { stroke-dasharray: 40 314; stroke-dashoffset: -60; } 100% { stroke-dasharray: 126 188; stroke-dashoffset: -314; } }这里的关键是周长计算。假设圆半径 30,周长就是2π×30 ≈ 188.5。stroke-dasharray: 126 188表示 126 实线、188 空白,合起来刚好覆盖一圈多一点,所以看起来是固定的 2/3 圆弧在转。50% 那里我把实线长度压到 40,是为了模拟"拉伸"的感觉。
stroke-linecap: round让线端变圆,这个小细节极大提升质感,很多教程漏了。
第 8 个,虚线追逐。一条虚线环绕跑动。核心是stroke-dasharray把它切成均匀的小段,然后动stroke-dashoffset:
@keyframes chasing { to { stroke-dashoffset: -100; } }stroke-dashoffset是负值的时候,虚线朝运动方向流动。这个属性单位是 SVG 用户单位,不是像素,所以要注意 viewBox 的缩放。
第 9 个,心电图脉冲线。一条折线在横向平移,看起来像示波器。用translateX配合一个宽于容器的路径,平移到左端后瞬间复位:
@keyframes ecg { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } }这里我用-50%而不是-100%,是因为路径画了两遍首尾相接,只要平移一半就完成一个循环,接缝完全看不出来。这是做无缝滚动的一个通用技巧,值得记住。
第 10 个,描边文字扫光。文字本身是空心描边效果,用-webkit-text-stroke或者text-shadow叠多层实现。再用一个渐变遮罩扫过去:
@keyframes shine { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }背景渐变用linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent),配合background-clip: text就能让"光"只在文字区域内显示。
第 11 个,删除线扫过。这个和上面的思路一致,只是把渐变扫过换成一条横线从左到右划过去。实现方式是在文本元素上放一个伪元素当作线,用transform: scaleX()从 0 拉到 1:
.strike::after { content: ''; position: absolute; left: 0; top: 50%; height: 2px; width: 100%; background: currentColor; transform: scaleX(0); transform-origin: left; animation: strike 1.6s ease-in-out infinite; } @keyframes strike { 0% { transform: scaleX(0); } 50%, 100% { transform: scaleX(1); } }用scaleX而不是width,原因还是性能。这条线如果用在"正在删除中"的交互反馈上,效果相当自然。
第 12 个,线条无限拉伸的进度轨道。一根本体固定的轨道上,有一段高亮条来回跑。这种叫 indeterminate 进度条,最适合用在"总数未知"的场景:
@keyframes slide { 0% { left: -35%; right: 100%; } 60% { left: 100%; right: -90%; } 100% { left: 100%; right: -90%; } }这里的left和right同时变化,效果是滑块被"拉长"再"收缩",比单纯平移更有动感。不过注意,这个方案动了left/right,会有重排开销。如果对性能极敏感,可以改成用一个两倍宽的条做translateX,效果接近。
3.3 后 5 个:数值进度类,必须和 JS 配合
这一组是真正的进度条,需要外部数据驱动。核心是把进度值写成 CSS 自定义属性,样式里只读这个变量。
第 13 个,条纹滚动进度条。线性渐变画斜条纹,然后用background-position让它滚动:
.bar-striped { background-image: repeating-linear-gradient( 45deg, rgba(255,255,255,0.25) 0 10px, transparent 10px 20px ); animation: stripes 1s linear infinite; } @keyframes stripes { to { background-position: 28px 0; } }位移量 28px 是怎么来的?45 度斜条纹,条纹周期是 20px,横向位移一个周期需要20 / sin(45°) ≈ 28.3px。取 28 或者用1.414 × 20都行,只要保证循环时图案能接上,否则会看到跳一下。
第 14 个,液态波浪进度条。用一个圆角矩形做波浪,border-radius给到 45%,整体旋转,看起来就是在晃动的液体。这个效果配合较低的透明度,质感非常好:
.wave { border-radius: 45%; animation: rotateWave 12s linear infinite; } @keyframes rotateWave { to { transform: rotate(360deg); } }转速不能快,12s 一圈比较自然。快了就像搅拌机。
第 15 个,conic-gradient环形进度。这是近几年最省事的环形方案,不需要 SVG:
.ring { background: conic-gradient(#4c8bf5 calc(var(--p) * 1%), #e8eaf0 0); border-radius: 50%; mask: radial-gradient(circle, transparent 62%, #000 63%); }conic-gradient从 12 点方向开始按角度铺色,calc(var(--p) * 1%)就是进度百分比。mask用径向渐变挖掉中间,形成圆环。这个写法在主流浏览器上支持已经很好,比 SVG 方案少写一半代码。
第 16 个,分段步进进度条。把总进度拆成 N 段小格子,依次点亮。实现方式是把每个格子做成一个元素,用animation-delay依次亮起,或者用 JS 根据当前段数给格子加 class。前者适合"自动播放"型,后者适合"真实进度"型。
第 17 个,无限滚动跑马灯式加载条。严格说它不是进度条,而是介于两者之间的过渡型。一条渐变色带在轨道里匀速跑动,用来填补"进度待定"的空档期。它和上面第 12 个的区别在于,这个更强调视觉上的连续感,常用于整页预载页面。
4. 进度条的数值驱动与结构设计
光有样式不够,进度条的核心在于数值怎么进来、怎么平滑、怎么不出错。这一节讲工程侧的东西。
4.1 结构选型:div 方案 vs SVG 方案 vs conic-gradient 方案
我做选型时按三个维度权衡:代码量、可控精度、可访问性。
| 方案 | 代码量 | 精度控制 | 支持度 | 推荐场景 |
|---|---|---|---|---|
| div + width | 最少 | 线性,够用 | 全支持 | 横向条形进度 |
| SVG | 中等 | 任意路径 | 全支持 | 环形、不规则形状 |
| conic-gradient | 少 | 环形,够用 | 现代浏览器 | 环形,不要求老浏览器 |
横向条我一律用 div + CSS 变量,简单到不能再简单。环形我优先conic-gradient,只有在必须支持老旧环境时才退回 SVG。SVG 的额外成本是你要自己算周长、处理 viewBox 缩放,代码一多容易出错。
不管哪种方案,我都建议把进度值统一收敛到一个 CSS 变量上,比如--p,范围 0 到 100。这样你在 JS 里只管改这一个值,样式层的适配全在 CSS 里完成:
function setProgress(el, percent) { const p = Math.max(0, Math.min(100, percent)); el.style.setProperty('--p', p.toFixed(1)); }Math.max/min这个钳位看着不起眼,但非常有必要。我就遇到过因为后端返回了一个 105 的进度,导致圆环过度绘制,视觉上溢出容器,排查了半小时。
4.2 百分比平滑与"假进度"的正确打开方式
真实进度经常是跳变的,比如一次性返回 30%,下一次直接到 80%,直接设值会显得很突兀。这时候加一个过渡:
.progress-fill { transition: width 0.4s ease-out; }但过渡时间不能太长,否则会出现"进度条还在爬,实际已经加载完了"的错觉。0.3s 到 0.5s 是我常用的区间。
至于"假进度",我的原则是:宁可放 indeterminate 动画,也不放一个会卡住的假百分比。如果真的需要一个心理安抚型的进度,可以做一个"渐近式"假进度——从 0 快速涨到 60,然后以很慢的速度继续往 90 爬,永远不会到 100,直到真实加载完成后瞬间补到 100:
let fake = 0; const timer = setInterval(() => { fake += (90 - fake) * 0.08; // 越接近 90 涨得越慢 setProgress(bar, fake); }, 300);(90 - fake) * 0.08这个公式是让增长速度随接近上限而衰减,视觉上非常自然。加载完成时清掉定时器,把值设成 100。
4.3 尺寸与主题适配:用 em 和 CSS 变量
加载组件的一个隐藏需求是要能塞进任何尺寸的容器。按钮里的加载是 14px,卡片里的是 40px,全屏的是 80px。如果每个尺寸都写一份样式,维护成本爆炸。
我的做法是让所有尺寸基于一个变量:
.spinner { --size: 24px; --thickness: calc(var(--size) / 8); width: var(--size); height: var(--size); border-width: var(--thickness); }这样你只需要改--size一个值,线宽自动跟着变。size / 8这个比例是我试出来的,出来线宽视觉上刚好,太细会看不清,太粗会显得笨重。
主题色也一样,全部走currentColor。这样组件放在任何文字颜色的容器里,都会自动和文字同色,不用单独传颜色参数。这一个技巧能省掉你大量的样式覆盖代码。
5. 性能、兼容与可访问性避坑清单
前面讲的都是"怎么做出来",这一节讲"怎么不出事"。这部分内容网上讲得少,但实际项目里最容易翻车。
5.1 加载动画导致的卡顿排查思路
加载动画出现在什么时刻?恰恰是页面最忙的时候——在请求接口、在渲染大数据、在解析资源。这时候如果你的动画本身很重,就会和主线程抢资源,形成恶性循环:越卡越动画掉帧,越掉帧看起来越像死了。
排查顺序我一般是这样走的:
- 打开浏览器性能面板,录制 5 秒,看是否有长任务。
- 检查动画元素是否触发了 Layout,看渲染面板里的紫色块。
- 检查图层数量,图层过多会吃显存,尤其移动端。
- 检查是否有大量元素同时执行
animation。
我踩过的最典型的一个坑:一个九宫格加载动画,我在 9 个点上同时写了animation: pulse 1.5s infinite,看起来没问题,但在低端机上帧率直接腰斩。后来改成只用一个父元素加animation,9 个点靠transform继承和延迟错开,性能立刻回来了。
注意:
filter: blur()慎用在动画元素上。它是一个非常昂贵的属性,每帧都要重新计算模糊,哪怕只动opacity,只要元素上有 blur,合成成本都会高很多。要做模糊光晕,用一张预先模糊好的图片代替。
5.2 尊重系统的"减少动态效果"偏好
这是一个很多人忽略但越来越重要的点。操作系统里有"减少动态效果"的设置,用户开启后,你的动画应该收敛。做得好的话,这是产品成熟度的体现:
@media (prefers-reduced-motion: reduce) { .spinner, .dots, .bar-striped { animation-duration: 2s; animation-iteration-count: 1; } }我一般不是直接关掉,而是把动画周期拉长、取消无限循环。因为完全静止的加载状态会让用户以为卡住了,同样体验不好。这个折中方案我用下来反馈最好。
顺带说一句可访问性,加载容器上加上role="status"和aria-live="polite",屏幕阅读器会在状态变化时播报。文本内容用aria-label给出,比如"正在加载,已完成百分之六十"。这些属性不占多少代码,但用起来专业度差很多。
5.3 常见问题速查表
下面这张表是我这两晚整理时顺手记下来的,都是实际会遇到的:
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 动画跑偏、位置跳动 | 用了top/left做位移动画 | 换成transform: translate |
| 旋转中心不对 | 没设transform-origin | 显式指定中心点,默认是元素中心 |
| 动画结束瞬间闪回 | 关键帧首尾状态不一致 | 让 0% 和 100% 属性完全对齐 |
| 圆环首尾接不上 | 周长算错了 | 按2πr重算,或用浏览器逐帧核对 |
| 进度条超过 100% | 后端数值异常 | JS 层做钳位,别只靠 CSS |
| 高分屏上模糊 | 用了 1px 以内的线宽 | 线宽取整,或用transform: scale |
| 动画在 Safari 上不动 | 前缀或属性支持问题 | 优先用transform,避开新属性 |
最后一条我要多说一句:conic-gradient这类较新的特性,用之前最好在目标环境的真机上过一遍。我有一次在开发机上跑得好好的,到一台老平板上直接变成纯色块,就是因为不支持。
6. 两个晚上踩出来的细节和后续延展
写到这里,17 个效果和背后的机制都过了一遍。最后分享几个只有动手做才会发现的东西。
关于配色,我一开始特别喜欢用高饱和的蓝色,觉得亮眼。做多了才发现,加载动画的配色应该比主视觉低一档饱和度。因为它是持续运动的元素,高饱和的循环运动会让人视觉疲劳,用户盯着看十几秒会很难受。后来我统一改成主色降 20% 饱和度,加一点点灰,观感舒服太多。
关于节奏,同一个页面里如果同时出现多个加载动画,它们的周期最好错开。我见过一个页面,三个转圈同时转,频率一致但相位一致,看起来像三个同步的机器人,非常机械。后来我给其中一个加了 0.3s 的随机延迟,整体感觉马上就"活"了。
关于缓存,如果你把这些动画做成组件,记得把关键帧抽到全局样式里。我遇到过同一个@keyframes spin在六个组件里各写一遍,编译后体积大了好几 KB,虽然能被压缩工具去重,但不如一开始就写在全局。
后续这套东西还能怎么延展,我最近在试两个方向。一个是把进度条的数值和 Web Animations API 结合,用 JS 控制关键帧的播放进度,做出可以"暂停、倒放"的进度反馈,用在可撤销的批量操作上很有意思。另一个是把加载动画和骨架屏结合起来——加载初期用骨架屏占位,超过 800ms 还没返回数据,再在骨架屏上叠一层轻量的脉冲动画。这样既避免了"一闪而过"的动画闪烁,又能在真正慢的时候给出等待反馈。
这两个方向我都还在调,等稳定了再单独整理一篇。眼下这 17 个效果,你先把形变类和数值类的用起来,覆盖日常开发九成场景足够了。