人人都会AI编程

图片懒加载、资源预加载

更新时间:2026-07-09

图片往往是页面上体积最大的静态资源,它们在何时加载、如何加载,直接影响着首屏速度和用户流量消耗。同样,对于一些后续页面必然用到的关键资源,提前加载又能让交互体验更流畅。两者并不矛盾,分别解决不同场景下的性能问题。


图片懒加载:只加载看得见的图片

图片懒加载的核心思路是:首屏只加载视口内的图片,其余图片暂时用占位符替代,等到用户滚动到它们附近时再加载。这样能大幅减少首屏的网络请求数量和下载体积,让首屏更快可交互,同时为按流量计费的移动端用户省下不必要的下载。

Vue 中的落地方式有三种主流选择:

1. 原生方案:浏览器级的 loading="lazy"

这是最简单、零依赖的懒加载方式。只需要在 <img> 标签上添加 loading="lazy" 属性,浏览器会自动判断元素是否接近视口并触发加载。

<img src="poster.jpg" loading="lazy" alt="电影海报" />

优点是完全不需要写任何 JavaScript,兼容性在现代浏览器中已经很好(Chrome 77+、Edge 79+、Firefox 75+)。缺点是你无法控制加载阈值、占位图样式、加载失败处理等细节。对于简单的列表页展示,这个方案已经足够实用。

2. 指令封装方案:自定义 v-lazy 指令

当需要更灵活的控制(比如自定义占位图、加载动画、错误回退图)时,可以基于 Intersection Observer API 封装一个自定义指令。

// directives/lazy.js
import defaultPlaceholder from '@/assets/placeholder.png'

export default {
  mounted(el, binding) {
    // 设置初始占位图
    el.src = defaultPlaceholder
    
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          // 进入视口,加载真实图片
          const realSrc = binding.value
          const img = new Image()
          img.src = realSrc
          img.onload = () => {
            el.src = realSrc
            el.classList.add('loaded')
          }
          img.onerror = () => {
            // 可设置错误回退图
            el.src = defaultPlaceholder
          }
          observer.unobserve(el)
        }
      },
      { rootMargin: '50px' } // 提前 50px 开始加载,提升体验
    )
    
    observer.observe(el)
  }
}

在组件中使用:

<img v-lazy="item.coverUrl" alt="文章封面" />

这种封装可以全局注册,之后任何地方只需一行指令即可。相比于第三方库,自己写的指令体积为零,逻辑完全可控。

3. 第三方库:vue-lazyload 等

如果项目对懒加载的需求非常复杂(比如支持背景图懒加载、视频懒加载、自定义加载组件),可以使用社区库如 vue-lazyload。但需要注意,Vue 3 生态下部分老库可能更新不及时,选用时要确认兼容性。多数项目用原生属性或自定义指令就足够了。


资源预加载:偷偷把资源提前备好

资源预加载的思路与懒加载正好相反:某些资源虽然当前页面上没用到,但用户极大概率马上就会需要(比如翻页的下一张图片、弹窗内的大图),提前加载它们,等到真正展示时就能瞬间呈现

预加载的实施方式:

1. <link> 标签预加载

在 HTML 头部通过 <link rel="preload"><link rel="prefetch"> 告诉浏览器提前获取资源。

  • preload:高优先级,用于当前页面马上要用到的资源(比如初始不可见但很快会渲染的字体文件、首屏关键图片)。
  • prefetch:低优先级,用于未来可能导航到的下一个页面的资源(比如下一页的数据、下一页的图片)。
<!-- 提前加载首屏轮播的下一张图片 -->
<link rel="preload" as="image" href="next-slide.jpg" />

<!-- 提前加载用户可能点击进入的详情页数据 -->
<link rel="prefetch" href="detail-page.js" />

在 Vue 中,你可以在 public/index.html 中写死关键资源的 preload,或者利用 vue-metaunhead 等方案动态生成。

2. JavaScript 动态预加载

Image 对象或 fetch 在空闲时间偷偷下载资源。

// 在进入某个路由后,预加载下一张可能需要的大图
const preloadImages = (urls) => {
  urls.forEach(url => {
    const img = new Image()
    img.src = url
  })
}

// 比如商品列表页,鼠标悬停在某个商品上时,预加载它的详情图
function onHoverItem(item) {
  preloadImages([item.detailImage])
}

这种动态预加载可以结合 requestIdleCallbacksetTimeout,在浏览器空闲时执行,避免与主业务争抢网络和计算资源。

3. 路由懒加载 + 魔法注释预加载

当使用 Vue Router 进行路由级别的代码分割时(() => import(...)),可以通过 Webpack 的魔法注释 / webpackPrefetch: true / 让打包工具自动在空闲时间预加载该路由组件。

const DetailPage = () => import(
  /* webpackPrefetch: true */
  '@/views/DetailPage.vue'
)

这样用户访问列表页时,DetailPage 的 JS 和 CSS 就会在后台悄悄下载,点击进入详情页时几乎秒开。这个技巧对提升后面页面的加载速度非常有效,而且配置成本极低。


结合使用的场景决策

  • 首屏不可见、且体积大的图片 → 懒加载(节省首屏带宽)
  • 用户极大概率点击的“下一页”或“详情”的关键图片/脚本 → 预加载(提升后续交互速度)
  • 弹窗内的大图 → 可以在弹窗触发前预加载,也可以在弹窗打开时懒加载边框外的图片,视弹窗触发频率和图片大小而定

在实际项目中,通常会同时使用两种策略:对整个列表的封面图做懒加载,同时对当前浏览的商品下一张细节图做预加载。这样既保证了首屏速度,又保证了浏览体验的流畅。

最后提醒一点:预加载要适度。预加载太多资源会抢占首屏请求的带宽,反而拖慢首屏。优先预加载用户最可能需要的下一张/下一页资源,并尽量在浏览器空闲时进行。