動態

詳情 返回 返回

【十五】CSS性能 - 動態 詳情

前言

本篇章來源於https://juejin.cn/post/7077347573740077069,內容只是經過咀嚼便於自己理解。

面試回答

1.重繪重排:簡單來説重繪就是改變某個節點的樣式,重排就是改變某些節點的佈局,比如元素尺寸變動、元素位置變動以及瀏覽器窗口變動。所以重排一定會引起重繪,而重繪不一定引起重排。減少重繪重排的方式主要是通過集中修改css來完成,比如將樣式統一放在class當中,在離線、脱離文檔流的DOM元素中進行修改最後一起顯示等。

2.性能優化:前端的性能優化有兩個優化方向,一種是頁面渲染優化,比如添加loading,使用骨架屏,圖片懶加載,異步加載css,能一定程度上拋開js和css。還有一種是資源加載優化,比如體積優化,一般js、css文件會普遍偏大,可以拆分js分成多個小js,按需引入插件,壓縮圖片,使用在線圖片等方式。

知識點

1.內聯首屏關鍵CSS

性能優化中有一個重要的指標是首次有效繪製(First Meaningful Paint,簡稱FMP),即指頁面的首要內容(primary content)出現在屏幕上的時間。這一指標影響用户看到頁面前所需等待的時間,而內聯首屏關鍵CSS能減少這一時間。

很多人都喜歡通過link標籤引用外部CSS文件。但需要知道的是,將CSS直接內聯到HTML文檔中能使CSS更快速地下載。而使用外部CSS文件時,需要在HTML文檔下載完成後才知道所要引用的CSS文件,然後才下載它們。所以説,內聯CSS能夠使瀏覽器開始頁面渲染的時間提前,因為在HTML下載完成之後就能渲染了。

但是我們不應該將所有的CSS都內聯在HTML文檔中,因為(初始擁塞窗口)存在限制(TCP相關概念,通常是 14.6kB,壓縮後大小),如果內聯CSS後的文件超出了這一限制,系統就需要在服務器和瀏覽器之間進行更多次的往返,這樣並不能提前頁面渲染時間。因此,我們應當只將渲染首屏內容所需的關鍵CSS內聯到HTML中

還有一點需要注意的是內聯CSS沒有緩存,每次都會隨HTML的加載而重新下載,但我們將內聯首屏關鍵CSS控制在 14.6kB以內,它對性能優化還是起到正向作用的。(凡事有利也有弊)

2.異步加載非首屏css

首先確認兩點:

  • CSS不會阻塞DOM的解析,但會阻塞DOM的渲染
  • CSS會阻塞JS執行,但不會阻塞JS文件的下載

由於CSS會阻塞DOM的渲染,所以我們將首屏關鍵CSS內聯後,剩餘的非首屏CSS內容可以使用外部CSS,並且異步加載,防止非首屏CSS內容阻塞頁面的渲染。

CSS有四種異步加載方式:

1.第一種方法是動態創建

// 創建link標籤
const myCSS = document.createElement( "link" );
myCSS.rel = "stylesheet";
myCSS.href = "mystyles.css";
// 插入到header的最後位置
document.head.insertBefore(myCSS,document.head.childNodes[document.head.childNodes.length-1].nextSibling );

2.第二種方法是將link元素的media屬性設置為用户瀏覽器不匹配的媒體類型(或媒體查詢)

對瀏覽器來説,如果樣式表不適用於當前媒體類型,其優先級會被放低,會在不阻塞頁面渲染的情況下再進行下載。在首屏文件加載完成之後,將media的值設為screenall,從而讓瀏覽器開始解析CSS。

<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">

3.第三種方法是通過rel屬性將link元素標記為alternate可選樣式表

<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">

4.第四種方法是使用rel=preload來異步加載CSS

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

注意,as是必須的。忽略as屬性,或者錯誤的as屬性會使preload等同於XHR請求,瀏覽器不知道加載的是什麼內容,因此此類資源加載優先級會非常低。as的可選值可以參考上述標準文檔。

