天天看點

攜程火車票7個優化動畫性能的方法

作者:閃念基因
攜程火車票7個優化動畫性能的方法

一 、背景

攜程火車票營銷頁使用 css 制作動畫很多年了,這大大提高了動畫給予頁面豐富的視覺體驗。不過,在開發的過程中,也遇到了一些性能相關問題和使用者回報,比如頭部動畫卡頓、頁面打開時間較長、頁面打開後部分資料加載時間較長等問題。為解決這些問題,我們借助性能檢測工具定位問題,并查閱源碼、文檔等資源解決問題,形成了這篇文章。

二、渲染優化

要優化動畫性能,首先要了解浏覽器是如何進行元素渲染的,浏覽器的渲染流程有以下四步:

a. 計算元素的樣式(可能通過腳本重新計算);

b. 生成每個元素的幾何形狀和位置(布局);

c. 繪制圖層的每個像素(初始化繪圖并且進行繪圖);

攜程火車票7個優化動畫性能的方法

d. 将圖層繪制到螢幕上(合并渲染層)。

對于 CSS3 動畫來說,每一幀都要經曆上述過程。關于最後一步合并渲染層(可以類比 Photoshop 的圖層),浏覽器會在特定的場合建立獨立的渲染層,每個渲染層由 GPU 獨立繪制,互不影響,最後浏覽器再把各個渲染層合并。這是一種代價較低的操作。

理論上說,FPS 越高,動畫會越流暢,目前大多數裝置的螢幕重新整理率為 60 次/秒,是以通常來講 FPS 為 60frame/s 時動畫效果最好,也就是每幀的消耗時間(幀預算)為 16.67ms。

三、解決方案

如上所說浏覽器有整理工作要做,是以所有工作需要盡量在 10 ms 内完成。如果制作的動畫觸發了布局,那就相當于要進行第二步重新繪制,如果重新繪制的話浏覽器渲染的時間肯定超過 16ms,那麼我們的頁面就會出現卡頓,如果是移動端的話那就會更慢,是以我們如果要優化的話那就要從第一步直接跳到第四步。

攜程火車票7個優化動畫性能的方法

下面我寫了七種優化動畫性能的方法,有直接從第一步跳到第四步的也有一些其他平時優化注意事項。

3.1 開啟 GPU 加速

Transform 屬性可以向元素應用 2D 或者 3D 轉換,可以對元素進行選擇、縮放、移動和傾斜。

在日常中我們可以使用 left/top,translate 來實作元素的位移,但是其實性能上還是有一定差別的因為 transform 屬性不會改變自己和他周圍元素的布局,他會對元素的整體産生影響。

讓我們先用 top/left 屬性建立一個動畫看看效果。

<style>
      .heart{animation: heartbeat 4s infinite;width:50px;height:50px;background:red;position:absolute;left:30px;top:30px;}
      @keyframes heartbeat{
          0%{top:30px;left:30px;}
          25%{top:30px;left:230px;}
          50%{top:230px;left:230px;}
          75%{top:230px;left:30px;}
      }
</style>           

用 chrome 浏覽器的 DevTools 檢視可以看到紅色方塊都是布局重繪。

圖中有那麼多的紅色方框與幀數是因為浏覽器會做大量的計算,動畫就會卡頓。

因為每一幀的變化浏覽器都在進行布局、繪制、把新的位圖交給 GPU 記憶體,雖然隻改變元素位置但是很可能要同步改變他的子元素的位置,那浏覽器就要重新計算布局,計算完後主線程再來重新生成該元素的位圖。

攜程火車票7個優化動畫性能的方法

現在,将動畫用 transform 代替:

<style>
      .heart{animation: heartbeat 1s;width:50px;height:50px;background:red;}
      @keyframes heartbeat{
          0%{transform: translate(30px,30px);}
     25%{transform: translate(30px,230px);}
       50%{transform: translate(230px,230px);}
          75%{transform: translate(230px,30px);}
      }
</style>           

再次分析,可以看到,沒有發生紅色的重繪方塊且隻有一條幀數,就知道動畫肯定會流暢絲滑。

因為 transform 屬性不會改變自己和他周圍元素的布局,他會對元素的整體産生影響。

攜程火車票7個優化動畫性能的方法

我們通過節點的 transform 可以修改節點的位置、旋轉、大小等。我們平常會使用 left 和 top 屬性來修改節點的位置,但正如上面所述,left 和 top 會觸發重布局,修改時的代價相當大。取而代之的更好方法是使用 translate,這個不會觸發重布局。

3.2 避免使用影響性能的 CSS 屬性

這些屬性會影響性能,因為它們需要進行複雜的計算和渲染,尤其是在動畫中使用時。這些屬性可能會導緻浏覽器進行重排和重繪,進而影響頁面的性能和流暢度。

如果您必須使用這些屬性,請盡可能減少它們的使用。例如,您可以嘗試使用 CSS3 的 transform 屬性來實作 box-shadow 和 border-radius 的效果,因為它們可以更好地利用浏覽器的硬體加速。

