客户基于 Vue2 的 HTML 游戏,资源链接由接口动态返回,需预加载并显示进度条,全部完成后再切入正式界面,保证首屏体验流畅。
效果预览


为什么一定要预加载
- 图片、音频、JSON 配置、WASM 文件体积太大,网络慢时界面直接“裸奔”
- 浏览器对同一域名并发连接数有限,资源并行下载反而拖慢首帧
- 游戏/动画第一帧如果贴图还没到位,会出现“黑块”、“破图”,用户体验瞬间拉垮
解决方案
把接口里拿到的所有图片 URL收集起来,用 JS 原生 Image 对象去一张一张地加载,每加载完一张就更新进度百分比;
当全部加载成功以后,再把 Vue 的根实例(或某个大组件)v-if 切出来,正式显示游戏界面。
核心思路
- 先请求接口,拿到图片地址数组
imgList - 构造一个
loadImage(url)函数,返回一个 Promise,成功时 resolve,失败时 reject - 用
Promise.allSettled并发加载所有图片,在 then 里累加已完成数量,实时算百分比 - 百分比 = 100 时,把
isReady置为 true,Vue 模板里用v-if="isReady"显示真正内容 - 整个加载过程放在 Vue 的
created或mounted钩子里即可
支持音频 / JSON / WASM
核心思想不变,把不同加载逻辑封装成返回 Promise 的函数,然后统一 push 进 tasks 数组即可。
| 资源类型 | 加载方式 |
|---|---|
| 音频 | new Audio(url) 并监听 oncanplaythrough |
| JSON | fetch(url).then(r => r.json()) |
| WASM | WebAssembly.instantiateStreaming(fetch(url)) |
完整代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Vue 资源预加载示例</title>
<style>
/* 加载遮罩 */
#loading {
position: fixed;
inset: 0;
background: #000;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font: 16px/1.5 sans-serif;
}
#loading p {
margin: 0 0 10px;
}
.bar {
width: 60vw;
max-width: 300px;
height: 6px;
background: rgba(255, 255, 255, .25);
border-radius: 3px;
overflow: hidden;
}
.bar-inner {
height: 100%;
width: 0%;
background: #42b883;
transition: width .2s;
}
/* 真正游戏区域,默认隐藏 */
#game {
text-align: center;
padding: 40px;
}
</style>
</head>
<body>
<!-- Vue 根节点 -->
<div id="app">
<!-- 加载中 -->
<div id="loading" v-if="!ready">
<p>资源加载中… {{ percent }}%</p>
<div class="bar">
<div class="bar-inner" :style="{width: percent + '%'}"></div>
</div>
</div>
<!-- 游戏主界面 -->
<div id="game" v-if="ready">
<h1>游戏正式开始!</h1>
<p>这里放你的 Canvas / 图片 / 音频 …</p>
<!-- 示例:把加载好的图片列出来 -->
<img v-for="src in images" :src="src" style="height:100px;margin:5px;">
</div>
</div>
<!-- 1. 引入 Vue 2.x -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.min.js"></script>
<!-- 2. 业务逻辑 -->
<script>
new Vue({
el: '#app',
data: {
ready: false, // 是否加载完毕
percent: 0, // 0~100
images: [] // 最终加载成功的地址,给 v-for 演示用
},
async mounted() {
// 2.1 请求接口拿图片数组
const list = await this.fetchList()
// 2.2 全部加载并更新进度
await this.loadAll(list)
// 2.3 切屏
this.ready = true
},
methods: {
// 请求接口
async fetchList() {
// 这里用 https://picsum.photos 随机图做演示,真实环境换成你自己的接口
const res = await fetch('https://picsum.photos/v2/list?page=1&limit=30')
const data = await res.json()
return data.map(item => item.download_url) // 取出 12 张随机图
},
// 单张图片加载 Promise
loadImg(src) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => resolve(src)
img.onerror = () => reject(src)
img.src = src
})
},
// 批量加载
async loadAll(urls) {
const total = urls.length
let done = 0
const tasks = urls.map(src =>
this.loadImg(src)
.then(() => this.images.push(src)) // 成功就收集
.finally(() => {
done += 1
this.percent = Math.round((done / total) * 100)
})
)
// 哪怕某几张失败也继续
await Promise.allSettled(tasks)
}
}
})
</script>
</body>
</html>
9566

被折叠的 条评论
为什么被折叠?



