🔥 Day 02 - 前端性能优化全栈实战:资源加载篇

269 阅读4分钟

🧭 系列目标:100 篇
✅ 已完成:1 篇
📍 当前篇章:Day 02 - 资源加载优化:图片、CSS、字体、JS 全面实战

网站首屏加载慢、白屏时间长、布局跳动频繁……这些都可能与你资源加载的方式息息相关。本文将以结构化视角,深入剖析前端中图片、CSS、字体和 JS 的资源加载优化策略,并提供相关方法。

1. 图片资源优化

在现代 Web 应用中,图片往往占据页面资源体积的 60%~80%。如果图片加载没做好,会出现:

  • 首屏加载慢,影响用户体验和转化

  • 滚动卡顿,尤其是瀑布流或大图浏览页

  • 页面跳动,尤其在图片加载完成前布局被打乱

  • 数据流量消耗大,影响移动端用户成本

所以,图片优化不仅是节省流量,而是用户体验的保障

1.1 使用现代图片格式(WebP、AVIF)

<picture>
  <source srcset="banner.webp" type="image/webp">
  <img src="banner.jpg" alt="Banner 图像">
</picture>

📌 优点:浏览器支持自动选择最佳格式;提升图片压缩比,减少体积。

1.2 懒加载(Lazy Load)

原生方案:

<img src="image.jpg" loading="lazy" alt="惰性加载图像">

IntersectionObserver 示例(兼容更旧浏览器):

手动监听滚动 + IntersectionObserver,实现自定义懒加载

const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));

1.3 使用 LQIP 占位(Base64 小图)

低质量图(LQIP)嵌入可避免图片加载前的跳动:

<img
  src="data:image/jpeg;base64,/9j/4AAQSkZJ..."
  data-src="highres.jpg"
  width="600"
  height="400"
  class="lazy"
/>

或者在背景图中使用:

.skeleton {
  background-image: url('data:image/webp;base64,UklGRh...');
  background-size: cover;
}

JavaScript 懒加载后替换高清图:

const img = document.querySelector('.lazy')
img.src = img.dataset.src

1.4 使用 Web Worker 解耦图像处理逻辑

对于图像缩放、滤镜、调整亮度等高开销计算:

主线程

const worker = new Worker('resize-worker.js')
worker.postMessage({ image, width: 300 })
worker.onmessage = (e) => updateCanvas(e.data)

Worker 文件(resize-worker.js)

self.onmessage = (e) => {
  const { image, width } = e.data
  const resized = resizeImage(image, width)
  self.postMessage(resized)
}

可显著降低主线程卡顿,提升复杂图像处理体验。

1.5 CDN 加速图片加载

  • 使用 CDN 缓存图片,靠近用户的节点提供更快响应。

  • 推荐服务:七牛、阿里云 OSS、Cloudflare Images。

1.6 响应式图片加载(Responsive Images)

  • 为不同屏幕密度和尺寸提供不同尺寸图片:

    响应式图像

2. CSS 加载优化

传统做法中,所有 CSS 文件通过 <link> 引入,浏览器必须下载、解析完 CSS 才开始渲染页面。这带来了两个问题:

  1. 阻塞渲染:CSS 是渲染阻塞资源,没加载完,页面白屏。

  2. 🧱 加载冗余:首屏渲染只用到了其中一小部分样式,其它全是浪费。

2.1 Critical CSS 提取(首屏样式内联)

<style>
  .header { height: 60px; background: #fff; }
  .hero { display: flex; height: 100vh; align-items: center; }
</style>

主样式异步加载,避免阻塞渲染:

<link rel="preload" href="main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="main.css"></noscript>

工具推荐:

  • critters:适合 React/Vite

  • penthouse:适合自定义构建流程

2.2 路由级别样式(Route-specific CSS)

针对 SPA,将样式随页面组件懒加载:

// Vue 路由懒加载
component: () => import('@/views/Home.vue')

// Vite + Vue3 自动提取局部 CSS

这样每个路由只加载自身所需样式,减小初始 CSS 体积。

3. 字体加载优化

3.1 字体渲染优化:避免 FOIT(文字不可见)

@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2');
  font-display: swap;
}

📌 使用 font-display: swap 可实现 FOUT(字体替换时闪烁),确保首屏内容不空白。

3.2 字体子集化(子集裁剪)

只保留使用到的中文字符:

npx fontmin myfont.ttf > myfont.min.ttf

或使用自动工具:

npx glyphhanger --subset=/index.html --formats=woff2 --LATIN

4. JavaScript 加载优化

4.1 使用 defer 和 async

<script src="/app.js" defer></script>
<script src="/analytics.js" async></script>
  • defer:页面解析完才执行,适用于主逻辑脚本

  • async:加载即执行,适用于无依赖脚本如监控、广告

4.2 动态导入(Code Splitting)

const Chart = () => import(/* webpackChunkName: "chart" */ './Chart.vue')

📌 搭配路由懒加载效果显著,减少首页包体积。

4.3 Tree Shaking & Scope Hoisting

确保使用 ES Module 并启用 sideEffects: false

// package.json
{
  "sideEffects": false
}

4.4 SSR 优化 JS 首屏加载体验

  • Vue(Nuxt)、React(Next)通过 SSR 缓解 JS 渲染白屏。

4.5 请求合并与缓存优化

  • 利用 HTTP/2 多路复用 + keep-alive 合理合并请求。

  • 设置合适的 cache-controletag 提升重复访问性能。

5. 预加载技术进阶

5.1 Preload 当前页核心资源

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="home.css" as="style">

5.2 Prefetch 后续可能资源

<link rel="prefetch" href="next-page.js">

📌 适用于用户空闲时加载“下一个页面”资源。

6. 总结与工具推荐

✅ 对照表

🔧 工程化自动实现建议

🧠 小结

前端资源加载优化是影响页面性能的基石之一。只有将资源按需、分阶段、有策略地加载,才能做到“快如闪电、稳如老狗”的体验。别忘了,优化不仅仅是压缩体积,更是优化加载路径与阻塞点。