前端预加载资源的那些坑我帮你踩过了

倚轩(打工版) 优化 阅读 1,920
赞 14 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

预加载资源这事儿,说简单也简单,说复杂也挺复杂。我之前做个项目,页面首屏加载慢得要命,用户流失率高得离谱。后来我把预加载好好搞了一下,效果立竿见影。

前端预加载资源的那些坑我帮你踩过了

我一般这么处理预加载:

<link rel="preload" href="/assets/main.css" as="style">
<link rel="preload" href="/assets/main.js" as="script">
<link rel="preload" href="/assets/hero-bg.jpg" as="image">

这里有个重点:一定要加 as 属性!这个属性的作用是告诉浏览器资源的类型,这样浏览器就能按正确的优先级去加载。我之前踩过一次坑,没加 as 属性,结果资源加载顺序乱七八糟,该快的不快,不该快的瞎快。

还有 preload 和 prefetch 的区别一定要搞清楚。preload 是当前页面马上要用的,优先级高;prefetch 是后续页面可能用的,优先级低。用错了就是浪费带宽。

// 动态预加载的写法,我也经常用
function preloadResource(url, type) {
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = type;
  link.href = url;
  document.head.appendChild(link);
}

// 使用示例
preloadResource('/api/config', 'fetch');
preloadResource('/assets/font.woff2', 'font');

动态加载的场景主要是那些根据用户行为才需要的资源。比如某个弹窗用的图片,用户点开了再预加载也不迟。

这几种错误写法,别再踩坑了

我见过太多错误的写法,简直不忍直视。先说最常见的:

<!-- 错误写法1:不指定as属性 -->
<link rel="preload" href="/styles/main.css">

<!-- 错误写法2:用错as属性 -->
<link rel="preload" href="/fonts/main.woff" as="style">

<!-- 错误写法3:preload用作跨域资源 -->
<link rel="preload" href="https://cdn.example.com/font.woff2" as="font">

第一种错误是最常见的,不加 as 属性的话,浏览器会把资源当 fetch 来处理,优先级很低,而且不会缓存。我之前项目里就是这么写的,导致 CSS 加载时机不对,页面闪屏严重。

第二种错误也很常见,font 写成 style,这样浏览器按 CSS 的方式解析字体文件,肯定报错。这个问题查起来还挺费劲的。

第三种错误涉及到跨域。如果要用 preload 加载跨域资源,必须配 CORS,而且要设置 crossorigin 属性:

<link rel="preload" href="https://jztheme.com/fonts/main.woff2" as="font" crossorigin>

还有一种错误是滥用 preload。很多人觉得反正预加载总比不加载强,于是把所有资源都 preload。这是个大坑!preload 的资源会被强制加载,不管用户要不要用,这就是带宽浪费。

实际项目中的坑

项目上线后我才发现问题,某些低端设备上 preload 反而拖慢了加载速度。原因是同时 preload 的资源太多,超过了浏览器的并发限制,导致阻塞了其他更重要的资源加载。

后来我改成了分批预加载:

// 关键资源先加载
const criticalResources = [
  { url: '/css/critical.css', type: 'style' },
  { url: '/js/critical.js', type: 'script' }
];

// 次要资源延迟加载
const secondaryResources = [
  { url: '/images/lazy-bg.jpg', type: 'image' },
  { url: '/fonts/custom.ttf', type: 'font' }
];

function preloadCritical() {
  criticalResources.forEach(resource => {
    const link = document.createElement('link');
    link.rel = 'preload';
    link.as = resource.type;
    link.href = resource.url;
    document.head.appendChild(link);
  });
}

function preloadSecondary() {
  setTimeout(() => {
    secondaryResources.forEach(resource => {
      const link = document.createElement('link');
      link.rel = 'preload';
      link.as = resource.type;
      link.href = resource.url;
      document.head.appendChild(link);
    });
  }, 1000); // 等关键资源加载完成后再加载
}

preloadCritical();
preloadSecondary();

还有一个坑是字体加载。字体资源如果 preload 时机不对,会导致 FOUT(Flash of Unstyled Text)或者 FOIT(Flash of Invisible Text):

<!-- 字体预加载,同时配合 CSS -->
<link rel="preload" href="/fonts/primary.woff2" as="font" type="font/woff2" crossorigin>

<style>
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/primary.woff2') format('woff2');
  font-display: swap; /* 这个属性很重要 */
}
</style>

font-display: swap 是个救命属性,它能控制字体加载期间的显示行为。不用的话,文本可能会一直空白直到字体加载完成。

移动端的特殊考虑

移动端网络环境不稳定,我一般会检测网络状况再决定是否预加载:

function shouldPreload() {
  // 检测网络状况
  if ('connection' in navigator) {
    const connection = navigator.connection;
    if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
      return false; // 网络太差就不预加载了
    }
  }
  
  // 检测是否启用了数据保护模式
  if (navigator.onLine && window.deviceMemory && window.deviceMemory <= 2) {
    return false; // 低内存设备也不预加载
  }
  
  return true;
}

if (shouldPreload()) {
  // 执行预加载逻辑
  preloadCritical();
}

这个检测逻辑帮我避免了很多移动端的性能问题。有些用户网络本来就差,你还一堆 preload 往下砸,这不是雪上加霜吗。

调试和监控

预加载效果如何,光靠感觉是不行的,得有数据支撑。我在项目里加了这些监控:

// 监控资源加载时间
function monitorPreload(url, startTime) {
  performance.mark(start-${url});
  
  const img = new Image();
  img.onload = () => {
    performance.mark(end-${url});
    performance.measure(load-${url}, start-${url}, end-${url});
    
    const measure = performance.getEntriesByName(load-${url})[0];
    console.log(${url} 预加载耗时: ${measure.duration}ms);
  };
  img.src = url;
}

Chrome DevTools 的 Network 面板也能看出预加载效果。preload 的资源会显示为绿色,表示命中了缓存。如果看到黄色或者红色,说明预加载没起到作用。

以上是我个人对预加载资源的完整讲解,有更优的实现方式欢迎评论区交流。这个优化技巧确实能让用户体验提升不少,但一定要结合具体场景来用,不然适得其反。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论