说实话,第一次看到气泡 Loading 的时候,我的第一反应不是“这个效果能用在哪”,而是“这东西怎么做的”。气泡一个接一个冒出来,又在水面炸开,加载过程不再是一根干巴巴的转圈,反而像是有个透明瓶子在眼前倒水——等多久都不觉得焦躁,这大概就是微交互动效的魔力。今天就把我从零实现这个气泡 Loading 的完整思路、代码细节和踩坑记录整理成文,从动画原理讲到性能优化,一条龙聊透。适合已经写了三到六个月前端、想给项目加一点“小设计感”的同行,也适合那些只想要现成代码直接抄的读者,这两种需求我都会照顾到。
1. 气泡 Loading 是什么,为什么值得亲手做一版
气泡 Loading 本质上是一组模拟液体冒泡过程的加载动效。页面请求数据时,屏幕上出现若干大小不一的气泡,沿着垂直方向缓缓上升,到达顶部后破裂消失,同时新的气泡从底部不断生成,形成一个循环往复的“冒泡—破裂—再冒泡”过程。和传统 spinner 转圈、进度条填充相比,气泡效果给人的心理暗示更轻盈——加载状态不再是一根冰冷的等待条,而是一个有节奏感的小场景。
这种效果最常见的落点在 H5 支付跳转、移动端下拉刷新、数据看板的初始加载,以及小程序里登录态校验那几秒钟的等待。设计语言偏清新、偏自然的团队尤其喜欢用它,因为气泡不需要太多修饰,本身就能传递“轻量、流动、持续进行中”的语义。还有一个现实原因:品牌方对加载页面越来越敏感,同样的等待时间,换成气泡动效后跳出率明显下降,这在电商活动页上被反复验证过。
那为什么不直接去 CodePen 复制一份?两个原因。第一,现成作品绝大多数是 Demo 级别,没有考虑真实项目里的适配问题,比如深浅色模式、容器尺寸变化、低端机性能;第二,气泡动效的“灵魂”在于随机性,而随机参数需要跟自己的设计体系对齐——气泡大小范围、上升速度、透明度衰减,这些都必须能调、能控。自己写一版,后面加需求也好接,比如加载结束时让气泡快速上浮消散,这种收尾细节直接抄来的代码通常改不动。
从实现路径上看,气泡动效并不复杂,核心无非三件事:生成气泡节点、驱动它做位移动画、到顶后销毁并重新生成。但要把这十几行逻辑写出“有质感”,里面涉及的细节比想象中多得多。接下来我把方案选型和动效拆解分开讲。
1.1 方案选型:CSS、SVG、Canvas 到底选哪个
做气泡 Loading 有三条主流路线,很多人上来就纠结。我直接给结论:常规业务项目选 CSS + 少量 JavaScript,追求复杂流体模拟才上 Canvas。
纯 CSS 方案可以做出气泡的上浮和缩放,用@keyframes+animation-delay就能让多个气泡错峰出现。但纯 CSS 的随机性很弱,气泡大小、位置、速度全都得静态写死在样式里,想做成“每次刷新都不一样”就得维护一长串规则,代码量反而失控。而且气泡到顶后的消失动画、循环周期控制,纯 CSS 写起来像在做谜题,对后续维护不友好。
SVG 方案的优势是气泡可以做得很精致,比如用渐变给气泡加高光、加镜面反光,质感比纯 CSS 强。劣势同样明显:几十个气泡就是几十个 SVG 节点,DOM 开销大,而且 SVG 动画的驱动通常还需要 JS 配合,等于既要维护图形又要维护逻辑,复杂度两头占。
Canvas 适合另一种场景——不需要 DOM 节点,所有气泡都在一张画布上绘制,性能上限最高。如果你要做的是“液面波动+气泡上浮+水花飞溅”这种全套流体模拟,Canvas 是唯一选择。但普通加载动效用 Canvas 属于大炮打蚊子:没法直接让设计师用 CSS 调样式,调试成本高,在低端安卓机上还容易遇到 canvas 上下文创建失败的老问题。
所以最终方案定为“CSS 定义视觉样式 + JS 生成随机参数并驱动生命周期”。CSS 负责气泡长得像气泡(圆角、阴影、高光),JS 负责决定每个气泡的尺寸、速度、延迟和销毁时机。这样视觉和逻辑解耦,参数全部集中在 JS 里,改起来一目了然。
1.2 动效核心逻辑拆解:一条循环生产线的三个工位
为了把气泡 Loading 讲清楚,我习惯把它拆成一条“生产线”,三个工位各司其职:生成、驱动、回收。
生成工位负责创建气泡 DOM 节点,并给它赋予一组随机参数。包括气泡直径、水平位置、上升时长、开始延迟、初始透明度、水平摆动幅度。这里的关键不是“能生成”,而是“生成的参数要在一个合理区间里随机”,例如直径范围、时长范围都必须有边界,否则会出现几个大得离谱的气泡把容器撑破的现象。
驱动工位是动效的核心,用的是 CSStransition或requestAnimationFrame驱动transform: translateY,让气泡从容器底部移动到顶部。我说一句实操中最重要的体会:位移一定要走transform,不要碰top和left。top变化触发的是布局重排,气泡数量一多,低端设备直接卡成幻灯片;transform走的是合成器线程,GPU 加速,几十个气泡同时动毫无压力。
回收工位最有意思,也是多数教程不讲的点。气泡到达顶部不能直接移出 DOM,否则下一轮循环还得重新创建节点,造成内存抖动。我的做法是给气泡加一个“上岸”前的过渡:先让气泡透明度降到 0、略微放大,模拟破裂的瞬间,然后用animationend或transitionend监听到动画结束,再把节点从 DOM 中移除,并新生成一个补位。这样从用户视角看,气泡是“碎掉”的,不是“消失”的,体验感完全不同。
2. 核心细节解析:从气泡长相到动画节奏控制
2.1 气泡的“长相”设计:圆角、高光与阴影缺一不可
气泡在视觉上要成立,三个要素缺一不可:圆角、高光、柔和阴影。很多人写气泡就写一个border-radius: 50%加上半透明背景色,结果看起来像彩色圆片,不像气泡。原因在于真实气泡的透光性和表面反光没有被模拟出来。
我的做法是给气泡节点加一层::before伪元素模拟高光。高光不是正中一个圆,而是在气泡左上部分画一个扁平的椭圆,白色半透明,模拟光从左上角打入时的弧面反射。核心样式大致是这样:
.bubble { position: absolute; bottom: -80px; border-radius: 50%; background: radial-gradient( circle at 32% 28%, rgba(255, 255, 255, 0.35) 0%, rgba(255, 255, 255, 0) 45% ); box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.12); will-change: transform, opacity; } .bubble::before { content: ""; position: absolute; left: 18%; top: 12%; width: 36%; height: 22%; border-radius: 50%; background: rgba(255, 255, 255, 0.5); filter: blur(2px); }高光用radial-gradient做基础,再用伪元素叠加一个模糊小椭圆,两层一叠,气泡的玻璃质感立刻就出来了。阴影部分我用box-shadow的inset内阴影,而不是外阴影,因为气泡是半透明的,内部边缘稍微压暗一点会更接近真实液泡。背景色我习惯用全局 CSS 变量控制,方便做深浅色主题适配,这一点后面单独说。
2.2 关键帧设计:上升、摆动与破裂三个阶段
气泡动画分成上升、摆动、破裂三个阶段,三个阶段的节奏参数决定了最终观感。上升阶段是主体,气泡从底部到容器顶部,耗时通过 JS 随机生成,通常在 3 到 6 秒之间。这个范围是我试出来的,太快会有“冒烟”的急躁感,太慢用户会觉得卡死了。
阶段二是水平摆动。真实气泡在水中上升不是直线,而是受到液体扰动会产生小幅度的侧向偏移。我给气泡加了一个可选的translateX偏移动画,偏移量控制在水平位置正负 10 像素以内,再配上ease-in-out的时间函数,气泡就会有一种“被水流带着走”的摇曳感。
阶段三是破裂。气泡到达顶部后我不会让它直接消失,而是先触发一个 200 到 300 毫秒的“破碎”动画:透明度从当前值降到 0,缩放从 1 放大到 1.15,同时高光部分快速变亮再熄灭,模拟气泡被撑破的感觉。这个细节需要 JS 配合,在气泡上升动画结束前 300 毫秒,给节点加一个.burst类。类样式的切换时间点如果控制不好,会出现气泡还没到顶就“碎”了,或者到顶后半天下不去,这里需要多调试几轮。
2.3 随机参数工程化:限幅、分布与防重叠
随机不是乱来,参数必须要做边界约束。我为每个气泡定义了一个配置对象,所有可调参数集中管理:
const bubbleConfig = { count: 14, size: { min: 18, max: 54 }, duration: { min: 3200, max: 5800 }, delay: { min: 0, max: 2500 }, drift: { min: -10, max: 10 }, opacity: { min: 0.35, max: 0.85 }, };count是同时存在的最大气泡数,size是直径范围,duration是上升周期毫秒数,delay是首次出现的错峰延迟,drift是水平摆动幅度,opacity是透明度区间。所有随机值都落在这些区间内,不会出现超出设计语义的极端情况。
防重叠是另一个容易被忽略的问题。气泡是半透明的,完全重叠其实视觉上不算糟,但如果两个气泡位置完全一样,会出现“两个气泡叠成一个却不破碎”的诡异效果。我在生成水平位置时做了简单互斥:新气泡的水平位置如果与现存气泡距离小于直径平均值的 60%,就重新随机一次,最多重试三次。这个逻辑只花了几行代码,但能明显减少重叠概率。
3. 实操过程:从零实现一个完整的气泡 Loading
3.1 基础结构与样式:先搭一个能跑的框架
开始之前先明确目标:实现一个 200 乘 200 的加载容器,容器内部持续冒出气泡,加载结束后气泡停止生成、已存在的自然消失。我建议先在本地搭一个最简页面,连样式带逻辑都跑通,再考虑封装成组件。
HTML 结构非常简洁,一个容器接一段文案:
<div class="loading-wrap"> <div class="bubble-stage" id="bubbleStage"></div> <p class="loading-text">正在加载,请稍候...</p> </div>.bubble-stage是气泡的活动区域,需要设置position: relative、overflow: hidden,并且给它一个明确的宽高。overflow: hidden这里非常关键——气泡在生成时会位于容器底部下方 80 像素处,也就是bottom: -80px,如果容器不裁切,气泡会在容器外露出来,破坏整体布局。
.bubble-stage { position: relative; width: 200px; height: 200px; margin: 0 auto; overflow: hidden; border-radius: 16px; background: linear-gradient(180deg, #e8f4fc 0%, #c8e8f8 100%); }背景我用了一个从浅蓝到稍深蓝的垂直渐变,模拟水面下的光线衰减。这个渐变个人建议别用纯白,气泡浅色在纯白背景上反差太小,视觉重心会漂移。主色调用变量维护,方便后续换肤。
3.2 生成气泡的 JavaScript 逻辑:随机参数与节点生命周期
气泡的生成逻辑我写成了一个简单的控制器,包含init和spawn两个公开方法。init负责按配置数量生成第一批气泡,并为每个气泡分配延迟;spawn负责创建单个气泡节点、设置随机参数,然后监听它的动画结束事件,准备回收。
class BubbleLoader { constructor(stage, config) { this.stage = stage; this.config = Object.assign({}, bubbleConfig, config); this.alive = false; // 控制加载状态 } start() { this.alive = true; for (let i = 0; i < this.config.count; i++) { this.spawnWithDelay(i); } } stop() { this.alive = false; } spawnWithDelay(index) { const { delay } = this.config; const randomDelay = rand(delay.min, delay.max) + index * 80; setTimeout(() => this.spawn(), randomDelay); } spawn() { if (!this.alive && this.stage.childElementCount >= 0) return; const bubble = document.createElement("div"); bubble.className = "bubble"; const size = rand(this.config.size.min, this.config.size.max); const left = rand(-10, 90); // 百分比表示水平位置 const duration = rand(this.config.duration.min, this.config.duration.max); const opacity = rand(this.config.opacity.min, this.config.opacity.max); const drift = rand(this.config.drift.min, this.config.drift.max); bubble.style.width = size + "px"; bubble.style.height = size + "px"; bubble.style.left = left + "%"; bubble.style.opacity = opacity; bubble.style.setProperty("--duration", duration + "ms"); bubble.style.setProperty("--drift", drift + "px"); bubble.addEventListener("animationend", () => { bubble.remove(); if (this.alive) this.spawn(); }); this.stage.appendChild(bubble); } } function rand(min, max) { return Math.round(min + Math.random() * (max - min)); }我把动画时长和摆动幅度放到了 CSS 自定义属性(--duration和--drift)里,这样 CSS 的 keyframes 可以直接引用,不需要通过 JS 频繁改 style。这是目前比较推荐的写法:随机参数进了 CSS 变量,样式在 CSS 里统一维护,JS 只负责数值。
3.3 上升与摆动动画的 CSS 实现:关键帧与变量联动
气泡的上升和摆动我拆成两套动画:rise负责垂直位移,drift负责水平摆动。两套动画同时作用于同一个节点,通过animation简写属性组合在一起。
.bubble { animation: rise var(--duration, 4s) ease-in forwards, drift 2.2s ease-in-out infinite alternate; } @keyframes rise { 0% { transform: translateY(0) scale(0.6); opacity: 0; } 12% { transform: translateY(-24px) scale(1); opacity: var(--bubble-opacity, 0.7); } 85% { transform: translateY(-180px) scale(1.02); opacity: var(--bubble-opacity, 0.7); } 100% { transform: translateY(-240px) scale(1.12); opacity: 0; } }注意rise动画我用了forwards填充模式,让气泡在动画结束时保持最终状态,避免最后突然跳回底部造成闪烁。另外translateY的终点值是负的,因为气泡本身定位在容器底部之外(bottom: -80px),要让它完全走出容器可视区,位移值必须比容器高度更大,具体数值要根据容器尺寸动态算。我这里是 200 高的容器配-240px,你可以等比换算。
drift动画用alternate来回摆动,气泡会有一种被水流轻轻推着走的感觉。摆动幅度通过--drift变量控制,但要注意rise动画也在操作transform,两者会冲突。解决办法是用一个嵌套结构:给气泡外层套一个.bubble做垂直位移,内层新增一个.bubble-core做水平摆动,这样两层各管一个维度,永远不会互相覆盖。
3.4 加载结束时的优雅收尾:让气泡自然消散而不是瞬间消失
加载结束后直接把气泡全部移除会显得很突兀。我的做法是把stop()方法设计成“停止生成,但已存在的气泡自然走完剩余生命周期”。也就是说,stop()只把alive置为false,让animationend回调里不再生成新气泡,已经升到一半的气泡继续上升到顶再消失。这样整个 Loading 区域会像一壶水慢慢停止沸腾,气泡逐渐变少,最终清空,体验比直接清空 DOM 平滑得多。
有些场景还需要一个更快的收尾,比如点击“跳过加载”按钮时最好 200 毫秒内全部消失。这时候我给容器加一个.loading-done类,批量把气泡的animation-duration改成当前剩余时间的一半,气泡就会加速上浮破裂,实现“快速放气”的视觉效果。这个细节挺有意思,建议你在项目里试试。
4. 进阶优化:把气泡动效做细腻性能调优与踩坑记录
4.1 性能优化的核心思路:图层、合成器与帧预算
气泡动效的瓶颈通常不在 JS 计算量,而在浏览器渲染流程。任何动画都是每一帧做一次“布局—绘制—合成”的管线处理,管线的不同阶段开销差异巨大。top/left的变化会触发布局计算,因为浏览器需要重新计算元素的位置和大小;transform和opacity的变化则可以在合成器层面完成,不需要走布局环节。同样的动效,优化前后在低端安卓机上的流畅度差距是“卡顿到飞起”和“全速丝滑”的差别。
我在代码里做了三件事来压榨性能。第一,气泡节点全程只改transform和opacity,不碰top/left,水平位置用left只在创建时设置一次,之后不修改。第二,给每个气泡加上will-change: transform, opacity,告诉浏览器提前为这些属性准备合成层,减少运行中反复创建图层的开销。第三,气泡数量控制在最小值,我测过 200 乘 200 容器里 14 个气泡和 28 个气泡的观感区别,28 个确实更密实,但在某些老处理器的手机上帧率会掉到 40 帧以下,最后我定在 18 个左右,视觉和性能的平衡点。
4.2 深浅色模式适配与容器尺寸自适应
气泡效果有个容易翻车的点:加载容器背景和页面底色不协调。在浅色模式下,气泡用蓝色渐变、白色高光没有问题;切到深色模式后,气泡若是半透明蓝色,深色背景下的通透感会完全丢失。
我的适配方案是定义两组 CSS 变量,浅色模式一组,深色模式一组,通过媒体查询切换:
:root { --bubble-bg: radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.35), rgba(64, 156, 255, 0.06) 45%); --stage-bg: linear-gradient(180deg, #e8f4fc 0%, #c8e8f8 100%); } @media (prefers-color-scheme: dark) { :root { --bubble-bg: radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.22), rgba(96, 120, 255, 0.12) 45%); --stage-bg: linear-gradient(180deg, #1c2333 0%, #121826 100%); } }气泡的background直接引用var(--bubble-bg),容器背景引用var(--stage-bg)。这样系统切换深浅模式时,整个动效的明暗关系会跟着调整,不会出现深色页面里有一个浅蓝色玻璃瓶的割裂感。
容器尺寸自适应用百分比加动态计算解决。气泡的left用百分比定位,尺寸上限和容器的min(width, height)绑定,比如配置里size.max由 JS 在初始化时根据容器元素的实际宽高计算,规则是容器边长的一半以内。这样做的好处是:同一套气泡代码放在移动端 200 像素的方形容器里和桌面端 400 像素的圆形容器里,气泡都不会撑破边界,比例始终协调。
4.3 运行中动画的生命周期管理:页面隐藏与组件销毁
单页应用里最常见的问题:切走了路由,气泡还在后台跑。气泡消耗的资源不大,但加载监听器、定时器不清理,页面来回切换几次,会积累出好几个并行运行的 Loading 循环,内存和 CPU 双双浪费。
我在控制器里加了一个destroy()方法,负责三件事:停止生成、移除所有剩余气泡节点、清空所有挂起的setTimeout定时器。组件卸载时调用它,确保不会有游离的定时器和 DOM 节点残留。另外,页面切到后台时,浏览器的requestAnimationFrame自动暂停,但用setTimeout驱动的生成逻辑不受影响,后台跑回来时可能一瞬间冒出多个气泡。处理办法是监听visibilitychange事件,页面重新可见时先清掉一部分现有气泡再继续生成,把积压的一次性释放掉。
注意:
animationend事件在页面切后台时也可能被浏览器挂起,等回到前台时才统一触发,这会导致气泡瞬间全部消失再瞬间全部出现。如果你遇到这个现象,多半是动画事件积压而不是逻辑写错。
5. 常见问题与排查技巧实录
5.1 高频问题速查表:现象、原因与解决办法
做气泡 Loading 过程中最容易碰到的问题,我整理成了下面这张表,大多是我自己踩过之后排查出来的,按出现频率从高到低排:
| 现象 | 直接原因 | 解决办法 |
|---|---|---|
| 气泡卡顿,掉帧明显 | 动画驱动用了top/left而不是transform | 全局搜索替换成transform: translateX/Y,加上will-change |
| 气泡到顶后闪烁一下又回到底部 | animation-fill-mode没设forwards | 设置animation-fill-mode: forwards保持结束状态 |
| 气泡重叠位置一模一样 | 水平位置随机范围过窄且无互斥 | 增加左边界随机范围,加入最小间距互斥逻辑 |
| 气泡在容器外露出半个 | 容器没设overflow: hidden | 给.bubble-stage加overflow: hidden |
| 加载完成后气泡残留不消失 | 没有监听animationend移除节点 | 在动画结束回调里remove()节点 |
| 页面切后台再回来,气泡全部刷新 | animationend积压触发 | 监听visibilitychange重置动画状态 |
| 深色模式下气泡几乎看不见 | 背景色与容器背景没有做适配 | 用 CSS 变量 +prefers-color-scheme切换配色 |
| 气泡上升快慢参差不齐 | duration范围设置太窄 | 把duration.min与duration.max的差距拉大到 2 秒以上 |
最后一行值得多说一句:随机时长的范围如果太窄,比如都集中在 4 到 4.5 秒之间,气泡会像阅兵方阵一样整整齐齐往上走,显得非常机械。把范围拉宽到 3.2 到 5.8 秒,气泡的节奏感立刻就有了,这是参数调节里投入产出比最高的一步。
5.2 一次印象深刻的排查:气泡为何突然加速上浮
最让我头疼的一次问题是:气泡在运行到大约 20 秒后,会突然全部加速上浮,视觉效果像“一群气泡同时逃命”。排查了很久,最终定位到问题出在requestAnimationFrame和setTimeout混合使用上。气泡的上升动画由 CSS 驱动,但生成新气泡的时间轴由setTimeout驱动,两者没有同步。当页面帧率发生波动时,CSS 动画的实际播放速度和setTimeout的时间轴会产生偏移,偏移积累到一定程度,所有的气泡几乎同一时刻触发animationend,然后统一的生成回调又让下一批气泡同一时刻出现。
解决思路是同步生成节奏。我把生成气泡的操作也改成了由 CSS 动画事件驱动:每次animationend触发后,延迟随机 0 到 800 毫秒再生成下一个气泡。这样新气泡的出现周期与现有气泡的消亡节奏绑定,不会出现长时间运行后周期漂移的问题。
5.3 其他值得注意的细节:点击穿透与低功耗模式
气泡节点虽然是半透明视觉,但它们是真实 DOM 元素,会拦截点击事件。加载容器如果出现在弹窗或遮罩层上,气泡区域将无法点击穿透,用户快速点击“取消”按钮时容易误触。解决办法是在气泡节点上设置pointer-events: none,让它不参与事件响应。这是一个很不起眼但实际影响很大的细节,尤其是移动端 H5 页面。
另一个细节是低功耗模式。部分手机系统打开省电模式后,会限制浏览器的动画渲染频率,CSS 动画的帧率会下降,气泡可能会出现“一顿一顿”的感觉。这不是代码问题,是系统策略。我的做法是检测动画帧率,如果连续几秒低于 30 帧,就自动减少气泡数量至原来的一半,同时缩短动画持续时间,让气泡以“简洁模式”继续运行。代码本身不复杂,核心是评估用户体验的底线,与其让用户看到卡顿的完整效果,不如让用户看到流畅的简化效果。
6. 玩法扩展方向:气泡 Loading 还能变成什么
气泡 Loading 只是一个起点,把它的设计语言延伸出去,可以做出很多变体。最常见的是把气泡换成其他上升物体,比如音符、星星、金币,在游戏结算、会员权益展示等场景里非常契合。换汤不换药,生成、驱动、回收的循环结构完全可以复用,只需要把 CSS 里的圆角高光换成对应图形。
更高级的变体是给气泡增加交互响应。我试过在气泡上绑定click事件,点击后气泡立刻破碎并飞溅出几个更小的微粒,这个效果在活动页的“互动按钮”场景里很出彩。实现上只需要在click回调里给气泡加一个.pop类,并在 CSS 里定义破碎动画,同时暂停该气泡原有的上升动画,避免两个动画争夺transform属性。
还有一种方向是让气泡携带信息。把气泡做成半透明的胶囊形状,内部用文字标签展示当前加载进度,比如“正在上传 36%”,气泡上浮的同时数字跟随变化。技术上需要在生成时动态写入内容,但要注意气泡尺寸与文字的适配,以及气泡破裂瞬间文字是否要保留的决策。如果你的产品有较强的动效设计欲,这几个方向都是值得投入时间去做的。
我在实际项目里遇到的另一个高频诉求是把气泡 Loading 做成一个 Web Component 或者框架无关的组件。气泡控制器本身不依赖任何框架,直接操作 DOM,所以封装起来很自由。React 里用一个useEffect包裹控制器生命周期,Vue 里用onMounted和onUnmounted触发start和destroy,结构都很干净。
最后再分享一个我在调参过程中摸索出来的小技巧:测试气泡节奏时,别只看最终效果,试试把页面缩到 50% 再观察一次,再开着开发者工具的性能面板看一遍。缩小窗口会让气泡的视觉密度变大,密度失衡、动画速度过快等问题这时候会暴露得特别明显。我很多次“感觉不错”的版本都是在这两步检查里被打回去重调的。气泡动效讲究的就是一个恰到好处:多一点嫌挤,少一点嫌空,节奏快一点嫌急,慢一点嫌拖。找到一个舒服的手感,比堆砌功能更能体现一个前端做交互动效的基本功。希望这篇拆解能让你少走一些弯路,直接做出让自己满意、用户也喜欢的那版气泡 Loading。