例如,下面是一個使用 box-shadow 屬性的示例:

.box {
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}           

這個 box-shadow 屬性會在元素周圍添加一個陰影效果,但是它會影響性能,因為浏覽器需要進行複雜的計算和渲染。為了優化性能,我們可以使用 CSS3 的 transform 屬性來實作相同的效果:

.box {  transform: translateZ(0);  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);}           

這個 transform 屬性會啟用硬體加速,進而提高性能。同時,我們仍然可以使用 box-shadow 屬性來添加陰影效果。

3.3 避免使用複雜的選擇器

選擇器和動畫之間存在一定的關系。在 CSS 動畫中,選擇器的複雜度越高,樣式計算的時間就越長。在動畫中使用複雜的選擇器會導緻浏覽器需要更長的時間來計算樣式,進而影響動畫的性能和流暢度。

例如,當我們使用複雜的選擇器來選擇元素,并為它們添加動畫效果時,浏覽器需要花費更長的時間來計算樣式,進而影響動畫的性能和流暢度。相反,當我們使用簡單的選擇器來選擇元素,并為它們添加動畫效果時,浏覽器可以更快地計算樣式,進而提高動畫的性能和流暢度。

假設我們有一個清單,其中包含多個項目。我們想要為這些項目添加一個簡單的動畫效果,當滑鼠懸停在項目上時,項目的背景色會漸變為藍色。我們可以使用以下 CSS 代碼來實作這個效果:

/* 使用類選擇器來選擇所有項目 */
.item {
  background-color: #fff; /* 初始背景色為白色 */
  transition: background-color 0.3s ease; /* 添加背景色漸變動畫 */
}


/* 當滑鼠懸停在項目上時,将背景色漸變為藍色 */
.item:hover {
  background-color: #007bff; /* 背景色漸變為藍色 */
}           

在這個例子中,我們使用了類選擇器來選擇所有的項目,并為它們添加了一個初始的背景色和一個背景色漸變動畫。當滑鼠懸停在項目上時,我們使用: hover 僞類選擇器來選擇目前懸停的項目,并将其背景色漸變為藍色。

這個例子中的選擇器非常簡單,浏覽器可以很快地計算樣式,進而提高動畫的性能和流暢度。相比之下,如果我們使用複雜的選擇器來選擇項目,并為它們添加動畫效果,浏覽器需要花費更長的時間來計算樣式,進而影響動畫的性能和流暢度。

3.4 使用 will-change

使用 will-change 屬性來告訴浏覽器哪些元素将要進行動畫,以便浏覽器提前進行優化。

will-change 屬性是 CSS3 的一個新屬性,它可以告訴浏覽器哪些元素将要進行動畫,進而使浏覽器可以提前進行優化,提高動畫的性能和流暢度。

例如,如果您要對某個元素進行動畫,您可以在 CSS 中添加以下代碼:

#textbox {
  opacity: 1; /* 初始透明度為1 */
  transition: opacity 0.3s ease; /* 添加透明度漸變動畫 */
  will-change: opacity; /* 告知浏覽器我們将會修改透明度 */
}           

在這個例子中,我們使用 will-change 屬性來告知浏覽器我們将會修改文本框的透明度,進而使浏覽器可以提前進行優化。當動畫開始時,浏覽器已經準備好了相應的資源,進而可以更快地渲染動畫,提高動畫的性能和流暢度。

需要注意的是,will-change 屬性應該謹慎使用,因為它可能會導緻浏覽器提前配置設定額外的記憶體和資源,進而影響頁面的性能。是以,隻有在必要的情況下才應該使用 will-change 屬性。

CSS3 will-change 屬于 web 标準屬性,相容性這塊 Chrome/FireFox/Opera 都是支援的。

攜程火車票7個優化動畫性能的方法

使用 will-change 屬性是優化 CSS 動畫的重要技巧之一,可以提高動畫的性能和流暢度。

3.5 使用 requestAnimationFrame

requestAnimationFrame 代替 setTimeout 或 setInterval 來執行動畫,因為它可以最大程度地利用浏覽器的優化。

requestAnimationFrame 是浏覽器提供的一個 API,它可以讓我們在下一次浏覽器繪制之前執行動畫。與 setTimeout 或 setInterval 相比,requestAnimationFrame 可以更好地利用浏覽器的優化,進而提高動畫的性能和流暢度。

例如,如果您要對某個元素進行動畫,您可以使用 requestAnimationFrame 來執行動畫:

function animate() {
  // 更新元素的樣式
  element.style.transform = 'translateX(100px)';


  // 使用requestAnimationFrame執行下一幀動畫
  requestAnimationFrame(animate);
}


// 開始執行動畫
requestAnimationFrame(animate);           

在上面的代碼中,我們使用 requestAnimationFrame 來執行動畫。在每一幀動畫中,我們更新元素的樣式,然後使用 requestAnimationFrame 執行下一幀動畫。這樣可以最大程度地利用浏覽器的優化,提高動畫的性能和流暢度。

