標籤實現預加載功能詳解

前言

最近在研究 vue-cli 3.0生成的工程,在構建後生成的 index.html裏面發現了下面這種用法:css

<link as=style href=/css/app.f60416c7.css rel=preload>複製代碼
<link as=script href=/js/app.69189fdd.js rel=preload>複製代碼

這就觸到了本人的知識盲區了,本着掃盲的目的,研究了下 link 標籤,發現這個小東西功能仍是挺強大的,上面的就是爲了實現預加載功能,懂點兒英文的,一看見preload 就大體知道了。html

以前也有預加載技術,像 prefetch,subresource 等,關於這二者和 preload 的區別,這是另外的話題了, 感興趣的能夠本身搜一下,不想搜的,你只要知道這兩個跟 preload 相比弱的一逼就好了,就是 prefetch 瀏覽器兼容性方面稍微好一點點,這三個也各有偏重和應用場景,就不詳細介紹了,下面咱們就詳細展開preload 這塊。前端

功能介紹:

preload 是一項新的 web 標準,旨在提升性能,讓 FE 對加載的控制更加粒度化。它讓開發者有自定義加載邏輯的能力,免受基於腳本的加載器所帶來的性能損耗。vue

preload 一個基本的用法就是提早加載資源,儘管大多數基於標記語言的資源能被瀏覽器的預加載器(preloader)儘早發現,但不是全部的資源都是基於標記語言的,好比一些隱藏在 css 和 js 中的資源(字體,圖片等),當瀏覽器發現頁面須要這些資源時,從新走一遍加載執行渲染的過程,會下降用戶體驗,而且對頁面的渲染 形成延遲;git


Preloader 簡介
HTML 解析器在建立 DOM 時若是碰上同步腳本(synchronous script),解析器會中止建立 DOM,轉而去執行腳本。因此,若是資源的獲取只發生在解析器建立 DOM時,同步腳本的介入將使網絡處於空置狀態,尤爲是對外部腳本資源來講,固然,頁面內的腳本有時也會致使延遲。


預加載器(Preloader)的出現就是爲了優化這個過程,預加載器經過分析瀏覽器對 HTML 文檔的早期解析結果(這一階段叫作「令牌化(tokenization)」),找到可能包含資源的標籤(tag),並將這些資源的 URL 收集起來。令牌化階段的輸出將會送到真正的 HTML 解析器手中,而收集起來的資源 URLs 會和資源類型一塊兒被送到讀取器(fetcher)手中,讀取器會根據這些資源對頁面加載速度的影響進行有次序地加載。


預加載的好處:github

  1. 讓瀏覽器提早加載指定資源(這裏預加載完成後並不執行),在須要執行的時候在執行,這樣將加載和執行分開,能夠不阻塞渲染和 window.onload事件。
  1. 提早預加載指定資源,特別是字體文件,不會再出現 font 字體在頁面渲染出來後,才加載完畢,而後頁面字體閃一下變成預期字體。
  1. 帶有 onload 事件,能夠自定義資源在預加載完畢後的回調函數。

涉及屬性介紹:

屬性名web

取值範圍vue-cli

介紹json

as跨域

script:js 腳本font:字體文件style:樣式表audio:音頻video: 視頻document:將被嵌入到<frame>或<iframe>元素內部的頁面image: 圖片fetch:將要經過 fetch 和 XHR 請求獲取的資源好比jsonobject: 將被嵌入到<embed >元素內的文件worker:js 的 web worker 或 share worker

該屬性僅在 link 元素設置了rel=preload 是才能使用。規定了 link 元素要預加載的資源的類型,其取值範圍也限制了哪些資源纔可被預加載。設置了此屬性使瀏覽器可以:1,更精確地優化資源加載優先級。2,匹配將來的加載需求,在適當的狀況下,重複利用同一資源。3,爲資源應用正確的內容安全策略。4,爲資源設置正確的 Accept 請求頭。

href

<url>

指定要加載資源的 URL,可使絕對地址也能夠是相對地址

rel

preload(當前功能相關)

此屬性用於指明被連接的資源相對於當前頁面的關係。屬性值必定是被空格分開的連接類型值。這個屬性最多見的取值是:stylesheet,代表被鏈接資源對當前文檔來講是一個層疊樣式表。當前取值 preload 代表連接資源是一個預加載的資源

type

MIME涵蓋類型

連接資源的 MIME 類型,在瀏覽器進行預加載到時候,這個屬性將會很是有用,瀏覽器將使用 type 屬性來判斷它是否支持這一資源類型,若是支持,將正常預加載,下載將開始,不然對其忽略。

crossorigin

加載字體文件的時候須要用到,詳情往下看

應用場景:

  • 包含媒體

<link>元素有一個很棒的特性是它們可以接受一個media屬性。它們能夠接受媒體類型或有效的媒體查詢做爲屬性值,這將令你可以使用響應式的預加載!

讓咱們來看一個簡單的示例(能夠查看Github上的源代碼在線示例):