看起來,rel="preload"的用法和上面兩種沒什麼區別,都是通過更改某些屬性,使得瀏覽器異步加載CSS文件但不解析,直到加載完成並將修改還原,然後開始解析。

但是它們之間其實有一個很重要的不同點,那就是使用preload,比使用不匹配的media方法能夠更早地開始加載CSS。所以儘管這一標準的支持度還不完善,仍建議優先使用該方法。

3.簡化選擇器層級

CSS選擇器的匹配是從右向左進行的,這一策略導致了不同種類的選擇器之間的性能也存在差異。相比於#markdown-content-h3,顯然使用#markdown .content h3時,瀏覽器生成渲染樹(render-tree)所要花費的時間更多。因為後者需要先找到DOM中的所有h3元素,再過濾掉祖先元素不是.content的,最後過濾掉.content的祖先不是#markdown的。試想,如果嵌套的層級更多,頁面中的元素更多,那麼匹配所要花費的時間代價自然更高。

不過現在瀏覽器在這一方面做了很多優化,不同選擇器的性能差別並不明顯,甚至可以説差別甚微。此外不同選擇器在不同瀏覽器中的性能表現也不完全統一,在編寫CSS的時候無法兼顧每種瀏覽器。鑑於這兩點原因,我們在使用選擇器時,只需要記住以下幾點,其他的可以全憑喜好。

  1. 保持簡單,不要使用嵌套過多過於複雜的選擇器。
  2. 通配符和屬性選擇器效率最低,需要匹配的元素最多,儘量避免使用。
  3. 不要使用類選擇器和ID選擇器修飾元素標籤,如h3#markdown-content,這樣多此一舉,還會降低效率。
  4. 不要為了追求速度而放棄可讀性與可維護性。

4.減少使用耗性能的屬性

在瀏覽器繪製屏幕時,所有需要瀏覽器進行操作或計算的屬性相對而言都需要花費更大的代價。當頁面發生重繪時,它們會降低瀏覽器的渲染性能。所以在編寫CSS時,我們應該儘量減少使用昂貴屬性,如box-shadow/border-radius/filter/opacity /:nth-child等。

當然,並不是讓大家不要使用這些屬性,因為這些應該都是我們經常使用的屬性。之所以提這一點,是讓大家對此有一個瞭解。當有兩種方案可以選擇的時候,可以優先選擇沒有昂貴屬性或昂貴屬性更少的方案,如果每次都這樣的選擇,網站的性能會在不知不覺中得到一定的提升。

5.不使用@import

不建議使用@import主要有以下兩點原因:

1.使用@import引入CSS會影響瀏覽器的並行下載。使用@import引用的CSS文件只有在引用它的那個css文件被下載、解析之後,瀏覽器才會知道還有另外一個css需要下載,這時才去下載,然後下載後開始解析、構建render tree等一系列操作。這就導致瀏覽器無法並行下載所需的樣式文件。

2.多個@import會導致下載順序紊亂。在IE中,@import會引發資源文件的下載順序被打亂,即排列在@import後面的js文件先於@import下載,並且打亂甚至破壞@import自身的並行下載。

所以不要使用這一方法,使用link標籤就行了。

6.使用硬件加速: will-change

參考博客:https://developer.aliyun.com/article/973113

will-change屬性允許你提前告知瀏覽器你可能會對一個元素進行什麼樣的改變,這樣它就可以提前設置適當的優化,避免了可能會對頁面的響應性產生負面影響的啓動成本。這些元素可以更快地被改變和渲染,頁面將能夠迅速地更新,從而帶來更流暢的體驗。

例如,當在一個元素上使用CSS三維變換時,該元素及其內容可能會升到一個新的圖層,之後才會被合成(繪製到屏幕上)。然而,在一個新的圖層中設置元素是一個代價相對昂貴的操作,這可能會使變換動畫的開始時間延遲幾分之一秒,導致明顯的“閃爍”。

