前端预加载资源 preload

1,945 阅读5分钟

预加载资源 preload

优化下一个页面显示的其中一个步骤,是可以在当前页面提前加载资源。包括css/'js'/'image'等。这时我们就可以使用到preload了。

preload 提供了一种声明式的命令,让浏览器提前加载指定资源(加载后并不执行),在需要执行的时候再执行。提供的好处主要是

  • 将加载和执行分离开,可不阻塞渲染和 document 的 onload 事件
  • 提前加载指定资源,不再出现依赖的 font 字体隔了一段时间才刷出

一、如何使用 preload?

<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="preload" href="/path/to/style.css" as="style" />

<!-- 或使用脚本动态创建一个 link 标签后插入到 head 头部 -->
<script>
  const link = document.createElement("link");
  link.rel = "preload";
  link.as = "style";
  link.href = "/path/to/style.css";
  document.head.appendChild(link);
</script>

说明:preload 不仅仅支持 css 的预加载,上述只是使用 css,作为样例。那么具体支持哪些呢?

二、注意事项

  1. 避免滥用 preload: 若不确定资源是必定会加载的,则不要错误使用 preload,以免本末倒置,给页面带来更沉重的负担。

  2. 避免混用 preload 和 prefetch preload 和 prefetch 混用的话,并不会复用资源,而是会重复加载。

  3. 避免错用 preload 加载跨域资源。 crossorigin 属性在加载字体的时候是必须的,即便字体没有跨域是在自己公司的服务器上,因为用户代理必须采用匿名模式来获取字体资源。为什么?请见这里阿里:preload 预加载资源

三、不同资源加载的优先级规则

我们先来看一张图:

一共分成五个级别:

  • Highest 最高
  • Hight 高
  • Medium 中等
  • Low 低
  • Lowest 最低

html 主要资源,其优先级是最高的

css 样式资源,其优先级也是最高的

script 脚本资源,优先级不一

前三个 js 文件是写死在 html 中的静态资源依赖,后三个 js 文件是根据首屏按需异步加载的组件资源依赖,这正验证了这个规则。

font 字体资源,优先级不一

css 样式文件中有一个 @font-face 依赖一个 font 文件,样式文件中依赖的字体文件加载的优先级是 Highest; 在使用 preload 预加载这个 font 文件时,若不指定 crossorigin 属性(即使同源),则会采用匿名模式的 CORS 去加载,优先级是 High

看下图对比:

第一条 High 优先级,也就是 preload 的请求:

第二条 Highest 也就是样式引入的请求:

可以看到,在 preload 的请求中,缺少了一个 origin 的请求头字段,表示这个请求是匿名的请求。 让这两个请求能共用缓存的话,目前的解法是给 preload 加上 crossorigin 属性,这样请求头会带上 origin, 且与样式引入的请求同源,从而做到命中缓存:

<link rel="preload" as="font" crossorigin href="https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff" />

四、预加载使用场景

字体提前加载

eb 字体是较晚才能被发现的关键资源中常见的一种。但是在用户体验对前端来说至关重要的现阶段前端开发来说,web 字体对页面的渲染也是至关重要。字体的引用被深埋在 css 中,即便预加载器有提前解析 css,也无法确定包含字体信息的选择器是否会真正作用在 dom 节点上。所以为了减少 FOUT(无样式字体闪烁,flash of unstyled text )需要预加载字体文件,有了 preload,一行代码搞定。

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />

注意:crossorigin 属性在加载字体的时候是必须的,即便字体没有跨域是在自己公司的服务器上,因为用户代理必须采用匿名模式来获取字体资源(为什么会这样呢?)。

动态加载,但不执行

通常我们可能想在当前页去加载下一页的资源,但是在媒婆 preload 的情况下,我们常常使用动态创建 script 标签的形式,但是动态创建 script 标签的话,js 代码会立即执行。在有了preload之后,就可以做到动态加载,延迟执行

let link = document.createElement("link");
link.href = "myscript.js";
link.rel = "preload";
link.as = "script";
document.head.appendChild(link);

上面这段代码可以让你预先加载脚本,下面这段代码可以让脚本执行

let script = document.createElement("script");
script.src = "myscript.js";
document.body.appendChild(script);

基于标记语言的异步加载

<link rel="preload" as="style" href="asyncstyle.css" onload="this.rel='stylesheet'" />

preload 的 onload 事件可以在资源加载完成后修改 rel 属性,从而实现非常酷的异步资源加载。

脚本也可以采用这种方法实现异步加载

难道我们不是已经有了<script async>,<scirpt async>虽好,但却会阻塞 window 的 onload 事件。某些情况下,你可能希望这样,但总有一些情况你不希望阻塞 window 的 onload 。

举个例子,你想尽可能快的加载一段统计页面访问量的代码,但又不愿意这段代码的加载给页面渲染造成延迟从而影响用户体验,关键是,你不想延迟 window 的 onload 事件。

有了 preload, 分分钟搞定。

<link
  rel="preload"
  as="script"
  href="async_script.js"
  onload="let script = document.createElement('script'); script.src = this.href; document.body.appendChild(script);"
/>

五、浏览器兼容性检测

其浏览器支持如下:

const isPreloadSupported = () => {
  const link = document.createElement("link");
  const relList = link.relList;

  if (!relList || !relList.supports) {
    return false;
  }

  return relList.supports("preload");
};

参考:用 preload 预加载页面资源

参考:link 预加载详解

欢迎关注我的公众号,每天定时推送前端相关知识