Nuxt <NuxtLoadingIndicator> 组件深度指南:页面切换进度条的配置、原理与二次开发
<NuxtLoadingIndicator> 是 Nuxt 内置的页面级进度指示组件,用于在客户端路由切换期间在页面顶部渲染一条进度条,直观反馈页面加载状态。本文基于本仓库中的官方 API 文档、组件源码、配套 composable 实现与测试用例,系统讲解其标准用法、全部 Props 含义、底层 useLoadingIndicator 工作机制,以及如何基于源码实现完全自定义的加载指示器。
用法:把它放进应用外壳或布局中
在 Nuxt 3.9+(本仓库为 Nuxt 4 版本,特性保持一致)中,该组件是一个内置组件,无需手动导入即可在模板中直接使用。官方文档建议把它放在 app.vue 或 app 目录下的布局文件(layouts)中,使其贯穿所有页面导航过程。
在应用外壳中放置的典型写法如下:
<template>
<NuxtLoadingIndicator />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
也可以放进某个共享布局内,例如 app/layouts/default.vue,效果等价于在应用外壳中全局挂载。其摆放位置不影响底层逻辑,因为进度条在源码中以 position: fixed 渲染并锚定在视口顶部(见 nuxt-loading-indicator.ts),始终悬浮在页面内容之上。
需要注意的是:该组件是可选组件,即使不放置它,路由切换功能也完全正常,只是缺少了可见的加载反馈。
触发机制:它如何感知页面正在加载
进度条的显示与隐藏并非由组件内部随机触发,而是通过与 Nuxt 运行时 hooks 联动实现的。源码位于 loading-indicator.ts,当运行于客户端时会订阅以下事件:
page:loading:start:路由导航开始时触发,调用start()让进度条出现;page:loading:end:导航结束(成功或失败)时触发,调用finish()让进度条收尾消失;vue:error:应用运行期出现 Vue 错误时,以错误态调用finish({ error: true }),进度条切换为错误颜色。
而 page:loading:start / page:loading:end 的发出位置在路由插件中(见 router.ts):router.beforeEach 中调用 page:loading:start(第 239-240 行),router.afterEach 中根据导航结果调用 page:loading:end(第 168-170 行)。这意味着只要发生了客户端路由跳转,进度条就会自动经历一次完整的"出现 → 前进 → 结束"生命周期,包括嵌套路由、带 query 的导航以及浏览器前进/后退场景。
测试 loading-indicator.test.ts 对该行为做了详细验证:例如在嵌套页面之间导航、携带 query 参数跳转、通过 router.back() 返回等场景下,进度条都会先显示(opacity: 1)再隐藏(opacity: 0),且 page:loading:end 只被触发一次(第 157-173 行)。这些测试也反向印证了上面描述的 hooks 调用链。
Props:完整参数与默认值
官方文档列出了进度条的主要可配置项,结合组件源码(nuxt-loading-indicator.ts 中的 props 定义),所有参数及其默认值如下:
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
color |
string | boolean |
三色渐变(见下) | 进度条颜色,可设为 false 关闭显式颜色样式 |
errorColor |
string |
红系渐变(见下) | 当发生错误时进度条切换成的颜色 |
height |
number |
3 |
进度条高度(像素) |
duration |
number |
2000 |
预估的加载时长(毫秒),用于计算进度推进速度 |
throttle |
number |
200 |
出现/隐藏的节流延时(毫秒),用于避免闪烁 |
hideDelay |
number |
500 |
到达 100% 后到开始隐藏的延迟(毫秒),源码补充项 |
resetDelay |
number |
400 |
完全隐藏后进度值重置为 0 的延迟(毫秒),源码补充项 |
estimatedProgress |
function |
内置反切曲线 | 自定义进度估算函数,见下文 |
颜色参数的默认值与配置示例
文档说明 color 可被设为 false 以关闭显式颜色,从而让进度条颜色完全由你自己的 CSS 类控制。未配置时的默认样式可以在源码中直接看到:
- 默认
color为repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%); - 默认
errorColor为repeating-linear-gradient(to right,#f87171 0%,#ef4444 100%)。
实际渲染时,进度条根元素上始终带有类名 nuxt-loading-indicator(渲染实现见源码 L65-83),因此如果设置 color=false,你可以用一条全局 CSS 规则(如 .nuxt-loading-indicator { background: ... })接管其外观。
常见的基础配置示例:
<template>
<NuxtLoadingIndicator
color="repeating-linear-gradient(to right,#42b883 0%,#35495e 100%)"
error-color="#e74c3c"
:height="4"
:duration="3000"
:throttle="100"
/>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
estimatedProgress:控制进度逼近曲线
文档明确指出,默认情况下 Nuxt 在进度接近 100% 时会"减速退让"(back off),避免长页面加载时进度条过早显示满格。其默认实现可在 composable 源码中看到(defaultEstimatedProgress 函数):
function defaultEstimatedProgress (duration: number, elapsed: number): number {
const completionPercentage = elapsed / duration * 100
return (2 / Math.PI * 100) * Math.atan(completionPercentage / 50)
}
这是基于反正切函数 arctan 的平滑曲线:进度前期增长较快,越接近 100% 增长越慢,从而真实反映"长任务未知时长"的特性。若你希望采用线性推进或自定义策略,可通过 estimatedProgress 传入你自己的估算函数,它接收两个参数——加载条的 duration 与已流逝时间 elapsed(单位均为毫秒),并返回 0~100 之间的数值:
<script setup lang="ts">
function myEstimate (duration: number, elapsed: number) {
return Math.min(100, (elapsed / duration) * 100) // 线性估算
}
</script>
<template>
<NuxtLoadingIndicator :estimated-progress="myEstimate" />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
Slots:默认插槽自定义内部内容
官方文档说明,可以通过默认插槽向进度条传入自定义 HTML 或组件。结合源码(组件渲染逻辑 L83)可见,插槽内容会被渲染在承载渐变背景的容器内部。例如可以在进度条位置放置自己的文本或徽标组件:
<template>
<NuxtLoadingIndicator>
<span style="position: fixed; top: 0; right: 8px; font-size: 12px">loading…</span>
</NuxtLoadingIndicator>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
需要注意的是,容器背景本身是一个基于 backgroundSize 与 scaleX(progress%) 组合实现的进度视觉,插入的内容不会自动跟随进度移动,通常用于在顶部区域叠加品牌元素或文案。
进阶:通过 useLoadingIndicator 手动控制进度
官方文档指出,可以通过 useLoadingIndicator composable 拿到进度条底层实例,从而在任意组件中手动触发 start / finish。这在"需要等待非路由型异步任务"(例如按钮触发的长请求)时非常有用。
返回的状态与方法
该 composable 挂载于全局 Nuxt 实例(nuxtApp._loadingIndicator),并暴露如下只读状态与方法(见 loading-indicator.ts 的类型定义与 useLoadingIndicator 实现 L172-190):
isLoading:Readonly<ShallowRef<boolean>>,当前是否处于加载中;error:Readonly<ShallowRef<boolean>>,是否处于错误态;progress:Readonly<ShallowRef<number>>,当前进度 0~100;start():置isLoading为 true 并开始推进进度,可传{ force: true }跳过节流立即显示;set(value):把进度设到指定值,同样支持{ force: true };finish():把进度置 100、停止所有定时器并在 500ms 后复位,支持{ force: true }(立即复位)与{ error: true }(以错误态收尾并切换errorColor);clear():清除 composable 内部使用的定时器与动画帧,供finish()内部调用。
它同样会自动监听上面提到的 page:loading:start / page:loading:end hooks 来改变自身状态,因此手动调用与路由导航驱动的进度不会产生逻辑冲突。
手动触发进度的示例
模拟"页面某数据请求较慢"的场景:
<script setup lang="ts">
const { start, finish, error } = useLoadingIndicator()
async function loadHeavyData () {
start({ force: true })
try {
const data = await $fetch('/api/heavy-data')
// ...处理数据
finish()
} catch (e) {
finish({ error: true })
}
}
</script>
<template>
<button @click="loadHeavyData">加载数据</button>
</template>
传入 { force: true } 后 start() 的效果等价于 set(0, { force: true }),即立刻把进度置为 0 并马上显示,不经过默认 200ms 的节流等待。
实现原理:进度推进与显隐的完整生命周期
了解内部实现有助于精确调整参数或编写替代实现。核心逻辑全部集中在 createLoadingIndicator 工厂函数 中,大致分四步:
- 启动(set/start):先清掉旧定时器,将
progress设为起点值,然后等待throttle(默认 200ms)——若导航在节流窗口内就结束,进度条根本不会出现,从而避免快速跳转时顶部一闪而过。若传force: true则节流时间视为 0 立即显示。 - 推进(_startProgress):使用
requestAnimationFrame驱动每帧计算estimatedProgress(duration, elapsed),并夹在 0~100 之间写入progress。默认的反正切曲线保证越接近 100% 推进越慢。 - 结束(finish):直接把
progress置 100,取消动画帧与定时器;普通结束走_hide()——等hideDelay(500ms)后把isLoading置 false,再过resetDelay(400ms)把进度归零,为下一次导航做准备;错误结束时同时把error置 true 以触发errorColor。 - 清理:组件卸载或作用域销毁时,订阅的 hooks 会被取消,定时器与动画帧一并清理(依赖计数
_loadingIndicatorDeps归零后删除全局实例)。
渲染层则利用 CSS transform: scaleX(...) 配合 transform-origin: left 从左侧展开,并用 transition: transform 0.1s, height 0.4s, opacity 0.4s 平滑过渡;不加载时透明度为 0(见组件源码 L65-83)。组件同时通过 expose 暴露了 progress、isLoading、error、start、finish、clear,方便父组件通过模板 ref 直接调用。
自定义:基于源码构建专属加载指示器
由于官方明确将"完全自定义"的途径指向其源码(nuxt-loading-indicator.ts),你可以把 useLoadingIndicator 直接组合进自己的组件——这是一个更推荐的方式,它复用了 hooks 联动与全局单例,无需自己订阅路由事件:
<script setup lang="ts">
// 复用同一全局 loading 实例,自动感知路由导航与 vue:error
const { progress, isLoading, error } = useLoadingIndicator()
</script>
<template>
<div
class="my-progress"
:class="{ visible: isLoading, 'is-error': error }"
:style="{ width: progress + '%' }"
/>
</template>
<style scoped>
.my-progress {
position: fixed;
top: 0;
left: 0;
height: 2px;
background: #42b883;
opacity: 0;
transition: width 0.1s ease, opacity 0.4s ease;
}
.my-progress.visible { opacity: 1; }
.my-progress.is-error { background: #e74c3c; }
</style>
然后在 app.vue 中替换官方组件即可获得外观完全由自己掌控、行为与官方一致的加载体验:
<template>
<MyProgress />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
小结与验证路径
- 标准接入:将
<NuxtLoadingIndicator />放入 app.vue 应用外壳 或 layouts 布局,即可在所有页面导航期间自动展示顶部进度条; - 常用调参:通过
color/errorColor/height控制外观,duration/throttle/estimatedProgress控制速度与节奏,color=false时可用.nuxt-loading-indicator类完全接管样式; - 手动控制:借助
useLoadingIndicator的start/set/finish在自定义异步流程中驱动进度并区分错误态; - 底层原理:进度条由路由插件在导航前后触发的
page:loading:start/page:loading:endhooks 驱动,内部以requestAnimationFrame计算进度,其默认估算曲线为反正切退让曲线,避免长加载过早显示 100%。
如果希望进一步核对实现细节,可依次阅读:组件源码、useLoadingIndicator 实现、触发 hooks 的路由插件,以及覆盖嵌套路由/前进后退等真实场景的单元测试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00