為了避免這種延遲,你可以在變化實際發生前的一段時間通知瀏覽器。這樣,瀏覽器就會有一些時間為這些變化做準備,當這些變化發生時,元素的圖層就會準備好,變換動畫就可以執行,然後元素就可以被渲染,頁面就會迅速更新。

使用will-change,向瀏覽器提示即將發生的變換,可以在你期望被變換的元素上添加這個簡單的規則:

will-change: transform;

你也可以向瀏覽器聲明,你打算改變一個元素的滾動位置(該元素在可見滾動窗口中的位置,以及在該窗口中的可見程度)、或者改變其內容、一個或多個CSS屬性值,方法是指定你期望改變的屬性的名稱。如果你期望或計劃改變一個元素的多個項,你可以提供一個逗號分隔的值的列表。

例如,你可以這樣向瀏覽器聲明:

will-change: transform, opacity;

指定你想要改變的具體內容,可以讓瀏覽器更好地優化為這些特定的變化。這顯然提升速度的一個更好的方法,而不必訴諸於hack以及強迫瀏覽器進行不必要圖層的創建。

除了對瀏覽器的渲染提示之外,will-change 會影響它聲明的元素嗎?

會或者不會取決於你所聲明並告知瀏覽器的屬性。如果一個屬性的任何非初始值都會在該元素上創建一個stacking context ,那麼在will-change中指定該屬性將在該元素上創建一個層疊上下文

譯者注:如果一個元素含有層疊上下文,我們可以理解為這個元素在z軸上的level更高一級。換句話説就是於網頁中元素級別更高,離用户更近了。

例如,clip-path和opacity屬性,當它們的值不是初始值時,都會在被應用的元素上創建一個層疊上下文。因此,使用這些屬性中的一個(或兩個)作為will-change的值會在元素上創建一個層疊上下文,甚至在變化實際發生之前創建。這同樣適用於其他會在元素上創建層疊上下文的屬性。

另外,一些屬性可以導致為固定位置的元素創建一個包含塊。例如,一個被轉換的元素為其所有定位的子元素創建一個包含塊,即使是那些已經被設置為position: fixed的元素。所以,如果一個屬性導致了一個包含塊的產生,那麼把它指定為will-change的值也會導致固定位置元素產生一個包含塊。

譯者注:一般情況下,CSS中一個元素的位置和尺寸的計算都相對於一個矩形,這個矩形被稱為包含塊。

如前所述,will-change的一些變化會導致創建一個新的合成層。然而,GPU並不支持大多數瀏覽器中CPU所做的子像素抗鋸齒,有時會導致內容模糊(尤其是文字)。

除此之外,will-change屬性對它所指定的元素沒有直接影響,它只是對瀏覽器的一個渲染提示,允許它為該元素髮生的變化進行優化設置。除了在上述情況下創建堆疊上下文和包含塊之外,它對元素沒有直接影響。

1.will-change注意事項

知道了will-change的作用後,你可能會想:“只要讓瀏覽器優化所有的東西就行了!” 這個想法很合理,誰不希望他們所有的變化都被優化,並在需要時準備好?

儘管will-change很🐂🍺,但它與其他種類的“權力”沒有任何區別,濫用它會導致性能上的打擊,可能使你的頁面崩潰。

與任何優化一樣,will-change也有其不能直接察覺的副作用(畢竟,它只是一種在幕後與瀏覽器對話的方式),所以它的使用可能很棘手。這裏有一些你在使用這個屬性時需要注意的事情,以確保你能得到它的最佳效果,同時避免濫用它可能帶來的傷害。

2.給瀏覽器足夠的時間

will-change屬性之所以叫做will是因為:通知瀏覽器即將發生的變化,而不是正在發生的變化。

使用will-change,我們要求瀏覽器對我們所聲明的變化進行某些優化,為了實現這一目標,瀏覽器需要一些時間來實際進行這些優化,這樣,當變化發生時,優化就可以毫無延遲地應用。

