重新思考 SVG Icon

SVG Icon + Custom Element

這一年多以來 SVG Icon 一直都是一個常見的議題,我們使用 SVG 可以輕易的做到以前 icon font 或 png icon 做不到的事情,像是:

  1. 使用 css 切換 icon 的 theme
  2. Icon 內部元素動畫
  3. 使用 class 切換 state 來改變 icon 顯示的圖案
  4. 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

iconic account icons

上圖中的兩個 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


  1. Responsive SVG
  2. Rethinking Responsive SVG
  3. Making SVGs responsive with css

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 的作法。

為什麼使用


  1. 讓 browser/polyfill 自己執行 inject method
  2. 讓 browser/polyfill 自己處理 responsive,避免手動執行 update method
  3. html 乾淨
  4. 完美 fallback
  5. polyfill 支援度良好(IE8+)
  6. polyfill 很輕巧(500行)
  7. 拔除容易,一支 script 拔掉就全部解決

太新,不敢用?


How GitHub is using Web Components in production

這邊有一篇關於 GitHub 將 Custom Element 用在 production 產品上的文章,看完後可以了解為什麼他們敢作這樣的嘗試。

Polyfill


document-register-element

單純使用 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?

https://gist.github.com/poying/3207f498208341eb2de8

png fallback


如果我們不自訂一個新的 element 而是利用現有 element 作擴充就可以用 css 快速的實作 fallback 機制:

加入檢查 registerElement 的程式碼

if (window.registerElement) {
// 把上方所有程式碼放到 if 裡面
}

createdCallback 修改

https://gist.github.com/poying/379a45aeb9b1681a7ae2

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 來切換作法。

  1. 只檢查 span 插入 dom 時的尺寸
  2. 用 requestAnimationFrame 不斷檢查 span 尺寸變化

插入時檢查尺寸

https://gist.github.com/poying/4681a8d64d63f5aaa257

requestAnimationFrame

https://gist.github.com/poying/66b20c8fe2936ddb0bcd

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>

結束!