需要注意的是,requestAnimationFrame 并不是所有浏覽器都支援,是以在使用它時需要進行相容性處理。通常情況下,我們可以使用一個polyfill來實作 requestAnimationFrame 的相容性。

3.6 避免在動畫中使用 JavaScript 操作 DOM

在動畫中使用 JavaScript 操作 DOM 會影響性能,主要是因為 DOM 操作是非常耗費資源的,因為這會引起重排和重繪。每次操作 DOM 都會觸發浏覽器重新計算元素的布局和重新繪制元素,這些操作會消耗大量的 CPU 資源和記憶體,導緻動畫卡頓或者不流暢。

在動畫中,如果需要頻繁地操作DOM,就會導緻性能問題。例如,如果在動畫中使用 JavaScript 來改變元素的位置、尺寸、樣式等屬性,就會觸發 DOM 操作,影響動畫的流暢度。

如果必須使用 JavaScript 操作 DOM,請盡可能減少它們的使用。例如,您可以在動畫開始前将需要操作的元素緩存到變量中,然後在動畫中直接使用這些變量,而不是每次都重新查找元素。

另外,還可以使用 CSS3 的動畫屬性來代替 JavaScript 操作 DOM。例如,使用 animation 屬性可以實作複雜的動畫效果,而不需要使用 JavaScript 操作 DOM,下面會詳細說為什麼用盡量用 css 動畫而不使用 javascript 動畫。

3.7 用 CSS 動畫盡量不使用 JavaScript 動畫

因為前者可以更好地利用浏覽器的優化。

假設我們有一個按鈕,當使用者點選按鈕時,我們想要将一個文本框從螢幕上移除,并在移除時添加一個簡單的動畫效果。我們可以使用以下 JavaScript 代碼來實作這個效果:

var textbox = document.getElementById('textbox'); // 擷取文本框元素
var button = document.getElementById('button'); // 擷取按鈕元素


button.addEventListener('click', function() {
  textbox.style.opacity = 0; // 文本框透明度漸變為0
  setTimeout(function() {
    textbox.parentNode.removeChild(textbox); // 移除文本框元素
  }, 300); // 延遲300毫秒後移除文本框元素
});           

在這個例子中,我們使用 JavaScript 操作 DOM 元素,通過擷取文本框和按鈕元素,并在按鈕被點選時逐漸将文本框的透明度降低到 0,然後在 300 毫秒後移除文本框元素。

然而,這種方法會導緻浏覽器進行重排和重繪,進而影響動畫的性能和流暢度。相反,我們可以使用 CSS3 的 transition 屬性來實作一個簡單的動畫效果,而無需使用 JavaScript 操作 DOM 元素。

例如,我們可以使用以下 CSS 代碼來實作一個簡單的動畫效果,當使用者點選按鈕時,文本框會逐漸消失:

#textbox {
  opacity: 1; /* 初始透明度為1 */
  transition: opacity 0.3s ease; /* 添加透明度漸變動畫 */
}


#textbox.hide {
  opacity: 0; /* 透明度漸變為0 */
}           

在這個例子中,我們使用 CSS3 的 transition 屬性來實作一個簡單的透明度漸變動畫。當使用者點選按鈕時,我們使用 JavaScript 為文本框添加一個 hide 類,這個類會将文本框的透明度逐漸降低到 0,進而實作文本框逐漸消失的動畫效果。

這個例子中的動畫效果可以直接作用于 DOM 元素,而無需使用 JavaScript 操作 DOM 元素,進而提高動畫的性能和流暢度。

在動畫中使用 CSS 動畫可以更好地利用浏覽器的硬體加速,進而提高動畫的性能和流暢度。相比之下,JavaScript 動畫通常需要更多的計算和操作,進而影響動畫的性能和流暢度。

當然,在某些情況下,JavaScript 動畫可能是必要的。例如,在需要與使用者互動的動畫中,JavaScript 動畫可以更好地控制動畫的行為。但是,在這種情況下,我們仍然應該盡可能減少 JavaScript 操作的次數,以提高動畫的性能和流暢度。

四、結論

動畫給予了頁面豐富的視覺體驗。我們應該盡力避免使用會觸發重布局和重繪的屬性,以免失幀。最好提前申明動畫,這樣能讓浏覽器提前對動畫進行優化。由于 GPU 的參與,現在用來做動畫的最好屬性是如下幾個:* opacity* translate* rotate* scale,Javascript 優化、減少 Layout 和 Paint。希望對大家了解浏覽器的渲染機制和日常的動畫開發有所幫助。

性能優化是一件不斷持續,不斷深入的事情。我們通過本文中所介紹的改進措施對頁面性能實作了很大的優化,達到了不錯的效果。後續也會在此基礎之上對還可提高的地方繼續加深,後續提供通用的解決方案。

作者:Kay Huang

Kay Huang,攜程進階視覺設計師,專注于前端樣式與動畫領域。

來源:微信公衆号:攜程技術

出處:https://mp.weixin.qq.com/s/uzVjPQb-VgpR4Eu7dt4FKw