在一個元素髮生變化之前立即對其設置will-change,幾乎沒有任何效果。(它實際上可能比不設置更糟,可能會有一個新圖層的成本,而你正在做的動畫以前並沒有資格做一個新層!)

例如,如果一個變化將在懸停時發生:

.element:hover {
    will-change: transform;
    transition: transform 2s;
    transform: rotate(30deg) scale(1.5);
}

告訴瀏覽器為已經發生的變化進行優化是沒有用的,這樣做是否定了will-change背後的整個概念。相反,你應該找到一種方法,至少可以稍微提前預測會發生變化的東西,並在那時設置will-change

例如,如果一個元素在被點擊的時候會發生變化,那麼就在該元素被懸停的時候設置will-change,這樣就給了瀏覽器足夠的時間來優化。從用户懸停該元素到實際點擊該元素之間的時間足以讓瀏覽器進行優化設置,因為人類的反應時間相對較慢,所以在變化實際發生之前,瀏覽器會有大約200ms的時間窗口,這足以讓它進行優化設置。

.element {
    transition: transform 1s ease-out;
}
.element:hover {
    will-change: transform;
}
.element:active {
    transform: rotateY(180deg);
}

但如果你希望變化發生在懸停時,而不是點擊時呢?上面的聲明將是無用的,在這種情況下,往往還是可以找到一些方法來預測動作發生之前的情況。

例如,懸停變化元素的祖先可能會提供足夠的準備時間:

.element {
    transition: opacity .3s linear;
}
/* 當鼠標進入或懸停在其祖先時,聲明該元素的變化 */
.ancestor:hover .element {
    will-change: opacity;
}
/* 當元素被懸停時的變化 */
.element:hover {
    opacity: .5;
}

然而,懸停祖先並不總是表明該元素肯定會被交互,所以你可以做一些事情,比如當視圖在你的應用程序中變得活躍時,或者如果該元素在視口的可見部分內,設置will-change,這將增加該元素被交互的機會。

3.更改完成後刪除

瀏覽器為即將發生的變化所做的優化通常會佔用機器的很多資源,通常要刪除這些優化儘快恢復到正常行為。然而,will-change覆蓋了這一行為,它維持優化的時間比瀏覽器所做的要長很多。

因此,你應該始終記得在元素變化完成後刪除will-change,這樣瀏覽器就可以恢復優化所佔用的資源。

如果在樣式表中聲明瞭will-change,就不可能刪除它,這就是為什麼建議你用JavaScript設置和取消它。通過腳本,你可以向瀏覽器聲明你的修改,然後在修改完成後,通過監聽這些修改完成的時間來刪除will-change

例如,就像我們在上一節的樣式規則中所做的那樣,你可以監聽元素(或其祖先)何時被懸停,然後在鼠標進入時設置will-change。如果你的元素正在被動畫化,你可以使用DOM事件animationEnd來監聽動畫何時結束,然後在animationEnd被觸發時移除will-change

// 一個例子
// 獲取點擊時將被動畫化的元素,例如
var el = document.getElementById('element');
// 設置元素被懸停時的變化
el.addEventListener('mouseenter', hintBrowser);
el.addEventListener('animationEnd', removeHint);
function hintBrowser() {
    // 在動畫的關鍵幀中要改變的可優化的屬性
    this.style.willChange = 'transform, opacity';
}
function removeHint() {
    this.style.willChange = 'auto';
}

Craig Buckler寫了一篇關於在JavaScript中捕捉CSS動畫事件的文章,如果你不熟悉這個,可以去看看。CSS-Tricks上也有一篇關於控制CSS動畫和過渡的文章,也值得一看。

4.在樣式表中謹慎使用

正如我們在上一節中所看到的,will-change可以用來提示瀏覽器在幾毫秒內某個元素即將發生的變化。這就是在樣式表中聲明will-change的用例之一。儘管我們建議使用JavaScript來設置和取消will-change,但在某些情況下,在樣式表中設置它(並保持它)是合適的。