<head>  <meta charset="utf-8">  <title>Responsive preload example</title>  <link rel="preload" href="bg-image-narrow.png" as="image" media="(max-width: 600px)">  <link rel="preload" href="bg-image-wide.png" as="image" media="(min-width: 601px)">  <link rel="stylesheet" href="main.css"></head><body>  <header>    <h1>My site</h1>  </header>  <script>    var mediaQueryList = window.matchMedia("(max-width: 600px)");    var header = document.querySelector('header');    if(mediaQueryList.matches) {      header.style.backgroundImage = 'url(bg-image-narrow.png)';    } else {      header.style.backgroundImage = 'url(bg-image-wide.png)';    }  </script></body>複製代碼

你能夠看到咱們在<link>元素中包含了一個media屬性,所以,當用戶在使用較窄屏幕的設備時,較窄的圖片將會被預加載,而在較寬的設備上,較寬的圖片將被預加載。而後咱們仍須要在header元素上附加合適的圖片——經過Window.matchMedia / MediaQueryList 來加以實現(能夠查看Testing media queries一文來了解更多信息)。


  • 字體提早加載

web 字體是較晚才能被發現的關鍵資源中常見的一種。可是在用戶體驗對前端來講相當重要的現階段前端開發來講,web 字體對頁面的渲染也是相當重要。字體的引用被深埋在 css 中,即使預加載器有提早解析 css,也沒法肯定包含字體信息的選擇器是否會真正做用在 dom 節點上。因此爲了減小 FOUT(無樣式字體閃爍,flash of unstyled text )須要預加載字體文件,有了 preload,一行代碼搞定:

<link rel=preload href='font.woff2' as=font type='font/woff2' crossorigin />複製代碼
NOTE :
crossorigin 屬性在加載字體的時候是必須的,即使字體沒有跨域是在本身公司的服務器上,由於用戶代理必須採用匿名模式來獲取字體資源( 爲何會這樣呢?)。
type 屬性能夠確保瀏覽器只獲取本身支持的資源。


  • 動態加載,但不執行

另一個有意思的場景也由於 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);
複製代碼
  • 基於標記語言的異步加載

先看代碼

<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="var script = document.createElement('script'); script.src = this.href; document.body.appendChild(script);">複製代碼
  • 響應式加載

preload 是一個link,根據規範有一個media 屬性(如今 Chrome 還不支持,不過快了),該屬性使得選擇性加載成爲可能。

有什麼用處呢?假設你的站點同時支持桌面和移動端的訪問,在使用桌面瀏覽器訪問時,你但願呈現一張可交互的大地圖,而在移動端,一張較小的靜態地圖就足夠了。

你確定不想同時加載兩個資源,如今常見的作法是經過 JS 判斷當前瀏覽器類型動態地加載資源,但這樣一來,瀏覽器的預加載器就沒法及時發現他們,可能耽誤加載時機,影響用戶體驗和 SpeedIndex 評分。

怎樣才能讓瀏覽器儘量早的發現這些資源呢?仍是 Preload!

經過 Preload,咱們能夠提早加載資源,利用 media 屬性,瀏覽器只會加載須要的資源。

<link rel="preload" as="image" href="map.png" media="(max-width: 600px)">複製代碼
<link rel="preload" as="script" href="map.js" media="(min-width: 601px)">複製代碼


  • http header 實現預加載

Preload 還有一個特性是其能夠經過 HTTP 頭信息被呈現。也就是說上文中大多數的基於標記語言的聲明能夠經過 HTTP 響應頭實現。(惟一的例外是有 onload 事件的例子,咱們不可能在 HTTP 頭信息中定義事件處理函數。)

Link: <thing_to_load.js>;rel="preload";as="script"複製代碼
Link: <thing_to_load.woff2>;rel="preload";as="font";crossorigin複製代碼

這一方式在有些場景尤爲有用,好比,當負責優化的人員與頁面開發人員不是同一人時(也就是說優化人員可能沒法或者不想修改頁面代碼),還有一個傑出的例子是外部優化引擎(External optimization engine),該引擎對內容進行掃描並優化。


  • 瀏覽器特性檢查

前面全部的列子都基於一種假設——瀏覽器必定程度上支持 preload,至少實現了腳本和樣式加載等基本功能。但若是這個假設不成立了。一切都將是然並卵。

爲了判斷瀏覽器是否支持 preload,咱們修改了 DOM 的規範從而可以獲知 rel 支持那些值(是否支持 rel=‘preload’)。

至於如何進行檢查,原文中沒有,但 Github有一段代碼可供參考。

var DOMTokenListSupports = function(tokenList, token) {複製代碼
if (!tokenList || !tokenList.supports) {複製代碼
return;複製代碼
}複製代碼
try {複製代碼
return tokenList.supports(token);複製代碼
} catch (e) {複製代碼
if (e instanceof TypeError) {複製代碼
console.log("The DOMTokenList doesn't have a supported tokens list");複製代碼
} else {複製代碼
console.error("That shouldn't have happened");複製代碼
}複製代碼
}複製代碼
};複製代碼
複製代碼
var linkSupportsPreload = DOMTokenListSupports(document.createElement("link").relList, "preload");複製代碼
if (!linkSupportsPreload) {複製代碼
// Dynamically load the things that relied on preload.複製代碼
}複製代碼

瀏覽器兼容性:

caniuse.com 網站上顯示瀏覽器版本支持狀況以下,目前仍是比較高版本的瀏覽器會支持此功能,不過你們也不要擔憂,在不支持的瀏覽器環境中,這部分標籤會被忽略,能夠作到平穩降級。

相關文章
相關標籤/搜索