前言
隨着互聯網發展至今,對於網站來說,性能顯的越來越重要了,CSS作為頁面渲染和內容展現的重要環節,影響着用戶對整個網站的第一體驗。所以,我們需要重視與CSS相關的性能優化。
項目開發初期我們可能因為各種原因(很大一部分原因是因為項目工期,產品往往把項目上線時間卡的死死的,根本不聽你說的什么性能優化),怎么寫的舒服就怎么來,對於性能優化我們常常在項目完成時才去考慮,經常被推遲到項目的末期,甚至到暴露出嚴重的性能問題時才進行性能優化。
為了更多地避免這一情況,首先要重視起性能優化相關的工作,將其貫穿到整個產品設計與開發中。其次,就是了解性能相關的內容,在項目開發過程中,自然而然地進行性能優化。
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖
第一時間獲取最新的文章~
css渲染規則
想要優化CSS的性能,我們首先需要了解CSS的渲染規則,CSS選擇器是從右向左進行匹配的
來看個例子🌰:
.nav h3 a{font-size: 14px;}
渲染過程大概是:首先找到所有的a
,沿着a
的父元素查找h3
,然后再沿着h3
,查找.nav
。中途找到了符合匹配規則的節點就加入結果集。如果找到根元素html
都沒有匹配,則不再遍歷這條路徑,從下一個a
開始重復這個查找匹配(只要頁面上有多個最右節點為a
)。
Tips:為什么CSS選擇器是從右向左匹配的?
CSS中更多的選擇器是不會匹配的,所以在考慮性能問題時,需要考慮的是如何在選擇器不匹配時提升效率。從右向左匹配就是為了達成這一目的的,通過這一策略能夠使得CSS選擇器在不匹配的時候效率更高。這樣想來,在匹配時多耗費一些性能也能夠想的通了。
內聯首屏關鍵CSS(Critical CSS)
性能優化中有一個重要的指標——首次有效繪制(First Meaningful Paint,簡稱FMP)即指頁面的首要內容(primary content)出現在屏幕上的時間。這一指標影響用戶看到頁面前所需等待的時間,而 內聯首屏關鍵CSS(即Critical CSS,可以稱之為首屏關鍵CSS) 能減少這一時間。
很多人都喜歡通過link
標簽引用外部CSS文件
。但需要知道的是,將CSS直接內聯到HTML文檔中能使CSS更快速地下載。而使用外部CSS文件時,需要在HTML文檔下載完成后才知道所要引用的CSS文件,然后才下載它們。所以說,內聯CSS能夠使瀏覽器開始頁面渲染的時間提前,因為在HTML下載完成之后就能渲染了。
但是我們不應該將所有的CSS都內聯在HTML文檔中,因為[初始擁塞窗口]存在限制(TCP相關概念,通常是 14.6kB,壓縮后大小)
,如果內聯CSS后的文件超出了這一限制,系統就需要在服務器和瀏覽器之間進行更多次的往返,這樣並不能提前頁面渲染時間。因此,我們應當只將渲染首屏內容所需的關鍵CSS內聯到HTML中。
⚠️還有一點需要注意的是內聯CSS沒有緩存,每次都會隨HTML的加載而重新下載,但我們將內聯首屏關鍵CSS控制在 14.6kB
以內,它對性能優化還是起到正向作用的。(凡事有利也有弊)
異步加載非首屏CSS
我們需要知道兩點內容:(具體可以看我之前的文章:這些瀏覽器面試題,看看你能回答幾個?)
CSS
不會阻塞DOM
的解析,但會阻塞DOM
的渲染CSS
會阻塞JS
執行,但不會阻塞JS
文件的下載
由於CSS會阻塞DOM的渲染,所以我們將首屏關鍵CSS內聯后,剩余的非首屏CSS內容可以使用外部CSS,並且異步加載,防止非首屏CSS內容阻塞頁面的渲染。
CSS異步加載方式
第一種方法是動態創建
// 創建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 );
第二種方法是將link元素的media
屬性設置為用戶瀏覽器不匹配的媒體類型(或媒體查詢)
對瀏覽器來說,如果樣式表不適用於當前媒體類型,其優先級會被放低,會在不阻塞頁面渲染的情況下再進行下載。在首屏文件加載完成之后,將media
的值設為screen
或all
,從而讓瀏覽器開始解析CSS。
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
第三種方法是通過rel
屬性將link
元素標記為alternate
可選樣式表
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
第四種方法是使用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。所以盡管這一標准的支持度還不完善,仍建議優先使用該方法。
CSS文件壓縮
這應該是最容易想到的一個方法了,通過壓縮CSS文件大小來提高頁面加載速度。現在的構建工具,如webpack、gulp/grunt、rollup等也都支持CSS壓縮功能。壓縮后的文件能夠明顯減小,可以大大降低了瀏覽器的加載時間。
CSS層級嵌套最好不要超過3層
一般情況下,元素的嵌套層級不能超過3級,過度的嵌套會導致代碼變得臃腫,沉余,復雜。導致css文件體積變大,造成性能浪費,影響渲染的速度!而且過於依賴HTML文檔結構。這樣的css樣式,維護起來,極度麻煩,如果以后要修改樣式,可能要使用!important
覆蓋。盡量保持簡單,不要使用嵌套過多過於復雜的選擇器。
刪除無用CSS代碼
一般情況下,會存在這兩種無用的CSS代碼:一種是不同元素或者其他情況下的重復代碼,一種是整個頁面內沒有生效的CSS代碼。
對於前者,在編寫的代碼時候,我們應該盡可能地提取公共類,減少重復。對於后者,在不同開發者進行代碼維護的過程中,總會產生不再使用的CSS的代碼,當然一個人編寫時也有可能出現這一問題。而這些無用的CSS代碼不僅會增加瀏覽器的下載量,還會增加瀏覽器的解析時間,這對性能來說是很大的消耗。所以我們需要找到並去除這些無用代碼。
那么我們如何知道哪些CSS代碼是無用代碼呢?
谷歌的Chrome瀏覽器就有這種開箱即用的功能。只需轉到查看>開發人員>開發人員工具,並在最近的版本中打開Sources選項卡,然后打開命令菜單。然后,點擊Coverage,在Coverage analysis窗口中高亮顯示當前頁面上未使用的代碼。
慎用*通配符
我們有時候可能會寫下面這種代碼來消除一些標簽的默認樣式或統一瀏覽器對標簽渲染的差異化:
*{
margin:0;
padding:0;
}
這樣雖然代碼量少,但它的性能可不是最佳的,我們最好還是寫對應的標簽選擇器:
body,dl,dd,h1,h2,h3,h4,h5,h6,p,form,ol,ul{
margin:0;
padding:0;
}
開發時盡量避免使用通配符選擇器
小圖片處理方式
一般來講一個網站上肯定會有很多個小圖標,對於這些小圖標,目前的主流的解決方案有三個,cssSprite(雪碧圖),字體圖標,把圖片轉成base64。
- cssSprite: 把所有icon圖片合成一張png圖片,使用時對節點設置寬高,加上bacgroud-position進行背景定位。以背景圖方式顯展示需要的icon,如果一個網站有20圖標,那么就要請求20次,使用
cssSprite
,只需要請求一次,大大的減少了http請求。缺點就是管理不靈活,如果需要新增一個圖標,都需要改合並圖片的源文件,圖標定位也要規范,不然容易干擾圖片之間的定位。 - 字體圖標: 簡單粗暴的理解就是把所有的圖標當成一個字體處理!這樣不用去請求圖片。一般是使用class來定義圖標,要替換圖標時,只需更換樣式名,管理方便,語意明確,靈活放大縮小,並且不會造成失真。但是只支持單色的圖片。
- base64: 另一種方案就是把小的icon圖片轉成base64編碼,這樣可以不用去請求圖片,把base64編碼直接整合到js或者css里面,可以防止因為一些相對路徑,或者圖片被不小刪除了等問題導致圖片404錯誤。但是找個方式會生成一大串的base64編碼。一般來說,8K以下的圖片才轉換成base64編碼。如果把一張50K的圖片轉成base64編碼,那么會生成超過65000個字符的base64編碼,字符的大小就已經是將近70K了!建議就是:
8K以下的圖片才轉換成base64編碼。
避免使用@import
不建議使用@import
主要有以下兩點原因:
-
使用
@import
引入CSS會影響瀏覽器的並行下載。使用@import
引用的CSS文件只有在引用它的那個css文件被下載、解析之后,瀏覽器才會知道還有另外一個css需要下載,這時才去下載,然后下載后開始解析、構建render tree等一系列操作。這就導致瀏覽器無法並行下載所需的樣式文件。 -
多個
@impor
t會導致下載順序紊亂。在IE中,@import
會引發資源文件的下載順序被打亂,即排列在@import后面的js文件先於@import下載,並且打亂甚至破壞@import自身的並行下載。
不要在ID選擇器前面進行嵌套其它選擇器
在ID選擇器前面嵌套其它選擇器純粹是多余的
- ID選擇器本來就是唯一的而且人家權值那么大,前面嵌套(
.content #text
)完全是浪費性能。 - 除了嵌套,在ID選擇器前面也不需要加標簽或者其它選擇器。比如
div#text
或者.box#text
。這兩種方式完全是多余的,理由就是ID在頁面就是唯一的。前面加任何東西都是多余的!
刪除不必要的單位和零
CSS 支持多種單位和數字格式,可以刪除尾隨和跟隨的零,零始終是零,添加維度不會為包含的信息附帶任何價值。
.box {
padding: .2px;
margin: 20px;
avalue: 0;
}
優化回流與重繪
在網站的使用過程中,某些操作會導致樣式的改變,這時瀏覽器需要檢測這些改變並重新渲染,其中有些操作所耗費的性能更多。我們都知道,當FPS為60時,用戶使用網站時才會感到流暢。這也就是說,我們需要在16.67ms內完成每次渲染相關的所有操作,所以我們要盡量減少耗費更多的操作。
減少回流與重繪
合並對DOM
樣式的修改,采用css class
來修改
const el = document.querySelector('.box')
el.style.margin = '5px'
el.style.borderRadius = '12px'
el.style.boxShadow = '1px 3px 4px #ccc'
建議使用css class
.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選擇符從右往左匹配查找,避免節點層級過多
DOM離線處理,減少回流重繪次數
離線的DOM不屬於當前DOM樹中的任何一部分,這也就意味着我們對離線DOM處理就不會引起頁面的回流與重繪。
- 使用
display: none
,上面我們說到了 (display: none
) 將元素從渲染樹中完全移除,元素既不可見,也不是布局的組成部分,之后在該DOM上的操作不會觸發回流與重繪,操作完之后再將display
屬性改為顯示,只會觸發這一次回流與重繪。
提醒⏰: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)
DOM脫離普通文檔流
使用absoult
或fixed
讓元素脫離普通文檔流,使用絕對定位會使的該元素單獨成為渲染樹中 body
的一個子元素,重排開銷比較小,不會對其它節點造成太多影響。
CSS3硬件加速(GPU加速)
使用css3硬件加速,可以讓transform、opacity、filters
這些動畫不會引起回流重繪 。但是對於動畫的其它屬性,比如background-color
這些,還是會引起回流重繪的,不過它還是可以提升這些動畫的性能。
常見的觸發硬件加速的css屬性:
- transform
- opacity
- filters
- Will-change
將節點設置為圖層
圖層能夠阻⽌該節點的渲染⾏為影響別的節點。⽐如對於video標簽來說,瀏覽器會⾃動將該節點變為圖層。
具體回流與重繪知識點可以看我這篇文章:介紹回流與重繪(Reflow & Repaint),以及如何進行優化?
推薦閱讀
- 超全面總結Vue面試知識點,助力金三銀四
- 【面試必備】前端常見的排序算法
- 前端常見的安全問題及防范措施
- 為什么大廠前端監控都在用GIF做埋點?
- 前端人員不要只知道KFC,你應該了解 BFC、IFC、GFC 和 FFC
- Promise、Generator、Async有什么區別?
- 2022年了你還不了解箭頭函數與普通函數的區別嗎?
- 從如何使用到如何實現一個Promise
- 超詳細講解頁面加載過程
我是南玖,我們下期見!!!