如何实现页面加载进度条?

羽霏 ☘︎ 阅读 47

我在做项目首页,想加个顶部的加载进度条,但不知道怎么监听整体资源加载进度。

试过用 window.onload,但只能知道什么时候加载完,没法拿到中间的进度。也查了 performance.getEntriesByType('resource'),但感觉拿不到实时百分比啊。

现在用的是 Vue 3 + Vite,有没有办法在路由切换或初始加载时显示一个平滑的进度条?比如像 NProgress 那种,但我想自己实现核心逻辑。

我来解答 赞 12 收藏
二维码
手机扫码查看
2 条解答
a'ゞ婧妍
实现页面加载进度条这个需求,在 SPA 应用里其实有点 tricky,因为浏览器并没有提供实时的资源加载百分比 API。

先说最直接的方案:用 NProgress 配合 Vue Router,这是最成熟的做法。

// 安装 nprogress
// npm install nprogress

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

// 配置 NProgress
NProgress.configure({
showSpinner: false, // 隐藏右上角那个转圈
minimum: 0.1, // 最小进度值
easing: 'ease', // 动画缓动
speed: 200 // 动画速度
})

const routes = [
// 你的路由配置
]

const router = createRouter({
history: createWebHistory(),
routes
})

// 路由切换时显示进度条
router.beforeEach((to, from, next) => {
NProgress.start()
next()
})

router.afterEach(() => {
NProgress.done()
})

export default router


这个方案的好处是简单稳定,NProgress 内部已经帮你做好了平滑动画。



如果你就想自己写核心逻辑,我给你一个手写版本,原理是利用 Vue Router 的导航守卫配合 CSS 动画:

/* App.vue 或全局样式 */
.progress-bar {
position: fixed;
top: 0;
left: 0;
height: 3px;
background: #42b883; /* Vue 绿 */
z-index: 9999;
transition: width 0.3s ease-out;
}


// ProgressBar.vue 组件
import { ref, onMounted, onUnmounted } from 'vue'

const progress = ref(0)
const isLoading = ref(false)
let timer = null

// 模拟进度动画
const startProgress = () => {
progress.value = 0
isLoading.value = true

// 用 setInterval 模拟进度,实际项目中
// 你可以根据资源加载状态调整进度
timer = setInterval(() => {
// 模拟非线性进度,越往后越慢
const remaining = 100 - progress.value
progress.value += remaining * 0.1

if (progress.value >= 95) {
clearInterval(timer)
}
}, 100)
}

const done = () => {
progress.value = 100
isLoading.value = false

// 进度条走完后淡出
setTimeout(() => {
progress.value = 0
}, 300)

if (timer) {
clearInterval(timer)
}
}

// 暴露给外部调用
defineExpose({ startProgress, done })


// 在路由守卫中使用
import { useProgressBar } from '@/components/ProgressBar'

router.beforeEach((to, from, next) => {
// 调用进度条开始
useProgressBar().startProgress()
next()
})

router.afterEach(() => {
// 模拟资源加载完成,结束进度条
// 实际可以根据 window.onload 或其他信号来判断
setTimeout(() => {
useProgressBar().done()
}, 500) // 稍微延迟,模拟加载感
})




说点实际的:为什么很难拿到"真正的"实时百分比?

因为浏览器只给你两个状态:loading 和 done,中间的过程不透明。你用 performance.getEntriesByType('resource') 确实能拿到资源列表,但那是已经加载完成的资源列表,没法实时监听单个资源的下载进度。

除非你用 XMLHttpRequest 的 onprogress 事件或者 fetch 的 body.getReader(),但这对于页面本身的资源加载不适用。

所以业界做法都是"模拟进度",NProgress 那种效果其实是假的进度,只是看起来像那么回事。路由切换时你没法知道下一个页面到底要加载多少资源。

我的建议:直接用 NProgress 就完事了,省心省力。如果你想研究原理,上面那个手写版本够你折腾的了。
点赞
2026-03-10 23:03
上官天朝
说实话,真实的资源加载进度基本拿不到精确值,浏览器的 API 限制就在那。实际项目中大多数进度条都是"假装"在加载,用一个定时器模拟进度,真正加载完了直接跳到 100%。

你用 Vue 3 + Vite 的话,分两个场景处理。

第一个场景是路由切换,这个好办,用 Vue Router 的导航守卫配合一个简单的进度条组件就行。写个简单的实现:

// progress.js
import { ref } from 'vue'

const progress = ref(0)
let timer = null
let isDone = false

export function useProgress() {
const start = () => {
progress.value = 0
isDone = false
timer = setInterval(() => {
if (isDone) return
// 模拟进度,越往后越慢
const increment = Math.random() * 10 * (1 - progress.value / 100)
progress.value = Math.min(95, progress.value + increment)
}, 100)
}

const done = () => {
isDone = true
clearInterval(timer)
progress.value = 100
setTimeout(() => {
progress.value = 0
}, 300)
}

return { progress, start, done }
}


路由守卫里这样用:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useProgress } from './progress'

const router = createRouter({ /* 你的路由配置 */ })
const { start, done } = useProgress()

router.beforeEach((to, from, next) => {
start()
next()
})

router.afterEach(() => {
done()
})


第二个场景是首次加载,这个稍微麻烦点。你可以在 index.html 里内联一段 CSS 和 JS,让进度条在 Vue 还没挂载前就显示。Vite 打包后会有一个入口 JS,用 document.readyState 或者监听 load 事件来判断页面加载完成。

有几个安全点要注意。定时器一定要清理,不然内存泄漏,组件销毁时记得 clearInterval。还有进度条组件尽量用固定定位,别让其他元素影响它。如果你用用户输入来控制进度条(虽然不太可能),要做校验,防止恶意注入。

另外,如果你项目里有异步请求依赖,比如路由组件里要 fetch 数据,可以在组件里手动调 start 和 done,配合 Suspense 或者 loading 状态一起用。

基本上这样就能实现一个类似 NProgress 的效果了,代码也不多,自己掌控也更灵活。
点赞
2026-03-02 12:15