一個例子:在一些元素上設置will-change,這些元素可能會被用户反覆交互,並且應該以一種快速的方式響應用户的交互。有限的元素數量意味着瀏覽器所做的優化不會被過度使用,因此不會有太大的傷害。

例如,通過在用户要求時將側邊欄滑出的方式來改造它。下面的規則就很合適:

.sidebar {
    will-change: transform;
}

另一個例子:在不斷變化的元素上使用will-change,比如一個響應用户鼠標移動的元素,隨着鼠標的移動在屏幕上移動。在這種情況下,只需在樣式表中聲明will-change的值就可以了,因為它準確地描述了該元素將定期/不斷地變化,所以應該保持優化。

.annoying-element-stuck-to-the-mouse-cursor {
    will-change: left, top;
}

5.屬性值

will-change有四個屬性值:autoscroll-positioncontents<custom-ident>

<custom-ident> 值用於指定你期望改變的一個或多個屬性的名稱。多個屬性用逗號隔開。

下面是帶有指定屬性名稱的有效的will-change聲明的例子。

will-change: transform;
will-change: opacity;
will-change: top, left, bottom, right;
複製代碼

<custom-ident>:排除了關鍵字will-changenoneallautoscroll-positioncontents,還有一些通常被排除在外的關鍵字。所以,正如我們在文章開頭提到的,will-change: all的聲明是無效的,會被瀏覽器忽略。

auto並沒有特別的意思,除了通常的優化外,瀏覽器不會設置任何特別的優化。

scroll-position:顧名思義,表示你希望在不久的將來隨時改變一個元素的滾動位置。這個值很有用,在使用時瀏覽器會準備並渲染可滾動元素上滾動窗口中可見內容之外的內容。瀏覽器通常只渲染滾動窗口中的內容,以及超過該窗口的部分內容,以平衡因跳過渲染而節省的內存和時間以及使滾動看起來更漂亮。使用will-change: scroll-position,它可以做進一步的渲染優化,從而使更長或更快的內容滾動可以順利地完成。

contents:預計該元素的內容會發生變化。瀏覽器通常會隨着時間的推移緩存元素的渲染,因為大多數東西並不經常改變,或者只改變它們的位置。這個值會被瀏覽器解讀為一個信號,即減少對該元素的緩存,或者完全避免對該元素的緩存,因為如果該元素的內容經常變化,那麼保持對內容的緩存將是無用且浪費時間的,所以瀏覽器會直接停止緩存,只要該元素的內容發生變化,就繼續從頭渲染。

7.減少重排重繪

在網站的使用過程中,某些操作會導致樣式的改變,這時瀏覽器需要檢測這些改變並重新渲染,其中有些操作所耗費的性能更多。我們都知道,當FPS為60時,用户使用網站時才會感到流暢。這也就是説,我們需要在16.67ms內完成每次渲染相關的所有操作,所以我們要儘量減少耗費更多的操作。

1.重繪與重繪

重排:

重排會導致瀏覽器重新計算整個文檔,重新構建渲染樹,這一過程會降低瀏覽器的渲染速度。如下所示,有很多操作會觸發重排,我們應該避免頻繁觸發這些操作。

  1. 改變font-size和font-family
  2. 改變元素的內外邊距
  3. 通過JS改變CSS類
  4. 通過JS獲取DOM元素的位置相關屬性(如width/height/left等)
  5. CSS偽類激活
  6. 滾動滾動條或者改變窗口大小

值得一提的是,某些CSS屬性具有更好的重排性能。如使用flex時,比使用inline-block和float時重排更快,所以在佈局時可以優先考慮flex。

重繪:

當元素的外觀(如color,background,visibility等屬性)發生改變時,會觸發重繪。在網站的使用過程中,重繪是無法避免的。不過,瀏覽器對此做了優化,它會將多次的重排、重繪操作合併為一次執行。不過我們仍需要避免不必要的重繪,如頁面滾動時觸發的hover事件,可以在滾動的時候禁用hover事件,這樣頁面在滾動時會更加流暢。

合併對DOM樣式的修改,採用css class來修改

