页面刷新时的 Load 动画是如何实现的
前言
之前在看自己博客刷新的时候,发现页面总会先出来一个短暂的 Load 动画:有时是全屏遮罩,有时是进度条,有时只是中间一个在转的小图标。
一开始我以为这是浏览器或者 Hexo 默认带的效果,后来翻了一下 Butterfly 的源码,才发现它其实是主题自己加的一层“过渡界面”。这篇文章就顺着这条线,记录一下它到底是怎么实现的。
它从哪里进入页面
Butterfly 会在全局布局里直接注入加载层。换句话说,页面刚开始渲染的时候,body 里面就已经先放进去了这层结构。
关键入口在布局文件里:
1 | body |
这个 index 入口会再根据配置选择不同的加载方案:
1 | if theme.preloader.enable |
所以我现在看到的刷新动画,其实就两种形态:
source: 1时是全屏 preloadersource: 2时是 pace.js 进度条
全屏 Load 动画怎么工作
如果用的是全屏 preloader,页面会先渲染出一个固定定位的遮罩层:左右两块背景,加中间那个 spinner。效果很简单,但第一眼挺有存在感。
对应的核心结构如下:
1 | #loading-box |
这层样式通过固定定位把整个视口盖住,保证页面还没准备好之前,用户看到的不是半成品内容,而是一个完整的加载状态。
CSS 的关键点有三个:
#loading-box以及左右背景块都是position: fixedspinner-box覆盖在正中央,展示旋转动画- 当加上
.loaded类后,左右背景会向两侧平移并退出视口,spinner 也会被隐藏
看到这里我就基本明白了:它并不是靠复杂的 JS 一帧一帧去画动画,而是切换 class,让 CSS 的 transition 和 keyframes 自己跑起来。
什么时候结束加载
真正决定动画什么时候消失的,是一段内联脚本。
它做了两件事:
1. 先进入加载状态
脚本一开始会执行 initLoading():
1 | initLoading: () => { |
这里会先把 body 的滚动锁住,避免页面还没加载完的时候,用户就已经开始乱滚导致视觉闪动。
2. 再在合适时机结束加载
随后它会根据页面状态决定什么时候移除加载层:
1 | if (document.readyState === 'complete') { |
这段逻辑其实挺典型,我自己看下来就是:
DOMContentLoaded负责在 DOM 结构可用时尽快结束load负责等图片、样式、脚本这些资源真正加载完setTimeout是兜底,防止某些资源出问题后加载层一直挂着不消失
最终执行 endLoading() 时,会恢复滚动并给加载层加上 .loaded:
1 | endLoading: () => { |
等 loaded 类加上去之后,CSS 动画开始生效,左右背景滑出,整个 Load 动画也就结束了。这个过程很干脆,没有多余的逻辑。
为什么刷新时特别明显
“刷新页面”这个场景和“站内切页”不太一样。
刷新时浏览器会重新请求整页,主题的加载层也会从最初就出现在 HTML 里,所以用户通常会先看到一个完整的过渡界面,等页面就绪后再被移除。
如果主题启用了 PJAX,站内跳转又会多一层处理:
1 | btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init') |
这也意味着,不只是首次刷新会出现动画,后续的局部切页同样会沿用这一套加载逻辑。
如果想改成进度条
Butterfly 还预留了 pace.js 方案。它不会显示全屏遮罩,而是显示一条页面顶部的进度条。
对应逻辑更简单:
1 | script. |
如果你更偏好轻量、克制一点的视觉效果,我觉得把 preloader.source 切到 2 会更舒服一点。
配置入口
这部分配置在主题配置文件里:
1 | preloader: |
如果要自己改配置,我觉得最需要关注的还是这两个点:
enable控制是否启用加载动画source控制是全屏遮罩还是进度条
