前端性能優化小紀
天下武功,無堅不破,唯快不破。對前端而言,快意味著要求資源體量更小、數量更精簡、內容更早呈現、交互更加人性化。當項目做到一定程度,就應該考慮性能的問題,前端的性能優化有諸多有跡可循的理論和方法,比如 Yahoo!性能軍規、Google PageSpeed Insights Rules。
我們團隊一個比較老的項目首屏加載大概需要20多秒,這嚴重影響了用戶體驗,于是進行了一次首屏加載的性能優化。
瀏覽器渲染過程
首先,稍微了解一下,瀏覽器接收到HTML/CSS/JavaScript等資源后的渲染過程:
瀏覽器在收到 HTML 文檔之后會對文檔進行解析開始構建 DOM (Document Object Model) 樹,進而在文檔中發現樣式表,開始解析 CSS 來構建 CSSOM(CSS Object Model)樹,這兩者都構建完成后,開始構建渲染樹。
DOM樹描述了文檔的結構與內容,CSSOM樹則描述了對文檔應用的樣式規則,想要渲染出頁面,就需要將DOM樹與CSSOM樹結合在一起,這就是渲染樹。渲染樹構建完畢后,瀏覽器得到了每個可見節點的內容與其樣式,下一步工作則需要計算每個節點在窗口內的確切位置與大小,也就是布局階段。當Layout布局事件完成后,瀏覽器會立即發出Paint Setup與Paint事件,開始將渲染樹繪制成像素,繪制所需的時間跟CSS樣式的復雜度成正比,繪制完成后,用戶就可以看到頁面的最終呈現效果了。
暫緩JavaScript解析
在上圖構建DOM樹時,<script>標簽可能會阻塞html解析,從而影響首頁加載速度,可以使用async進行異步加載或者用defer進行延遲加載。
async屬性表示腳本會在下載后盡快執行,但不能保證腳本會按照順序執行。
defer屬性表示腳本會先下載,但會在整個頁面都解析完成后再運行,并且按照腳本出現的先后順序執行。
用網上一張圖能比較明顯得看出兩者的不同之處。
藍色線代表網絡讀取,紅色線代表執行時間,這倆都是針對腳本的;綠色線代表 HTML 解析。
用這兩個屬性可以很好解決由<script>引起地加載緩慢問題。
減少不必要的HTML標簽
從瀏覽器渲染的流程可以看出,如果HTML中有很多不必要的標簽會影響DOM解析速度并且增加了HTML文件的大小,可以對嵌套過深的結構進行優化,去除不必要的標簽。
減少CSS嵌套
CSS嵌套過深,會影響瀏覽器查找選擇器的速度,一定程度上產出了很多冗余的字節,一般最多嵌套3層。
啟用CSS Sprite
CSS Sprites在國內很多人叫css精靈,是一種網頁圖片應用處理方式。它允許你將一個頁面涉及到的所有零星圖片都包含到一張大圖中去,這樣一來,當訪問該頁面時,載入的圖片就不會像以前那樣一幅一幅地慢慢顯示出來了。
該項目首頁有有三張svg的圖片,參考SVG Sprite對這三張照片進行了svg sprite的簡單處理,后續在angular/cli中也可以參照這個SVG icon system with angular-cli對項目中的svg圖片進行統一的處理。
進行css sprite處理時,注意以下幾點:
- 把圖片橫向合并,這樣圖片大小更小
- 間距不要太大,這對圖片大小影響不是很大,但對客戶端解壓時需要的內存更少
進行css sprite處理后,降低了首頁資源請求次數。
對于圖標類的圖片,最好用iconfont來減少圖片的額外請求。
壓縮靜態資源
合并打包后的js、css、圖片文件體積一般會比較大,這個時候要對它們進行壓縮處理。gulp和webpack都有相應的壓縮插件。
針對個別圖片,有時候也可以單獨拿出來處理,可以去tinypng 進行在線壓縮。
使用lazyload和preloading
在Angular中,可以在路由中用loadChildren來實現lazyload,這樣可以實現按需加載,加快加載速度。
{ path: 'home', loadChildren: 'app/home/home.module#HomeModule', },
首頁顯示的模塊不應該過大,我們項目中首頁加載的模塊雖然使用了lazyload,但是模塊太大,以至于嚴重影響了加載速度,于是對模塊進行了切割,分成2個模塊,對于第二個模塊進行了preloading,這樣在首頁加載完畢后,會對該模塊進行預加載,加快了路由切換時的速度。關于preloading可以參考Angular官網的自定義預加載策略。
Nginx啟用Gzip壓縮
HTTP協議上的gzip編碼是一種用來改進web應用程序性能的技術,web服務器和客戶端(瀏覽器)必須共同支持gzip。目前主流的瀏覽器,Chrome,firefox,IE等都支持該協議。常見的服務器如Apache,Nginx,IIS同樣支持gzip。
gzip壓縮比率在3到10倍左右,可以大大節省服務器的網絡帶寬。而在實際應用中,并不是對所有文件進行壓縮,通常只是壓縮靜態文件。
在Nginx中,啟用gzip:
# 開啟gzip gzip on; # 啟用gzip壓縮的最小文件,小于設置值的文件將不會壓縮 gzip_min_length 1k; # gzip 壓縮級別,1-10,數字越大壓縮的越好,也越占用CPU時間 gzip_comp_level 5; # 進行壓縮的文件類型。javascript有多種形式。其中的值可以在 mime.types 文件中找到。 gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; # 是否在http header中添加Vary: Accept-Encoding,建議開啟 gzip_vary on; # 禁用IE 6 gzip gzip_disable "MSIE [1-6]\.";
不同gzip_comp_level的壓縮率可以參考下圖:
gzip對svg和x-icon的壓縮效果比較明顯,一般可以達到50%以上的壓縮效果,但是對于壓縮過的PNG、GIF格式圖片啟用Gzip,反而會因為添加標頭、壓縮字典,增大了圖片的大小。
啟用壓縮后,首頁請求的資源大小由原來的10M降低到2.8M,效果還是比較明顯的。
啟用http緩存
每次訪問網頁時80%的時間都會花在資源下載上,因此使用緩存可以大大提高網頁訪問時的響應速度。
參考H5BP配置目錄下的expires.conf,作為Nginx服務器配置:
# Expire rules for static content # No default expire rule. This config mirrors that of apache as outlined in the # html5-boilerplate .htaccess file. However, nginx applies rules by location, # the apache rules are defined by type. A consequence of this difference is that # if you use no file extension in the url and serve html, with apache you get an # expire time of 0s, with nginx you'd get an expire header of one month in the # future (if the default expire rule is 1 month). Therefore, do not use a # default expire rule with nginx unless your site is completely static # cache.appcache, your document html and data location ~* \.(?:manifest|appcache|html?|xml|json)$ { add_header Cache-Control "max-age=0"; } # Feed location ~* \.(?:rss|atom)$ { add_header Cache-Control "max-age=3600"; } # Media: images, icons, video, audio, HTC location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|mp4|ogg|ogv|webm|htc)$ { access_log off; add_header Cache-Control "max-age=2592000"; } # Media: svgz files are already compressed. location ~* \.svgz$ { access_log off; gzip off; add_header Cache-Control "max-age=2592000"; } # CSS and Javascript location ~* \.(?:css|js)$ { add_header Cache-Control "max-age=31536000"; access_log off; } # WebFonts # If you are NOT using cross-domain-fonts.conf, uncomment the following directive # location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ { # add_header Cache-Control "max-age=2592000"; # access_log off; # }
上述配置禁用manifest,appcache,html,xml和json文件的緩存。 它將RSS和ATOM訂閱文件緩存1小時,Javascript和CSS文件1年,以及其他靜態文件(圖像和媒體)1個月。
緩存全部設置為public,所以任何系統都可以緩存它們。 將它們設置為私有將限制它們被私有緩存(例如我們的瀏覽器)緩存。
關于緩存中資源的新鮮度控制可以看這篇文章HTTP緩存控制小結。
總結
這次只是很簡單地對首屏加載進行了性能優化,減少了10個http請求,總資源大小從10.4MB降到2.8MB,首屏DOMContentLoaded時間從12秒左右降到2秒左右,load時間從22秒左右降到6秒左右,效果還是很明顯的。
參考文章
Google Developers中performance系列文章
Front-End Performance Checklist 2018
前端那些事兒」② 極限性能優化
前端性能優化相關
HTTP緩存控制小結
Nginx緩存最佳實踐
來自:http://www.iteye.com/news/32894