const el = document.querySelector('.box')
el.style.margin = '5px'
el.style.borderRadius = '12px'
el.style.boxShadow = '1px 3px 4px #ccc'

--->

.update{
  margin: 5px;
  border-dadius: 12px;
  box-shadow: 1px 3px 4px #ccc
}
const el = document.querySelector('.box')
el.classList.add('update')

如果需要對DOM進行多次訪問,儘量使用局部變量緩存該DOM

避免使用table佈局,可能很⼩的⼀個⼩改動會造成整個table的重新佈局

CSS選擇符從右往左匹配查找,避免節點層級過多。

此外,我們編寫的CSS中動畫相關的代碼越來越多,我們已經習慣於使用動畫來提升用户體驗。我們在編寫動畫時,也應當參考上述內容,減少重繪重排的觸發。除此之外我們還可以通過硬件加速和will-change來提升動畫性能。

最後需要注意的是,用户的設備可能並沒有想象中的那麼好,至少不會有我們的開發機器那麼好。我們可以藉助Chrome的開發者工具進行CPU降速,然後再進行相關的測試,降速方法如下圖所示。

如果需要在移動端訪問的,最好將速度限制更低,因為移動端的性能往往更差。

2.DOM離線處理,減少迴流重繪次數

離線的DOM不屬於當前DOM樹中的任何一部分,這也就意味着我們對離線DOM處理就不會引起頁面的迴流與重繪。

  • 使用display: none,將元素從渲染樹中完全移除,元素既不可見,也不是佈局的組成部分,之後在該DOM上的操作不會觸發迴流與重繪,操作完之後再將display屬性改為顯示,只會觸發這一次迴流與重繪。

    Tip:visibility : hidden 的元素只對重繪有影響,不影響重排。

  • 通過documentFragment創建一個dom文檔片段,在它上面批量操作dom,操作完成之後,再添加到文檔中,這樣只會觸發一次重排。
const el = document.querySelector('.box')
const fruits = ['front', 'nanjiu', 'study', 'code'];
const fragment = document.createDocumentFragment();
fruits.forEach(item => {
  const li = document.createElement('li');
  li.innerHTML = item;
  fragment.appendChild(li);
});
el.appendChild(fragment);
  • 克隆節點,修改完再替換原始節點
const el = document.querySelector('.box')
const fruits = ['front', 'nanjiu', 'study', 'code'];
const cloneEl = el.cloneNode(true)
fruits.forEach(item => {
  const li = document.createElement('li');
  li.innerHTML = item;
  cloneEl.appendChild(li);
});
el.parentElement.replaceChild(cloneEl,el)

3.DOM脱離普通文檔流

使用absoult或fixed讓元素脱離普通文檔流,使用絕對定位會使的該元素單獨成為渲染樹中body的一個子元素,重排開銷比較小,不會對其它節點造成太多影響。

4.CSS3硬件加速(GPU加速)

使用css3硬件加速,可以讓transform、opacity、filters這些動畫不會引起迴流重繪 。但是對於動畫的其它屬性,比如background-color這些,還是會引起迴流重繪的,不過它還是可以提升這些動畫的性能。

常見的觸發硬件加速的css屬性:

  • transform
  • opacity
  • filters
  • will-change

5.將節點設置為圖層

圖層能夠阻⽌該節點的渲染⾏為影響別的節點。⽐如對於video標籤來説,瀏覽器會⾃動將該節點變為圖層。

8.css文件壓縮

這應該是最容易想到的一個方法了,通過壓縮CSS文件大小來提高頁面加載速度。現在的構建工具,如webpack、gulp/grunt、rollup等也都支持CSS壓縮功能。壓縮後的文件能夠明顯減小,可以大大降低了瀏覽器的加載時間。同時也有以下操作可以提升性能:

1.CSS層級嵌套最好不要超過3層

