重新思考 SVG Icon
SVG Icon + Custom Element
這一年多以來 SVG Icon 一直都是一個常見的議題,我們使用 SVG 可以輕易的做到以前 icon font 或 png icon 做不到的事情,像是:
- 使用 css 切換 icon 的 theme
- Icon 內部元素動畫
- 使用 class 切換 state 來改變 icon 顯示的圖案
- RWD
目前技術以及問題
針對目前提出的 SVG Icon 技術提出一些問題,不是針對技術本身,而是針對 “Icon” 這個應用場景。
SVG Sprites
不管使用 symbol 或 group SVG Sprites 都讓一些 SVG 強項顯現不出,跟以往的 icon font 差不多,這問題是出在 <use>,我們無法從主網頁對 <use> 的 SVG 裡的 elmenet 作樣式的改變,我們只能針對我們目當前網頁內的 SVG 作修改,下面會舉一些實際狀況。
sprite
<svg>
<symbol id="icon-">
</symbol>
<symbol id="icon-2">
<!-- <path>... -->
</symbol>
</svg>
主網頁
<svg class="icon">
<use xlink:href="sprite.svg#icon-1" />
</svg>
狀況 1: css 操作部分 icon

上圖中的兩個 icon 很明顯只差在中間的箭頭方向,其他部分完全一樣,一般情況下我們可以直對 login 的箭頭作 transform 旋轉 180deg 來得到 logout icon:
#icon-account.logout .arrow {
transform: rotate(180deg);
}但是 <use> 讓我們無法做到這一點,同理,我們無法使用 :hover 這類偽元素來作 transition:
#icon-account .arrow {
transition: all .4s;
}#icon-account:hover .arrow {
fill: #f60;
}我們也無法對 icon 作 state 或 theme 的切換:
#icon-account.theme-1 {
//...
}#icon-menu.has-opened {
//...
}我們唯一能做的是對主網頁的 SVG 作樣式改變:
.icon:hover {
fill: #f60;
}但是這樣會同時套用到箭頭跟右邊那框框,有時候感覺起來很不理想。
狀況 2: 無法使用 css animation
在 Chrome 上 sprite 裡的 css animation 只會停留在 0% 時候的樣式,他有執行,但詭異的停止了,不知道這是不是一個 bug,Safari、Firefox 正常。
Responsive
Responsive SVG 使用 SVG 當作 background,在主網頁的 css 寫 media query 來改變 background-position,這個作法讓我們完全不可能對 SVG 作操作。
Rethinking Responsive SVG 的問題也差不多,不過他的 Responsive 解法是目前看到最漂亮的,可以漂亮的搭配 sprite 使用。
Making SVGs responsive with css 提到的所有方法都存在相同問題。
後端插入 Inline SVG
Inline SVG 確實可以讓我們使用全部的 SVG 優點,但這… 誰用的下去,一兩張圖還可以,可是一個頁面裡面怎麼可能只有一兩個 icon?
Iconic
跟 Inline SVG 有一樣優勢,不過 Iconic 使用 JS 注入 SVG,不是直接寫在 html 裡,唯一的問題在於他(要錢)不會監測網頁變動,當我們用 JS 插入新 icon(可能是 SPA 頁面包含的 Icon)他不會知道,必須我們自己 call inject method。
偽 Responsive。
詳細 API 在這裡
Custom Element
iconic 是目前最接近理想的方式,只要在多包一層,用法就會很漂亮,所以這部份會採用類似 iconic 的作法。
為什麼使用
- 讓 browser/polyfill 自己執行 inject method
- 讓 browser/polyfill 自己處理 responsive,避免手動執行 update method
- html 乾淨
- 完美 fallback
- polyfill 支援度良好(IE8+)
- polyfill 很輕巧(500行)
- 拔除容易,一支 script 拔掉就全部解決
太新,不敢用?
How GitHub is using Web Components in production
這邊有一篇關於 GitHub 將 Custom Element 用在 production 產品上的文章,看完後可以了解為什麼他們敢作這樣的嘗試。
Polyfill
單純使用 Custom Eelemnt 不使用其他 Web Component 標準可以做到 IE8+ 的瀏覽器支援,polyfill 才 500 行。
用法:
<script src="//cdnjs.cloudflare.com/ajax/libs/document-register-element/0.1.2/document-register-element.js"
></script>
建立 Custom Element
為了之後處理 fallback 方便,我們使用現有的 element 作擴充,icon 部分建議選用 span,不要用 i,i 是 bootstrap 帶來的惡夢之一,想知道為什麼可以看篇 <I> tag for icons? 。
png fallback
如果我們不自訂一個新的 element 而是利用現有 element 作擴充就可以用 css 快速的實作 fallback 機制:
加入檢查 registerElement 的程式碼
if (window.registerElement) {
// 把上方所有程式碼放到 if 裡面
}createdCallback 修改
icon 的 html
<spna is="my-icon" icon="login"></span>
css
span[is="my-icon"] {
background-image: url(sprite.png);
}span[icon="login"] {
background-position: left top;
}span[is="my-icon"].custom-element {
background: none;
}偽 Responsive
跟 iconic 一樣,我們做不到真的 Responsive,不過我們可以做的比 iconic 像一點,這邊介紹兩種作法,建議用 option 來切換作法。
- 只檢查 span 插入 dom 時的尺寸
- 用 requestAnimationFrame 不斷檢查 span 尺寸變化
插入時檢查尺寸
requestAnimationFrame
Theme/State
同 fallback 作法,不想重複了。
theme
<span is="my-icon" icon="login" theme="xxx-theme"></span>
or
<span is="my-icon xxx-theme" icon="login"></span>
state
<span is="my-icon" icon="login" state="xxx-state"></span>
or
<span is="my-icon xxx-state" icon="login"></span>
結束!