SVG Sprites
20 Aug 2018小圖示的顯示和壓縮技術的演進從 .png
、.jpg
或 .gif
圖檔、Image Sprites,到 Base64、Icon Fonts 再來到 SVG 與 SVG Sprites,來看看到底 SVG Sprites 哪裡好 (✪ω✪)
本文前半段著重在產生 SVG Sprites 的方法和如何使用 SVG Symbols 定位;後半段說明各種 SVG Icon 的使用方式和優缺比較。
如何產生 SVG Sprites?
以下是使用 gulp-svg-sprite 來產生 SVG Sprites。
- 將各個 SVG 圖檔放到同一個資料夾中。注意,SVG 圖檔要先經過最佳化,並且 fill 屬性要刪除,這樣之後才用使用 CSS 客製化填色。
安裝 svgo 並優化所有 svg 資料夾下的 SVG 圖檔。
npm install svgo --save-dev
svgo svg/*.svg
svgo 對圖檔優化後的結果。
或使用 gulp-svgo 來做優化,這會在下文中與 gulp-svg-sprite 一併說明。
- 安裝 gulp-svg-sprite,而這裡還會用到 gulp、gulp-plumber 和 gulp-svgo,所以如果專案沒裝的話,要順便裝一裝。
npm install gulp gulp-plumber gulp-svg-sprite gulp-svgo --save-dev
gulpfile.js 內容如下。打開 symbol 模式,這樣之後就可以使用 SVG Symbols 來定位,超級方便的!
const gulp = require('gulp');
const plumber = require('gulp-plumber');
const svgSprite = require('gulp-svg-sprite');
const svgo = require('gulp-svgo');
const config = {
mode: {
symbol: true,
},
};
gulp.task('default', ['icons']);
gulp.task('watch', () => {
gulp.watch(['assets/images/svg/*.svg'], ['icons']);
});
gulp.task('icons', () => {
gulp
.src('assets/images/svg/*.svg', { cwd: '' })
.pipe(plumber())
.pipe(svgo())
.pipe(svgSprite(config))
.on('error', (err) => {
console.log(err);
})
.pipe(gulp.dest('style/generals/sprites/'));
});
點此看完整程式碼。
執行 gulp icons
,先將 svg 整個資料夾的 SVG 圖檔優化,再製作成 SVG Sprites 並輸出到 sprites 資料夾底下。
除了 SVG Symbols,還有多種選擇,例如:利用 class 在 background 使用個別的 SVG 圖檔等,請參考這裡。而為什麼選 SVG Symbols 則可繼續看下文。
實作元件包裝 SVG Sprites
使用 React.js 實作一個包裝 SVG Sprites 的元件,這樣引用的時候就只要輸入 icon 名稱和相關資訊就好了,其他亂亂的部份就藏好好不要讓其他人發現。注意,屬性名稱都是 camelCase,因此 <use>
的 xlink:href
要改為 xlinkHref
。
import React from 'react';
import PropTypes from 'prop-types';
import sprite from './sprite.symbol.svg';
const Icon = ({ name, width, height, fill, title }) => {
return (
<svg
className={`icon-${name}`}
width={width}
height={height}
fill={fill}
role='img'
aria-label={title}
>
{title && <title>{title}</title>}
<use xlinkHref={`${sprite}#${name}`} />
</svg>
);
};
Icon.propTypes = {
name: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number,
fill: PropTypes.string,
title: PropTypes.string,
};
Icon.defaultProps = {
width: 40,
height: 40,
fill: '#fff',
title: null,
};
export default Icon;
點此看完整程式碼。
使用這個元件 Icon,預設是白色的。
<Icon name='logo' title='吃什麼,どっち' />
使用 fill 更改顏色為深灰色。
<Icon name='logo' title='吃什麼,どっち' fill='#525252' />
打開瀏覽器開發工具來看實作結果。
SVG Icon 使用方式總整理
觀察目前市場上產品使用 SVG Icon 的方式,列舉如下,稍後會詳細說明。
- Inline SVG,這是指直接將 SVG Icon 放到畫面上。
- SVG in CSS Background,這是指利用 class 在 background 使用個別的 SVG 圖檔。
- Using SVG as an
<img>
,這是指使用<img>
載入個別 SVG Icon。 - SVG in Data URI,這是指將個別 SVG Icon 轉為 Data URI 再放到 CSS 背景來使用。
- SVG Sprites by Using SVG Symbols,這是指將個別的 SVG Icon 集合成一個 SVG Sprites,並使用 SVG Symbols 來定位。
- SVG Sprites in Data URI,這是指將 SVG Sprites 轉為 Data URI,再利用 CSS 背景定位。
先說結論,目前最佳解是「SVG Sprites by Using SVG Symbols」。
Inline SVG
Inline SVG 是最簡單的方法,直接將 SVG Icon 放到 View 上即可。
範例
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="14" viewBox="0 0 18 14">
<path fill="#342E36" d="..."></path>
</svg>
優點
<svg>
標籤具備語義化。- 在無障礙方面能透過
<title>
、<desc>
和 aria-labelledby 提供輔助說明。 - 避免讀取圖檔而產生的 HTTP Request。
- 可利用 CSS 設定更改 SVG 圖檔樣式,例如在
<svg>
上加上 class 或 inline-style 來設定 fill 屬性。 - 保留動畫和狀態,例如:若 icon 有 hover 效果不需要再另存圖片。
缺點
- View 上的程式碼較雜亂。
- 無法快取圖片,只能將整個 View 快取,例如:使用 HTTP Caching。
SVG in CSS Background
個別 SVG Icon 可利用 CSS 背景來使用 SVG 圖檔。
範例
.icon {
background-image: url(icon.svg);
width: 10px;
height: 10px;
}
<span class="icon"></span>
優點
- 改進 Inline SVG 在 View 上程式碼雜亂的問題。
- 可利用 CSS 設定更改 SVG 圖檔樣式,例如在
<svg>
上加上 class 來設定 fill 屬性。 - 保留動畫和狀態,例如:若 icon 有 hover 效果不需要再另存圖片。
- 可快取樣式檔和圖片。
缺點
- 讀取圖檔需要額外的 HTTP Request。
- 無語義化。
- 無法做到無障礙,必須要設定 arial-hidden 來避免被機器朗讀。
Using SVG as an <img>
使用 <img>
載入個別 SVG Icon。
範例
<img src="icon.svg" />
優點
- 改進 Inline SVG 在 View 上程式碼雜亂的問題。
<img>
標籤具語義化。- 保留動畫和狀態,例如:若 icon 有 hover 效果不需要再另存圖片。
- 可快取圖片。
缺點
- 無法使用 CSS 改變 SVG 圖檔樣式,例如:改變顏色。
- 讀取圖檔需要額外的 HTTP Request。
SVG in Data URI
將個別 SVG Icon 轉為 Data URI 再放到 CSS 背景來使用。
範例
.icon {
background: url(data:image/svg+xml;utf8,<svg width='17' height='15' viewBox='0 0 17 15' xml…46 2.446 0 0 1 7.32 7.24H9.2z' fill='#FFF' fill-rule='nonzero'/></g></svg>) no-repeat 0 0;
}
<span class="icon"></span>
優點
- 改進 Inline SVG 在 View 上程式碼雜亂的問題。
- 避免讀取圖檔而產生的 HTTP Request。
- 檔案可經編碼後適度優化、縮小,意即在 Data URI 中直接使用 SVG 碼是比轉 Base64 來得小很多,可參考這裡。
- 可快取樣式檔。
缺點
- 無語義化。
- 無法做到無障礙。
- 無法使用 CSS 改變 SVG 圖檔樣式,例如:改變顏色。
- 狀態的改變或動畫必須另存檔案,例如 hover 狀態就要另外寫成一個 class,而不能使用同一 Data URI 字串。
- 針對 IE 要做額外處理和提供 fallback。
SVG Sprites by Using SVG Symbols
將個別的 SVG Icon 集合成一個 SVG Sprites,並使用 SVG Symbols 來定位。
範例
<svg class="icon-cart">
<use xlink:href="sprite.svg#cart"></use>
</svg>
.icon-cart {
fill: red;
&:hover {
fill: white;
}
}
優點
- 節省 HTTP Request,比起個別的 icon 讀取次數,只會有一次而已。
- Sprites 圖檔可被快取。
- 由於節省 HTTP Request 和快取,可加快畫面載入速度。
- 容易定位,在
<use>
指定xlink:href
icon 名稱即可,不用算位置。 - 可利用 CSS 設定更改 SVG 圖檔樣式,例如在
<svg>
上加上 class 或 inline-style 來設定 fill 屬性。 - 保留動畫和狀態,例如:若 icon 有 hover 效果不需要再另存圖片。
缺點
- 若 SVG Sprites 是外部檔案,必須經過跨網域存取,則 IE 9-11 必須使用 polyfill 來處理才能正常顯示。推薦使用 SVG4Everybody 和 SVGxUse。
SVG Sprites in Data URI
將 SVG Sprites 轉為 Data URI,再利用 CSS 背景定位。
範例
.icon {
background-image: url('data:image/svg+xml;…');
background-position: 35% 35%;
}
<span class="icon"></span>
Demo。
優點
- 改進 Inline SVG 在 View 上程式碼雜亂的問題。
- 避免讀取圖檔而產生的 HTTP Request。
- 檔案可經編碼後適度優化、縮小。
- 可快取樣式檔。
缺點
- 和傳統的 Images Sprites 方式類似,會有因定位錯誤而滲透的問題。並且,定位的位置需要另外紀錄,比起 SVG Symbols 來得麻煩。
- 無語義化。
- 無法做到無障礙。
- 無法使用 CSS 改變 SVG 圖檔樣式,例如:改變顏色。
- 狀態的改變或動畫必須另存檔案,例如 hover 狀態就要另外寫成一個 class,而不能使用同一 Data URI 字串。
- 針對 IE 要做額外處理和提供 fallback。工具-Grumpicon 可提供完整 fallback 機制。
總結
目前我考慮兩個方案:SVG in Data URI 和 SVG Sprites by Using SVG Symbols,後者勝出的主要原因是彈性和定位方便,而前者雖然有機會讓檔案壓縮得更小,卻要處理瀏覽器相容的問題,因此暫時捨棄 (┐「﹃゚。)
工具
自動將 SVG 資料夾內的所有 Icon 製作成 SVG Sprites 以供使用。
- SVG Sprite
- gulp-svg-sprite,範例如本文前半段。
- svg4everybody:針對 IE 提供完整 fallback 機制。
- SVGxUse:針對 IE 提供完整 fallback 機制。
- Grumpicon:針對 IE 提供完整 fallback 機制。
備註
整理目前各大網站 Icon 所使用的技術。
- 淘寶:Base64、Icon Fonts、Imgae Sprites、單獨的 png 圖片(未經 Sprites)
- Pinkoi:SVG in Data URI、SVG in CSS Background、Inline SVG、Using SVG as an
<img>
、Base64、Imgae Sprites、單獨的 png 圖片(未經 Sprites) - Yahoo 拍賣:SVG in CSS Background、Icon Fonts、單獨的 png 圖片(未經 Sprites)
- Evernote:Inline SVG、Base64
- Netflix:Icon Fonts
- Airbnb:Inline SVG、Icon Fonts
- Facebook:Imgae Sprites、單獨的 png 圖片(未經 Sprites)
- GitHub:Inline SVG
- Google Calendar:Inline SVG、單獨的 png 圖片(未經 Sprites)
後記
(2018/08/27 更新) 隨著網站的成長, icon 數也跟著增加,這時候做成的 SVG Sprites 就挺大的,雖然有做 preload 但畫面載入時 SVG Sprites 還是會延遲或閃跳,最後只好轉為 base64 只求不發出 HTTP Request,希望之後能找到更好的解法。