一般情況下,元素的嵌套層級不能超過3級,過度的嵌套會導致代碼變得臃腫,沉餘,複雜。導致css文件體積變大,造成性能浪費,影響渲染的速度!而且過於依賴HTML文檔結構。這樣的css樣式,維護起來,極度麻煩,如果以後要修改樣式,可能要使用!important覆蓋。儘量保持簡單,不要使用嵌套過多過於複雜的選擇器。

2.刪除無用CSS代碼

一般情況下,會存在這兩種無用的CSS代碼:一種是不同元素或者其他情況下的重複代碼,一種是整個頁面內沒有生效的CSS代碼即沒有使用到的多餘代碼。

對於前者,在編寫代碼的時候,我們應該儘可能地提取公共類,減少重複。對於後者,在不同開發者進行代碼維護的過程中,總會產生不再使用的CSS的代碼,當然一個人編寫時也有可能出現這一問題。而這些無用的CSS代碼不僅會增加瀏覽器的下載量,還會增加瀏覽器的解析時間,這對性能來説是很大的消耗。所以我們需要找到並去除這些無用代碼。

谷歌的Chrome瀏覽器有可以到看到無用CSS代碼的功能。只需轉到查看>開發人員>開發人員工具,並在最近的版本中打開Sources選項卡,然後打開命令菜單。然後,點擊Coverage,在Coverage analysis窗口中高亮顯示當前頁面上未使用的代碼。

9.首屏問題

首屏指的是網站加載後,用户不用滾動屏幕所看到的所有信息。當項目代碼量達到一定級別,我們就會發現我們首次進入項目的時間特別長,其原因有很多,優化分為兩個方向:資源加載優化頁面渲染優化(網絡方向的就暫不學習,如DNS等)。

頁面渲染優化:

這裏我們主要考慮用户體驗,有以下幾種方式能夠一定程度上提高用户體驗:

1.白屏時的loading動畫

首屏優化,在JS沒解析執行前,讓用户能看到Loading動畫,減輕等待焦慮。通常會在index.html上寫簡單的CSS動畫,直到Vue掛載後替換掛載節點的內容,但這種做法實測也會出現短暫的白屏,建議手動控制CSS動畫關閉

2.首屏骨架加載

骨架屏,也就是APP內常見的加載時各部分灰色色塊,現今已經有很多成熟的骨架屏,比如vue-content-loader,這邊不展開理解骨架屏原理,感興趣的朋友可以看一下骨架屏插件實現原理。

骨架屏適合在頁面加載延遲比較高的情況下使用,但是骨架屏的灰色色塊需要視覺切圖,這個圖是需要緩存在客户端的。如果大範圍使用骨架屏,可能會導致客户端緩存過多的圖片,反而得不償失吧。SSR實施成本會高一些,如果不是特別重要的頁面不建議使用。 當網絡環境很差勁的情況下,依然還是要做降級處理的,也就是客户端渲染。

3.漸進加載圖片

一般來説,圖片加載有兩種方式,一種是自上而下掃描,一種則是原圖的模糊展示,然後逐漸/加載完清晰。前者在網速差的時候用户體驗較差,後者的漸進/交錯式加載則能減輕用户的等待焦慮,帶來更好的體驗,可以參考 js-mozjpeg依賴。

資源加載優化:

對於資源加載的優化,可以分為體積優化、代碼優化、傳輸優化。

1.體積優化

該方式是通過減小所加載資源的大小,來提高請求加載速度。

  • 圖片的格式未針對瀏覽器進行優化,體積比較大,小型圖片可以使用svg替換 png;其他圖片可進行壓縮
  • 排查並移除多餘的依賴和靜態資源
  • 離線包hybrid:提前將html、css、js等下載到App內,用户在下載時便將主要內容也一同下載,當在App內打開頁面時,webview使用file:// 協議加載本地的html、css、js,然後再請求數據、渲染。

2.代碼優化

