【第1159期】CSS预加载Preload

前言

看天气预报,今天好多地方都开始下雪了。今日早读文章由@李斌分享。

正文从这开始~

Preload 作为一个新的web标准,旨在提高性能和为web开发人员提供更细粒度的加载控制。Preload使开发者能够自定义资源的加载逻辑,且无需忍受基于脚本的资源加载器带来的性能损失。

在 HTML 代码中,它看上去大概是下面这样的一段声明式获取指令(declaratiev fetch directive)。

 rel=“preload”>

拿我们的话来说,通过这一方法我们告诉浏览器开始获取某一特定资源,毕竟我们是作者,知道浏览器很快就会用到这一资源。

与现有的类似技术的区别

事实上,关于预加载,我们已经有,而且浏览器支持情况还不错。

【第1159期】CSS预加载Preload_第1张图片

除此之外,Chrome还支持过

但Preload与这两者不同,的作用是告诉浏览器加载下一页面可能会用到的资源,注意,是下一页面,而不是当前页面。因此该方法的加载优先级非常低(自然,相比当前页面所需的资源,未来可能会用到的资源就没那么重要了),也就是说该方式的作用是加速下一个页面的加载速度。

的设计初衷是处理当前页面,但最后还是壮烈牺牲了。因为开发者无法控制资源的加载优先级,因此浏览器(其实也只有 Chrome 和基于 Chrome 的浏览器)在处理此类标签时,优先级很低,到底有多低呢?这么说吧,在大多数情况下,用了等于没用。

为什么 Preload 更好

Preload是为处理当前页面所生,这点和 subresource 一样,但他们之间有着细微且意义重大的区别。Preload 有 as 属性,这让浏览器可做一些 subresource 和 prefetch 无法实现的事:

浏览器可以设置正确的资源加载优先级,这种方式可以确保资源根据其重要性依次加载, 所以,Preload既不会影响重要资源的加载,又不会让次要资源影响自身的加载。

浏览器可以确保请求是符合内容安全策略的,比如,如果我们的安全策略是Content-Security-Policy: script-src 'self',只允许浏览器执行自家服务器的脚本,as 值为 script 的外部服务器资源就不会被加载。

浏览器能根据 as 的值发送适当的 Accept 头部信息

浏览器通过 as 值能得知资源类型,因此当获取的资源相同时,浏览器能够判断前面获取的资源是否能重用。

Preload 的与众不同还体现在 onload 事件上(至少在 Chrome 中,prefetch 和 subresource 是不支持的)。也就是说你可以定义资源加载完毕后的回调函数。

  rel="preload" href="..." as="..." onload="preloadFinished()">

此外,preload 不会阻塞 windows 的 onload 事件,除非,preload资源的请求刚好来自于会阻塞 window 加载的资源。

结合上面所有这些特征,preload 给我们带来了一些以前不可能实现的功能。

资源的提前加载:

preload 一个基本的用法是提前加载资源,尽管大多数基于标记语言的资源能被浏览器的预加载器(Preloader)尽早发现,但不是所有的资源都是基于标记语言的,比如一些隐藏在 CSS 和 Javascript 中的资源。当浏览器发现自己需要这些资源时已经为时已晚,所以大多数情况,这些资源的加载都会对页面渲染造成延迟。

Preloader 简介

HTML 解析器在创建 DOM 时如果碰上同步脚本(synchronous script),解析器会停止创建 DOM,转而去执行脚本。所以,如果资源的获取只发生在解析器创建 DOM时,同步脚本的介入将使网络处于空置状态,尤其是对外部脚本资源来说,当然,页面内的脚本有时也会导致延迟。

预加载器(Preloader)的出现就是为了优化这个过程,预加载器通过分析浏览器对 HTML 文档的早期解析结果(这一阶段叫做“令牌化(tokenization)”),找到可能包含资源的标签(tag),并将这些资源的 URL 收集起来。令牌化阶段的输出将会送到真正的 HTML 解析器手中,而收集起来的资源 URLs 会和资源类型一起被送到读取器(fetcher)手中,读取器会根据这些资源对页面加载速度的影响进行有次序地加载。

现在,有了 preload,你可以通过一段类似下面的代码对浏览器说,”嗨,浏览器!这个资源你后面会用到,现在就加载它吧。“

 rel="preload" href="late_discovered_thing.js" as="script">

as 属性的作用是告诉浏览器被加载的是什么资源,可能的 as 值包括:

  • “script”

  • “style”

  • “image”

  • “media”

  • “document”

更多请参考fetch spec

忽略 as 属性,或者错误的 as 属性会使 preload 等同于 XHR 请求,浏览器不知道加载的是什么,因此会赋予此类资源非常低的加载优先级。

对字体的提前加载

web 字体是较晚才能被发现的关键资源(late-discovered critical resources)中常见的一类 。web 字体对页面文字的渲染资至关重要,但却被深埋 CSS 中,即便是预加载器有解析 CSS,也无法确定包含字体信息的选择器是否会真正应用在 DOM 节点上。理论上,这个问题可以被解决,但实际情况是没有一个浏览器解决了这个问题。而且,即便是问题得到了解决,浏览器能对字体文件做出合理的预加载,一旦有新的 css 规则覆盖了现有字体规则,前面的预加载就多余了。

总之,非常复杂。

但有了 preload 这个标准,简单的一段代码就能搞定字体的预加载。

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

需要注意的一点是:crossorigin 属性是必须的,即便是字体资源在自家服务器上,因为用户代理必须采用匿名模式来获取字体资源。

type 属性可以确保浏览器只获取自己支持的资源。尽管Chrome 支持 WOFF2,也是目前唯一支持 preload 的浏览器,但未来或许会有更多的浏览器支持 preload,而这些浏览器支不支持 WOFF2 就不好说了。

动态加载,但不执行

另外一个有意思的场景也因为 preload 的出现变得可能——当你想加载某一资源但却不想执行它。比如说,你想在页面生命周期的某一时刻执行一段脚本,而你无法对这段脚本做任何修改,不可能为它创建一个所谓的 runNow()函数。

在 preload 出现之前,你能做的很有限。如果你的方法是在希望脚本执行的位置插入脚本,由于脚本只有在加载完成以后才能被浏览器执行,也就是说你得等上一会儿。如果采用 XHR 提前加载脚本,浏览器会拒绝重用这段脚本,有些情况下,你可以使用 eval 函数来执行这段脚本,但该方法并不总是行得通,也不是完全没有副作用。

现在有了 preload,一切变得可能

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

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

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

基于标记语言的异步加载

先看代码

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

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

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

难道我们不是已经有了


type="text/javascript" src="//cdn.cn/Page1-B.js" defer>

最后,为你推荐

【第1053期】Preload,Prefetch 和它们在 Chrome 之中的优先级

关于本文
作者:@李斌
原文:https://zhuanlan.zhihu.com/p/32561606

0?wx_fmt=jpeg

【第1155期】如何快速融入新团队?


【第1151期】技术的热门度曲线

你可能感兴趣的:(【第1159期】CSS预加载Preload)