VuePress 2 添加 HTML 首屏 Loading
VuePress 2 添加 HTML 首屏 Loading
如果 Loading 本身是 Vue 组件,它必须等入口脚本下载、执行并完成应用挂载后才能出现。页面最初那段空白恰好发生在这些步骤之前,因此组件级 Loading 接不住。
我的处理方式是把 Loading 直接写进 VuePress 的 HTML 模板,放在 #app 外面。浏览器拿到 HTML 后就能渲染它,页面资源加载完成时再淡出并移除节点。
作用边界
这只是首屏反馈,不会缩短脚本、样式或图片的下载时间。
适用范围
本文只处理首次打开页面或手动刷新。
VuePress 的站内跳转由客户端路由完成,不会重新请求完整 HTML。若要给站内切页增加 Loading,需要监听路由状态,和本文的模板方案是两套逻辑。
在配置中指定 HTML 模板
VuePress 2 支持分别设置开发和构建模板。在 config.ts 中加入:
templateDev: fileURLToPath(new URL("./templates/dev.html", import.meta.url)),
templateBuild: fileURLToPath(new URL("./templates/build.html", import.meta.url)),对应文件放在:
src/.vuepress/templates/dev.html
src/.vuepress/templates/build.htmldev.html 至少要保留 #app。build.html 还包含 VuePress 构建时使用的占位符:
<!--vuepress-ssr-head-->
<!--vuepress-ssr-styles-->
<!--vuepress-ssr-preload-->
<!--vuepress-ssr-prefetch-->body 中还需要:
<div id="app"><!--vuepress-ssr-content--></div>
<!--vuepress-ssr-scripts-->不要删除模板占位符
这些注释是 VuePress 的构建占位符。VuePress 会把页面内容、样式和脚本注入对应位置,删除后可能得到结构不完整的页面。
把 Loading 放在 #app 前面
先看最小结构关系:
<body>
<div id="page-loader">...</div>
<div id="app"><!-- VuePress app --></div>
</body>实际模板中的 body 结构如下:
<body>
<div id="page-loader" role="status" aria-live="polite">
<div class="page-loader__stack">
<div class="page-loader__animation-wrap">
<div class="loader" aria-hidden="true"></div>
</div>
<p class="page-loader__text">欢迎来到枫桥居,资源太大正在加载中...</p>
</div>
</div>
<div id="app"><!--vuepress-ssr-content--></div>
<!-- hide loader script -->
<!--vuepress-ssr-scripts-->
</body>#page-loader 与 #app 是同级节点。这样做有两个好处:
- VuePress 水合
#app时不会处理 Loading 节点。 - 即使客户端 JavaScript 尚未执行,浏览器也能显示 Loading。
动画和文字使用两个容器。动画中的小球会跳出 .loader 的初始高度,单独设置 .page-loader__animation-wrap 可以给它留出空间,避免文字被顶动。
Loading 动画
动画来自 Uiverse 上 alexruix 的 tame-fly-42。下面保留原始 HTML/CSS 和来源注释。
<div class="loader"></div>/* Loader source: https://uiverse.io/alexruix/tame-fly-42 */
/* From Uiverse.io by alexruix */
.loader {
position: relative;
width: 120px;
height: 90px;
margin: 0 auto;
}
.loader:before {
content: "";
position: absolute;
bottom: 30px;
left: 50px;
height: 30px;
width: 30px;
border-radius: 50%;
background: #2a9d8f;
animation: loading-bounce 0.5s ease-in-out infinite alternate;
}
.loader:after {
content: "";
position: absolute;
right: 0;
top: 0;
height: 7px;
width: 45px;
border-radius: 4px;
box-shadow: 0 5px 0 #f2f2f2, -35px 50px 0 #f2f2f2, -70px 95px 0 #f2f2f2;
animation: loading-step 1s ease-in-out infinite;
}
@keyframes loading-bounce {
0% {
transform: scale(1, 0.7);
}
40% {
transform: scale(0.8, 1.2);
}
60% {
transform: scale(1, 1);
}
100% {
bottom: 140px;
}
}
@keyframes loading-step {
0% {
box-shadow: 0 10px 0 rgba(0, 0, 0, 0),
0 10px 0 #f2f2f2,
-35px 50px 0 #f2f2f2,
-70px 90px 0 #f2f2f2;
}
100% {
box-shadow: 0 10px 0 #f2f2f2,
-35px 50px 0 #f2f2f2,
-70px 90px 0 #f2f2f2,
-70px 90px 0 rgba(0, 0, 0, 0);
}
}放进模板时,我给选择器加上 #page-loader 前缀,防止 .loader 与站内其他组件重名。遮罩层使用固定定位并覆盖视口:
#page-loader {
position: fixed;
inset: 0;
z-index: 2147483647;
display: grid;
place-items: center;
}
#page-loader.page-loader--hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}字体不要阻塞 Loading
Loading 文案是:
欢迎来到枫桥居,资源太大正在加载中...字体栈先写站点使用的 LXGW WenKai,再接系统中文字体:
.page-loader__text {
font-family:
"LXGW WenKai",
"LXGW WenKai Screen",
"Microsoft YaHei",
"PingFang SC",
"Hiragino Sans GB",
sans-serif;
}这里不等待字体文件。LXGW WenKai 尚未下载时,浏览器直接使用 fallback 字体显示文案。Loading 若先等待自定义字体,就失去了首屏提示的作用。
在 window.load 后移除
DOMContentLoaded 只表示 HTML 已完成解析,图片、字体和其他资源可能仍在下载。本文记录的方案在 window.load 后隐藏 Loading:
if (document.readyState === "complete") {
window.setTimeout(hideLoader, 0);
} else {
window.addEventListener("load", hideLoader, { once: true });
}
window.setTimeout(hideLoader, 12000);第二个计时器是 12 秒兜底。某个资源如果长时间没有结束,遮罩也不会永久挡住页面。
hideLoader 负责添加隐藏 class,并在过渡完成后删除节点:
var done = false;
var hideLoader = function () {
if (done) return;
done = true;
var loader = document.getElementById("page-loader");
if (!loader) return;
loader.classList.add("page-loader--hidden");
window.setTimeout(function () {
if (loader.parentNode) loader.parentNode.removeChild(loader);
}, 420);
};done 用来防止 load 事件和兜底计时器重复执行。脚本不调用 document.write,也不改写 #app 的内容。
构建和浏览器验证
先执行生产构建:
npm run docs:build构建通过后,还需要用静态服务器打开输出目录。检查这些场景:
- 页面刚打开时 Loading 立即出现。
window.load触发后遮罩淡出并被移除。- 页面内容可点击,没有透明遮罩残留。
- 控制台没有水合错误。
- 刷新文章页和首页都能正常进入。
- 站内路由跳转不会重复显示首屏 Loading。
- 资源加载失败时,12 秒兜底仍能放行页面。
方案边界
HTML 模板方案解决的是“应用挂载前没有反馈”。若页面加载慢,仍应继续检查资源体积、字体、图片、预加载和构建产物。
站内路由 Loading、骨架屏和真实性能优化也应分别处理。把这些功能都塞进同一个全屏遮罩,反而更难判断页面到底卡在哪里。