該方式是通過減小所加載資源的大小、以及所需文件的優先級,來提高請求加載速度或是先完成樣式的加載。

  • 減少dom elements的數量
  • 先加載css外聯文件:如js外聯文件放到body底部,css外聯文件放到head內
  • 路由懶加載:SPA中一個很重要的提速手段就是路由懶加載,當打開頁面時才去加載對應文件,我們利用webpack的代碼分割,減小app.js的體積,從而提高首屏加載速度。

    記得只在生產時懶加載,否則路由多起來後,開發時的構建速度非常影響開發-。=
    const route = [
      {
        path:'/test',
        component:()=>import('../../test.vue')
      }
    ]

3.傳輸優化

該方式是通過減少Http請求次數、提高Http響應速度、優化加載方式,來提高請求速度。

  • 圖片懶加載:圖片不用一次性全部加載,可以等到需要使用的再進行請求加載,從而避免一開始大量的圖片請求加載導致的白屏,這類插件也已經相當成熟,比如vue-lazyLoad
  • 減少cookie大小可以提高獲得響應的時間
  • 採用gzip壓縮傳輸

    //vue.config.js
    const CompressionPlugin = require('compression-webpack-plugin');
     
    module.exports = {
      configureWebpack: config => {
        config.plugins = [
          ...config.plugins,
          // 開啓 gzip 壓縮
          new CompressionPlugin({
            filename: '[path][base].gz',
            algorithm: 'gzip',
           test: /\.js$|\.html$|\.css$|\.jpg$|\.jpeg$|\.png/, // 需要壓縮的文件類型
            threshold: 10240,
            minRatio: 0.8
          })
        ]
      }
    }

    nginx配置

    http {
    
       gzip on; # 開啓 gzip 壓縮
       gzip_static on; # 若存在靜態 gz 文件,則使用該文件
       gzip_min_length 10k; # 設置允許壓縮的頁面最小字節數
       gzip_buffers 16 8k; # 設置用於處理請求壓縮的緩衝區數量和大小
       gzip_comp_level 1; # 設置壓縮級別 1-9,數字越大,壓縮後的大小越小,也越佔用CPU,花費時間越長
       # 對特定的 MIME 類型生效, 其中'text/html’被系統強制啓用
       gzip_types application/javascript text/css font/ttf font/x-woff;
       gzip_vary on; # 是否在 http header中 添加 Vary:Accept-Encoding, on | off
       gzip_http_version 1.1; # 在 http/1.1 的協議下不開啓壓縮
    }

    資源響應頭中出現 Content-Encoding: gzip 則代表配置成功

  • CDN:採用CDN引入,在index.html使用CDN引入,並在webpack配置。打包之後webpack進會從外部打包第三方引入的庫,減小app.js的體積,從而提高首屏加載速度。

    <!-- 使用CDN的CSS文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
      <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
      <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
    <% } %>
    <!-- 使用CDN加速的JS文件,配置在vue.config.js下 -->
    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
      <script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <% } %>
  • 開啓HTTP2:HTTP2是HTTP協議的第二個版本,相較於HTTP1 速度更快、延遲更低,功能更多。 目前來看兼容性方面也算過得去,在國內有超過50%的覆蓋率。通常瀏覽器在傳輸時併發請求數是有限制的,超過限制的請求需要排隊,以往我們通過域名分片、資源合併來避開這一限制,而使用HTTP2協議後,其可以在一個TCP連接分幀處理多個請求(多路複用),不受此限制。(其餘的頭部壓縮等等也帶來了一定性能提升)如果網站支持HTTPS,請一併開啓HTTP2,成本低收益高,對於請求多的頁面提升很大,尤其是在網速不佳時。
  • 服務端渲染SSR:對服務器性能消耗較高,項目複雜度變高,出問題排查起來比較麻煩。

最後

走過路過,不要錯過,點贊、收藏、評論三連~

user avatar toopoo 頭像 nihaojob 頭像 freeman_tian 頭像 kobe_fans_zxc 頭像 dirackeeko 頭像 aqiongbei 頭像 thosefree 頭像 littlelyon 頭像 zourongle 頭像 razyliang 頭像 leexiaohui1997 頭像 longlong688 頭像
點贊 254 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.