前端性能優化——圖片篇
在類電商類項目,往往存在大量的圖片,如 banner 廣告圖,菜單導航圖,美團等商家列表頭圖等。圖片眾多以及圖片體積過大往往會影響頁面加載速度,造成不良的用戶體驗,所以對圖片進行優化勢在必行。
我們先來看一個頁面啟動時加載的圖片信息。
http.png
如圖所示,這個頁面啟動時加載了幾十張圖片(甚至更多),而這些圖片請求幾乎是并發的,在 Chrome 瀏覽器,最多支持的并發請求次數是有限的,其他的請求會推入到隊列中等待或者停滯不前,直到上輪請求完成后新的請求才會發出。所以相當一部分圖片資源請求是需要排隊等待時間的,過多的圖片必然會影響頁面的加載和展示。
選擇合適的圖片格式
JPEG
JPEG 是由 Joint Photographic Experts Group 所開發出的一種圖片。它最大的特點是 有損壓縮。這種高效的壓縮算法使它成為了一種非常輕巧的圖片格式。另一方面,即使被稱為“有損”壓縮,JPG 的壓縮方式仍然是一種高質量的壓縮方式:當我們把圖片體積壓縮至原有體積的 50% 以下時,JPG 仍然可以保持住 60% 的品質。此外,JPG 格式以 24 位存儲單個圖,可以呈現多達 1600 萬種顏色,足以應對大多數場景下對色彩的要求,這一點決定了它壓縮前后的質量損耗并不容易被我們人類的肉眼所察覺。
優點
- JPEG 格式的圖片可以呈現數百萬種顏色。所以每當網站需要呈現色彩豐富的圖片,JPEG 總是最佳選擇。
- 有損壓縮,你可以通過壓縮大大的減少圖片的體積,一般圖片用 60%級別比較合適,如果選擇大于 75%的壓縮等級,則會使圖片有明顯的質量下降。
- 無兼容性問題,所以開發者可以放心隨意使用。
使用場景
- JPG 適用于呈現色彩豐富的圖片,在我們日常開發中,JPEG 圖片經常作為大的背景圖、輪播圖或 Banner 圖出現。
- 但是有損壓縮后的圖片確實很難露出馬腳,當它處理矢量圖形和 Logo 等線條感較強、顏色對比強烈的圖像時,人為壓縮的圖片模糊會相當明顯。
- JPEG 圖像不支持透明度處理,透明圖片可選擇使用 PNG。
PNG
PNG(可移植網絡圖形格式)是由 W3C 開發的圖片格式,是一種無損壓縮的高保真的圖片格式。它同時支持 8 位和 24 位,這里都是二進制數的位數。按照我們前置知識里提到的對應關系,8 位的 PNG 最多支持 256 種顏色,而 24 位的可以呈現約 1600 萬種顏色。
PNG 圖片具有比 JPEG 更強的色彩表現力,對線條的處理更加細膩,對透明度有良好的支持。它彌補了上文我們提到的 JPEG 的局限性,唯一的缺點就是 體積太大。
應用場景
- PNG 在處理線條和顏色對比度方面的優勢,我們主要用它來呈現小的 Logo、顏色簡單且對比強烈的圖片或背景等。
- 支持透明度處理,透明圖片可選擇使用 PNG
GIF
GIF 是一種最多支持 256 種顏色的 8 位無損圖片格式。這個限制讓 GIF 格式對于多顏色或者攝影圖片的展示無能為力。
優點
- 支持 256 中顏色,文件體積通常都很小
- 支持透明
應用場景
- 支持動畫,適合去展示一些無限循環的動畫,比如圖標、表情、廣告欄等。
- 對于一些只有簡單色彩的圖片非常合適。
WebP
WebP 是一種同時提供了有損壓縮與無損壓縮(可逆壓縮)的圖片文件格式,派生自影像編碼格式 VP8。它像 JPEG 一樣對細節豐富的圖片信手拈來,像 PNG 一樣支持透明,像 GIF 一樣可以顯示動態圖片,集多種圖片文件格式的優點于一身。
WebP 最初在 2010 年發布,目標是減少文件大小,但達到和 JPEG 格式相同的圖片質量,希望能夠減少圖片檔在網絡上的發送時間。根據 Google 較早的測試,WebP 的無損壓縮比網絡上找到的 PNG 檔少了 45%的文件大小,即使這些 PNG 檔在使用 pngcrush 和 PNGOUT 處理過,WebP 還是可以減少 28%的文件大小。
雖然 webP 有諸多優點,但是它不能完全替代 JPEG 和 PNG,因為瀏覽器對 WebP 支持并不普遍。特別是移動端 IOS 系統基本不支持。
webp.png
圖片壓縮
我們再來看一下一張圖片的加載過程:
load.png
圖片眾多以及圖片體積過大往往會影響頁面加載速度,造成不良的用戶體驗,有部分圖片達到幾百 kB,甚至 2M(這鍋必須運營背,非得上傳高清大圖不可?),直接導致了加載時間過長。所以對于體積過大的圖片,在保持圖片在可接受的清晰度范圍內可適當對圖片大小進行壓縮。
圖片壓縮又分為有損壓縮和無損壓縮。
有損壓縮
有損壓縮指在壓縮文件大小的過程中,損失了一部分圖片的信息,也即降低了圖片的質量(即圖片被壓糊了),并且這種損失是不可逆的。常見的有損壓縮手段是按照一定的算法將臨近的像素點進行合并。壓縮算法不會對圖片所有的數據進行編碼壓縮,而是在壓縮的時候,去除了人眼無法識別的圖片細節。因此有損壓縮可以在同等圖片質量的情況下大幅降低圖片的體積。例如 jpg 格式的圖片使用的就是有損壓縮。
無損壓縮
無損壓縮指的是在壓縮圖片的過程中,圖片的質量沒有任何損耗。我們任何時候都可以從無損壓縮過的圖片中恢復出原來的信息。壓縮算法對圖片的所有的數據進行編碼壓縮,能在保證圖片的質量的同時降低圖片的體積。例如 png、gif 使用的就是無損壓縮。
下面是各種圖片格式的壓縮類型
image.png
工具壓縮
- tinypng[1] 免費、批量、速度塊
- 智圖壓縮[2] 百度很難搜到官網了,免費、批量、好用
- squoosh[3] 在線圖片壓縮工具
- compressor[4] 支持 JPG、PNG、SVG、GIF
webpack 壓縮
工程化的項目可以在 webpack 里面配置 image-webpack-loader 進行圖片壓縮
1. 安裝依賴
- npm install --save-dev image-webpack-loader
2. 配置 webpack
- module.exports = {
- ...
- module: {
- rules: [
- {
- test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
- use: [
- {
- loader: 'file-loader',
- options: {
- name: '[name].[hash:7].[ext]'
- },
- },
- {
- loader: 'image-webpack-loader',
- options: {
- mozjpeg: {
- progressive: true,
- quality: 50,
- },
- optipng: {
- enabled: true,
- },
- pngquant: {
- quality: [0.5, 0.65],
- speed: 4,
- },
- gifsicle: {
- interlaced: false,
- },
- webp: { // 不支持WEBP就不要寫這一項
- quality: 75
- },
- },
- },
- ],
- },
- ],
- },
- }
至于要不要使用插件自動壓縮就見仁見智了,因為有些 UI 和產品會說壓縮出來的效果圖片不是他們想要的。
使用雪碧圖
雪碧圖,CSS Sprites,國內也叫 CSS 精靈,是一種 CSS 圖像合成技術,主要用于小圖片顯示。
瀏覽器請求資源的時候,同源域名請求資源的時候有最大并發限制,chrome 為 6 個,就比如你的頁面上有 10 個相同 CDN 域名小圖片,那么需要發起 10 次請求去拉取,分兩次并發。第一次并發請求回來后,發起第二次并發。如果你把 10 個小圖片合并為一張大圖片的畫,那么只用一次請求即可拉取下來 10 個小圖片的資源。減少服務器壓力,減少并發,減少請求次數。
優點
把諸多小圖片合成一張大圖,利用 backround-position 屬性值來確定圖片呈現的位置,可以有效的較少請求個數,而且,而不影響開發體驗,使用構建插件可以做到對開發者透明。適用于頁面圖片多且豐富的場景。
缺點
生成的圖片體積較大,減少請求個數同時也增加了圖片大小,不合理拆分將不利于并行加載。
合成雪碧圖
在 webpack 中,有相應的插件提供了自動合成雪碧圖的功能并且可以自動生成對應的樣式文件—— webpack-spritesmith,使用方法如下
- var path = require('path')
- var SpritesmithPlugin = require('webpack-spritesmith')
- module.exports = {
- // ...
- plugins: [
- new SpritesmithPlugin({
- src: {
- cwd: path.resolve(__dirname, 'src/ico'),
- glob: '*.png',
- },
- target: {
- image: path.resolve(__dirname, 'src/spritesmith-generated/sprite.png'),
- css: path.resolve(__dirname, 'src/spritesmith-generated/sprite.styl'),
- },
- apiOptions: {
- cssImageRef: '~sprite.png',
- },
- }),
- ],
- }
通過上面配置就能將 src/ico 目錄下的所有 png 文件合成雪碧圖,并且輸出到對應目錄,同時還可以生成對應的樣式文件,樣式文件的語法會根據你配置的樣式文件的后綴動態生成。
使用 iconfont
iconfont(字體圖標),即通過字體的方式展示圖標,多用于渲染圖標、簡單圖形、特殊字體等。
優點
- 像使用字體一樣,設置大小、顏色及其他樣式,不失真
- 輕量,易修改
- 有效減少 HTTP 請求次數
推薦使用阿里的字體圖標庫:iconfont[5]
使用 base64 格式
原理:將圖片轉換為 base64 編碼字符串 inline 到頁面或 css 中。
優點
- 提升性能: 網頁上的每一個圖片,都是需要消耗一個 http 請求下載而來的, 圖片的下載始終都要向服務器發出請求,要是圖片的下載不用向服務器發出請求,base64 可以隨著 HTML 的下載同時下載到本地.減少 https 請求。
- 加密: 讓用戶一眼看不出圖片內容 , 只能看到編碼。
- 方便引用: 在多個文件同時使用某些圖片時, 可以把圖片轉為 base64 格式的文件, 把樣式放在全局中, 比如 common.css, 以后在用的時候就可以直接加類名, 二不需要多層找文件路徑, 會提升效率
但需要注意的是:如果圖片較大,圖片的色彩層次比較豐富,則不適合使用這種方式,因為該圖片經過 base64 編碼后的字符串非常大,會明顯增大 HTML 頁面的大小,從而影響加載速度。
base64 化最常見的就是在 url-loader 中使用。
- module.exports = {
- ...
- module: {
- rules: [
- {
- test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
- loader: 'url-loader',
- options: {
- limit: 10240,
- name: utils.assetsPath('img/[name].[hash:7].[ext]'),
- }
- },
- ],
- },
- }
這樣就能將項目中小于 10kb 的圖片轉化為 base64 應用到頁面中
使用 css 代替圖片。
比如實現修飾效果,如半透明、邊框、圓角、陰影、漸變等,在當前主流瀏覽器中都可以用 CSS 達成,這樣能減少圖片的請求,達到優化的目的。
缺點
- 受限于 css 的瀏覽器的兼容性
- 對于較復雜的圖案就無能為力了,寫也麻煩,開發成本大
使用 CDN 圖片
CDN 的全稱是 Content Delivery Network,即內容分發網絡。CDN 是構建在網絡之上的內容分發網絡,依靠部署在各地的邊緣服務器,通過中心平臺的負載均衡、內容分發、調度等功能模塊,使用戶就近獲取所需內容,降低網絡擁塞,提高用戶訪問響應速度和命中率。CDN 的關鍵技術主要有內容存儲和分發技術。
舉個簡單的例子:
以前買火車票大家都只能去火車站買,后來我們買火車票就可以在樓下的火車票代售點買了。
基本原理
CDN 的基本原理是廣泛采用各種緩存服務器,將這些緩存服務器分布到用戶訪問相對集中的地區或網絡中,在用戶訪問網站時,利用全局負載技術將用戶的訪問指向距離最近的工作正常的緩存服務器上,由緩存服務器直接響應用戶請求。
基本思路
CND 的基本思路是盡可能避開互聯網上有可能影響數據傳輸速度和穩定性的瓶頸和環節,使內容傳輸的更快、更穩定。通過在網絡各處放置節點服務器所構成的在現有的互聯網基礎之上的一層智能虛擬網絡,CDN 系統能夠實時地根據網絡流量和各節點的連接、負載狀況以及到用戶的距離和響應時間等綜合信息將用戶的請求重新導向離用戶最近的服務節點上。其目的是使用戶可就近取得所需內容,解決 Internet 網絡擁擠的狀況,提高用戶訪問網站的響應速度。
CDN 的優勢
- CDN 節點解決了跨運營商和跨地域訪問的問題,訪問延時大大降低;
- 大部分請求在 CDN 邊緣節點完成,CDN 起到了分流作用,減輕了源站的負載。
圖片懶加載
懶加載是一種網頁性能優化的方式,它能極大的提升用戶體驗。圖片一直是影響網頁性能的主要元兇,現在一張圖片超過幾兆已經是很經常的事了。如果每次進入頁面就請求所有的圖片資源,那么可能等圖片加載出來用戶也早就走了。所以進入頁面的時候,只請求可視區域的圖片資源。
總結出來就是:
- 減少資源的加載,頁面啟動只加載首屏的圖片,這樣能明顯減少了服務器的壓力和流量,也能夠減小瀏覽器的負擔。
- 防止并發加載的資源過多而阻塞 js 的加載,影響整個網站的啟動,影響用戶體驗
- 浪費用戶的流量,有些用戶并不想全部看完,全部加載會耗費大量流量。
原理
圖片懶加載的原理就是暫時不設置圖片的 src 屬性,而是將圖片的 url 隱藏起來,比如先寫在 data-src 里面,等當前圖片是否到了可視區域再將圖片真實的 url 放進 src 屬性里面,從而實現圖片的延遲加載。
- function lazyload() {
- let viewHeight = document.body.clientHeight //獲取可視區高度
- let imgs = document.querySelectorAll('img[data-src]')
- imgs.forEach((item, index) => {
- if (item.dataset.src === '') return
- // 用于獲得頁面中某個元素的左,上,右和下分別相對瀏覽器視窗的位置
- let rect = item.getBoundingClientRect()
- if (rect.bottom >= 0 && rect.top < viewHeight) {
- itemitem.src = item.dataset.src
- item.removeAttribute('data-src')
- }
- })
- }
- // 可以使用節流優化一下
- window.addEventListener('scroll', lazyload)
通過上面例子的實現,我們要實現懶加載都需要去監聽 scroll 事件,盡管我們可以通過函數節流的方式來阻止高頻率的執行函數,但是我們還是需要去計算 scrollTop,offsetHeight 等屬性,有沒有簡單的不需要計算這些屬性的方式呢,答案是有的---IntersectionObserver
- const imgs = document.querySelectorAll('img[data-src]')
- const config = {
- rootMargin: '0px',
- threshold: 0,
- }
- let observer = new IntersectionObserver((entries, self) => {
- entries.forEach((entry) => {
- if (entry.isIntersecting) {
- let img = entry.target
- let src = img.dataset.src
- if (src) {
- img.src = src
- img.removeAttribute('data-src')
- }
- // 解除觀察
- self.unobserve(entry.target)
- }
- })
- }, config)
- imgs.forEach((image) => {
- observer.observe(image)
- })
圖片預加載
圖片預加載,是指在一些需要展示大量圖片的網站,將圖片提前加載到本地緩存中,從而提升用戶體驗。
常用的方式有兩種,一種是隱藏在 css 的 background 的 url 屬性里面,一種是通過 javascript 的 Image 對象設置實例對象的 src 屬性實現圖片的預加載。
1、用 CSS 和 JavaScript 實現預加載
- #preload-01 {
- background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px;
- }
- #preload-02 {
- background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px;
- }
- #preload-03 {
- background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px;
- }
通過 CSS 的 background 屬性將圖片預加載到屏幕外的背景上。當它們在 web 頁面的其他地方被調用時,瀏覽器就會在渲染過程中使用預加載(緩存)的圖片。該方法雖然高效,但仍有改進余地。使用該法加載的圖片會同頁面的其他內容一起加載,增加了頁面的整體加載時間。
為了解決這個問題,我們增加了一些 JavaScript 代碼,來推遲預加載的時間,直到頁面加載完畢。
- function preloader() {
- if (document.getElementById) {
- document.getElementById('preload-01').style.background =
- 'url(http://domain.tld/image-01.png) no-repeat -9999px -9999px'
- document.getElementById('preload-02').style.background =
- 'url(http://domain.tld/image-02.png) no-repeat -9999px -9999px'
- document.getElementById('preload-03').style.background =
- 'url(http://domain.tld/image-03.png) no-repeat -9999px -9999px'
- }
- }
- function addLoadEvent(func) {
- var oldonload = window.onload
- if (typeof window.onload != 'function') {
- window.onload = func
- } else {
- window.onload = function () {
- if (oldonload) {
- oldonload()
- }
- func()
- }
- }
- }
- addLoadEvent(preloader)
2、使用 JavaScript 實現預加載
- function preloader() {
- if (document.images) {
- var img1 = new Image()
- var img2 = new Image()
- var img3 = new Image()
- img1.src = 'http://domain.tld/path/to/image-001.gif'
- img2.src = 'http://domain.tld/path/to/image-002.gif'
- img3.src = 'http://domain.tld/path/to/image-003.gif'
- }
- }
- function addLoadEvent(func) {
- var oldonload = window.onload
- if (typeof window.onload != 'function') {
- window.onload = func
- } else {
- window.onload = function () {
- if (oldonload) {
- oldonload()
- }
- func()
- }
- }
- }
- addLoadEvent(preloader)
響應式圖片加載
什么是響應式圖片加載?其實就是在不同分辨率的設備上顯示不同尺寸的圖片,避免資源的浪費。
常用的方法就是 css3 的媒體查詢(media query)。
- @media screen and (min-width: 1200px) {
- img {
- background-image: url('1.png');
- }
- }
- @media screen and (min-width: 992px) {
- img {
- background-image: url('2.png');
- }
- }
- @media screen and (min-width: 768px) {
- img {
- background-image: url('3.png');
- }
- }
- @media screen and (min-width: 480px) {
- img {
- background-image: url('4.png');
- }
- }
此外,還可以使用 HTML5 的 picture 屬性進行響應式處理。方法如下:
- 創建 picture 標簽。
- 放置多個 source 標簽,以指定不同的圖像文件名,進而根據不同的條件進行加載。
- 添加一個回退的元素
- <picture>
- <source srcset="src/img/l.png" media="(min-width: 1200px)" />
- <source srcset="src/img/2.png" media="(min-width: 992px)" />
- <source srcset="src/img/4.png" media="(min-width: 768px)" />
- <img src="src/img/4.png" />
- </picture>
需要注意的是:現在很多瀏覽器對于 picture 這個標簽還不支持,使用的時候需要加以注意。
picture.png
漸進式圖片
漸進式圖片的意思是在高畫質圖像加載完之前會先顯示低畫質版本。低畫質版本由于畫質低、壓縮率高,尺寸很小,加載很快。在兩者之間我們也可以根據需要顯示不同畫質的版本。
cat.png
漸進式圖片可以讓用戶產生圖片加載變快的印象。用戶不再盯著一片空白區域等待圖片加載,而能看到圖像變得越來越清晰,這樣對用戶體驗也是友好的。
骨架屏技術也是類似的原理。
總結
- 選擇合適的圖片格式和壓縮大圖,可從根源上截圖大圖加載過慢的問題。
- 使用雪碧圖,iconfont,base64,css 代替圖片等可減少圖片 http 請求,提高頁面加載速度。
- 使用 CDN 圖片可達到分流的效果,減少服務券壓力。
- 圖片懶加載,預加載,漸進式圖片等可不同程度減少白屏時間,提高產品體驗。
本文在github做了收錄