如何通過使用優(yōu)先級提示,來控制所有網(wǎng)頁資源加載順序
當(dāng)你打開瀏覽器的網(wǎng)絡(luò)標(biāo)簽時,你會看到大量的活動。資源正在下載,信息正在提交,事件正在記錄,等等。
由于有太多的活動,有效地管理這些流量的優(yōu)先級變得至關(guān)重要。帶寬爭用是真實存在的,當(dāng)所有請求同時觸發(fā)時,有些HTTP請求的優(yōu)先級并不像其他請求那樣高。例如,如果你必須選擇,你可能更希望某人的付款請求成功完成,而不是僅僅表示他們嘗試過的分析請求。而讓你的主要圖片盡快顯示無疑比在頁面底部渲染你的標(biāo)志更為重要。
幸運(yùn)的是,瀏覽器擁有越來越多的工具來幫助優(yōu)先處理所有這些網(wǎng)絡(luò)活動。這些“優(yōu)先級提示”幫助瀏覽器在資源有限時,對哪些請求應(yīng)該優(yōu)先處理做出更少的假設(shè)和更明確的決策。
這是一套有用的工具,當(dāng)它們得到很好的利用時,它們可以對頁面性能產(chǎn)生實質(zhì)性的影響,包括那些越來越重要的核心網(wǎng)絡(luò)指標(biāo)。讓我們探索其中的一些,以及它們最有幫助的一些場景。
這是一套有用的工具,當(dāng)它們得到很好的利用時,它們可以對頁面性能產(chǎn)生實質(zhì)性的影響,包括那些越來越重要的核心網(wǎng)絡(luò)指標(biāo)。讓我們探索其中的一些,以及它們最有幫助的一些場景。
優(yōu)先加載的資源
現(xiàn)代瀏覽器有一個受到良好支持的方法,可以告訴瀏覽器當(dāng)前頁面最終需要哪些資源:<link rel="preload" ... />。當(dāng)它放在文檔的<head>中時,瀏覽器會被指示盡快以“高”優(yōu)先級下載它。
公平地說,瀏覽器中的預(yù)加載掃描器已經(jīng)非常擅長這方面的工作。因此,預(yù)加載通常最適用于晚些時候發(fā)現(xiàn)的資源 - 任何不直接由你的HTML加載的東西,比如通過內(nèi)聯(lián)樣式屬性加載的背景圖像。但它也適用于任何其他可能不像你希望的那樣被瀏覽器優(yōu)先考慮的東西。
例如:默認(rèn)情況下,Chrome 會以非常高的優(yōu)先級加載字體,但如果某人的網(wǎng)絡(luò)連接速度很慢,它會使用備用字體并降低該優(yōu)先級。
考慮一個僅通過CSS @font-face規(guī)則加載的字體:
@font-face {
font-family: "Inter Variable";
src: url("./font.woff2") format("woff2");
}
在加載時,由于網(wǎng)絡(luò)連接慢,該字體獲得了最低的下載優(yōu)先級,盡管它對于頁面的視覺體驗非常重要。
但我們可以通過預(yù)加載該資源來覆蓋瀏覽器的決定:
<head>
<!-- Other stuff... -->
<link rel="preload" href="/font.woff2" as="font">
</head>
現(xiàn)在它更受歡迎了:
你可以直接在鏈接標(biāo)簽上使用 fetchpriority 來明確指示相對優(yōu)先級,這在同時預(yù)加載多個資源時非常有用。
這是一個假設(shè)的場景,你想預(yù)加載兩種字體,但想讓其中一種優(yōu)先于另一種:
<link rel="preload" href="./font-1.woff2" as="font" fetchpriority="low" />
<link rel="preload" href="./font-2.woff2" as="font" fetchpriority="high" />
網(wǎng)絡(luò)活動的結(jié)果會反映這些指示。
何時使用
通常,當(dāng)資源不直接由HTML加載,但對頁面的體驗至關(guān)重要時(例如字體、CSS背景圖像等),使用預(yù)加載。當(dāng)預(yù)加載多種同類型的資源,且你明確知道哪個最重要時,加入fetchpriority屬性。
優(yōu)先化 fetch() 請求
我認(rèn)為,F(xiàn)etch API 是現(xiàn)代網(wǎng)絡(luò)的最佳工具之一。與 XMLHttpRequest 相比,它有一些很好的功能,比如在外發(fā)請求時發(fā)出優(yōu)先信號的能力。
最容易想到的用例是:分析請求。當(dāng)帶寬有限并且有多個請求在執(zhí)行時,瀏覽器會自行決定優(yōu)先級。但我們作為工程師應(yīng)該知道,通常的分析請求應(yīng)該優(yōu)先于頁面目的更為關(guān)鍵的其他請求?,F(xiàn)代的fetch()使這變得簡單。
下面是兩個請求幾乎同時入隊的簡單設(shè)置:
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
});
默認(rèn)情況下,瀏覽器會自動將它們都視為 "高 "優(yōu)先級:
現(xiàn)在,我們要明確地告訴瀏覽器每個請求的優(yōu)先級:
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
+ priority: "high"
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
+ priority: "low"
});
這次,優(yōu)先級是不同的:
可能的擔(dān)憂是"low"優(yōu)先級的請求可能會丟失 - 如果用戶過早離開頁面,請求可能會被取消。這是一個真正的問題。根據(jù)幾個因素,關(guān)閉標(biāo)簽頁或轉(zhuǎn)到下一個頁面可能導(dǎo)致一個重要但相對低優(yōu)先級的請求被中止。
幸運(yùn)的是,fetch() 還接受一個 keepalive 選項。當(dāng)設(shè)置為true時,即使頁面終止,瀏覽器也會完成該請求。
何時使用
當(dāng)你知道多個請求正在并發(fā)執(zhí)行,并且你明確知道哪個最重要(或哪個可以安全地被降級)時,指示fetch()的優(yōu)先級。
優(yōu)先化<img />請求
如果我們不做任何特殊處理,瀏覽器會盡量確定頁面上最重要的圖像。為了說明這一點(diǎn),我加載了以下圖像,它們之間的距離很大,所以只有一個會在"頁面首部"顯示。
<img src="./cat-1.jpeg" />
<div style="height: 5000px"></div>
<img src="./cat-2.jpeg" />
<div style="height: 5000px"></div>
<img src="./cat-3.jpeg" />
瀏覽器發(fā)現(xiàn)了哪個最重要,但這花了一秒鐘。當(dāng)開始下載時,這三者都是“低”優(yōu)先級。但很快,頁面首部的那個切換到了“高”優(yōu)先級。
當(dāng)我為第一張圖片添加fetchpriority屬性時,情況變得更加可預(yù)測:
<img src="./cat-1.jpeg" fetchpriority="high" />
此后,cat-1.jpeg 從一開始就以最高的優(yōu)先級加載。雖然最初令人費(fèi)解,但這是有道理的。瀏覽器非常擅長確定資源的關(guān)鍵性,但它從明確的指示中受益。如果你知道一張圖片很重要,就明確說明。
順便說一句,這個特性與本地圖像延遲加載非常搭,這是現(xiàn)在非常受支持的特性。
<img src="./cat-1.jpeg" fetchpriority="high"/>
<div style="height: 5000px"></div>
<img src="./cat-2.jpeg" loading="lazy" />
<div style="height: 5000px"></div>
<img src="./cat-3.jpeg" loading="lazy" />
有了這個,瀏覽器就知道如何加載圖像,只在合適的時候加載。在我的情況下,它甚至不會開始請求初始加載時屏幕外的圖像。相反,它會等到它們更接近視口。
何時使用
當(dāng)你知道它們對頁面體驗非常重要時,對圖像使用明確的fetchpriority。主圖像是一個很好的開始,它甚至可以影響頁面的核心網(wǎng)絡(luò)指標(biāo) - 特別是LCP(最大內(nèi)容繪制)。
優(yōu)先化 <script /> 標(biāo)簽
頁面上帶有src屬性的任何普通<script />在獲取時都會得到高優(yōu)先級,但這有一個權(quán)衡:在它加載并執(zhí)行之前,它會阻止解析頁面的其余部分。出于這個原因,async屬性很有用。它會以低優(yōu)先級在后臺請求腳本,并在準(zhǔn)備好后立即執(zhí)行。知道這一點(diǎn),以下設(shè)置行為是可預(yù)測的:
<script src="/script-async.js" async notallow="console.log('async')"></script>
<script src="/script-sync.js" notallow="console.log('sync')"></script>
<script>console.log("inline");</script>
異步腳本在優(yōu)先級中被降低:
控制臺確認(rèn),在 async 腳本加載過程中,允許解析和執(zhí)行后續(xù)腳本。
非阻塞,但高優(yōu)先級的腳本
大多數(shù)時候,這種行為都很好。但有時,你可能希望腳本既以“高”優(yōu)先級加載,又異步加載。
一個可能的場景是在落地頁的英雄部分安裝一個小的 SPA。為了保留頁面的核心網(wǎng)絡(luò)指標(biāo),特別是LCP和FID(首次輸入延遲,很快將被下一個繪制的交互所取代),你需要高度優(yōu)先這個腳本(畢竟,它負(fù)責(zé)構(gòu)建和供電你的應(yīng)用)。但同時,你不希望它阻止頁面的其余部分進(jìn)行解析。
所以,我們給它一個fetchpriority:
<script src="/script-async.js" async notallow="console.log('async')" fetchpriority="high"></script>
<script src="/script-sync.js" notallow="console.log('sync')"></script>
<script>console.log("inline");</script>
現(xiàn)在,它以提高的優(yōu)先級下載,同時仍然不阻止頁面的其他部分:
控制臺驗證了這一點(diǎn)。有了更高的優(yōu)先級,異步腳本加載得更快。在這種情況下,甚至比同步和內(nèi)聯(lián)的還要快。
雖然我這里沒有特意玩它,但是,是的,fetchpriority 也適用于延遲的腳本。
何時使用
當(dāng)你提前知道腳本的優(yōu)先級,并且懷疑瀏覽器可能沒有足夠的信息來自行決定時,將 fetchpriority 放在你的腳本上。正如我所提到的,對于你希望以非阻塞、異步的方式加載的腳本,優(yōu)先化它們特別有幫助。
有意使用
很容易對這樣的工具過于熱衷,導(dǎo)致過度使用。所以,要小心 - 這樣做可能會付出代價。正如俗話所說:“強(qiáng)調(diào)一切=強(qiáng)調(diào)無?!笔聦嵣?,過度使用可能實際上使得瀏覽器更難管理網(wǎng)絡(luò)爭用,損害頁面的性能。
MDN 甚至特意在他們的優(yōu)先級提示文檔中指出:
僅在瀏覽器可能無法自動推斷加載資源的最佳方式的特殊情況下使用它。過度使用可能會導(dǎo)致性能下降。
所以,不要因為這些工具存在就覺得有義務(wù)使用它們。小心使用。
回顧:何時提示
這里有很多內(nèi)容,所以讓我們快速回顧一下你可能選擇使用優(yōu)先級提示的時機(jī)。這些都不是詳盡無遺的。只是一些好的開始。
- 當(dāng)你希望瀏覽器知道多個晚些時候發(fā)現(xiàn)的資源,其中一些比其他資源更對頁面至關(guān)重要時,提示預(yù)加載的資源。
- 提示你知道是用戶體驗的關(guān)鍵部分的 fetch() 請求,或者可以安全地被降級以為更重要的請求讓路。
- 提示你希望盡快加載和顯示的首屏圖像。
- 提示對頁面功能至關(guān)重要的腳本,但你不希望阻止頁面的其他部分(包括其他資源)被解析和下載。
讓瀏覽器猜得少些
瀏覽器非常擅長弄清楚如何以及何時下載使我們的頁面運(yùn)行的東西。但它并不總是那么好。它不知道一個頁面存在的原因,也不知道它的各個部分背后的意圖。所以偶爾,它可以使用一些額外的幫助。
這就是為什么這些優(yōu)先級提示存在的原因:為了使指令清晰,并且讓瀏覽器很少有機(jī)會做出錯誤的決策。下次當(dāng)你研究自己應(yīng)用程序的網(wǎng)絡(luò)活動時,記住它們,當(dāng)有意義時,使用它們來幫助使你的頁面性能更加智能。