用 HTML + Vue 手写一个资源加载进度条(附完整代码)

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

效果预览

image-20251024143814071

image-20251024143846475

为什么一定要预加载

  • 图片、音频、JSON 配置、WASM 文件体积太大,网络慢时界面直接“裸奔”
  • 浏览器对同一域名并发连接数有限,资源并行下载反而拖慢首帧
  • 游戏/动画第一帧如果贴图还没到位,会出现“黑块”、“破图”,用户体验瞬间拉垮

解决方案

把接口里拿到的所有图片 URL收集起来,用 JS 原生 Image 对象去一张一张地加载,每加载完一张就更新进度百分比;

当全部加载成功以后,再把 Vue 的根实例(或某个大组件)v-if 切出来,正式显示游戏界面。

核心思路

  1. 先请求接口,拿到图片地址数组 imgList
  2. 构造一个 loadImage(url) 函数,返回一个 Promise,成功时 resolve,失败时 reject
  3. Promise.allSettled 并发加载所有图片,在 then 里累加已完成数量,实时算百分比
  4. 百分比 = 100 时,把 isReady 置为 true,Vue 模板里用 v-if="isReady" 显示真正内容
  5. 整个加载过程放在 Vue 的 createdmounted 钩子里即可

支持音频 / JSON / WASM

核心思想不变,把不同加载逻辑封装成返回 Promise 的函数,然后统一 push 进 tasks 数组即可。

资源类型加载方式
音频new Audio(url) 并监听 oncanplaythrough
JSONfetch(url).then(r => r.json())
WASMWebAssembly.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>
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值