Icon 進化史

「南方古猿」之 png sprite

看到上面這張圖,相信好多資深前端會感到很親切。javascript

早期爲了減小資源的請求,人們想到了將小的 png 圖片合併到一張圖上,而後根據 background-position 來顯示不一樣的圖片。css

早期還有靠人肉來測量座標,隨着構建工具的發展,咱們能夠用一些插件,如 grunt-spritesmithgulp.spritesmith 等。它能夠幫助咱們自動合成,並生成好 css, 位置都計算好的。html

那麼使用 png 圖片這種方式它的優勢就是兼容性好。可是一旦開發多了,它的不便變體現出來了,換顏色、改大小、透明度、多倍屏等等。前端

因此對於這種方式咱們只能緬懷。java

「能人」之 Iconfont

因而人們又想出了用字體文件取代圖片文件:Iconfont。react

雖然早期製做或尋找合適字體比較麻煩,但隨着各類字體庫的網站出現,如: http://www.iconfont.cn ,那都不是事了。再加上 css 的自定義字體的兼容性很是好,Iconfont 迅速開始流行起來。以這個站爲例,大概看下使用方法:android

  1. 在 Iconfont 中建立本身的項目,將須要使用的圖標添加到本身的項目中。webpack

  2. 複製出 Unicode 或 Font classcss3

  3. 所有代碼以下git

@font-face {
  font-family: 'iconfont';  /* project id 38792 */
  src: url('//at.alicdn.com/t/font_1444792316_9706304.eot');
  src: url('//at.alicdn.com/t/font_1444792316_9706304.eot?#iefix') format('embedded-opentype'),
  url('//at.alicdn.com/t/font_1444792316_9706304.woff') format('woff'),
  url('//at.alicdn.com/t/font_1444792316_9706304.ttf') format('truetype'),
  url('//at.alicdn.com/t/font_1444792316_9706304.svg#iconfont') format('svg');
}
.iconfont {
  font-family:"iconfont" !important;
  font-size:16px;
  font-style:normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-tishi:before { content: "\e600"; }
<i class="iconfont icon-tishi"></i>

這裏有demo

在實際開發中,咱們會把經常使用的一些圖標封裝成組件,直接使用。像這樣

<i class="kuma-icon kuma-icon-success"></i>

Iconfont 用起來挺方便的,並且兼容性也十分的好,大小、顏色可隨意改變。

但它仍有缺陷:

  1. 字體須要加載資源

  2. 有時候可能會出現鋸齒

  3. 只能被渲染成單色或者css3的漸變色

因此咱們要繼續進化。

「直立人」之 svg symbol

使用 svg ,這裏所謂的進化並非 svg 自己的進化,由於 svg 並不晚於 Iconfont。只是環境(兼容性)的緣由致使它無用武之地。就像最近火的一塌糊塗的 AI, 其實最先在 1956 年就提出了。隨着外界因素的進化,IE6/7/8 的淘汰, android 4.x 的開始,svg 的機會變到來了。先看下兼容性:
image

svg 的使用方式:

  • 保存成文件

    • 須要請求加載資源

  • inline 方式

    • 在 html 一坨坨,很麻煩

  • symbol

    • 適合咱們作組件

The <symbol> element is used to define graphical template objects which can be instantiated by a <use> element.

經過 <symbol> 定義的 svg 模板,咱們可使用 <use> 來加載它。

<svg width="120" height="140">
<!-- symbol definition  NEVER draw -->
<symbol id="sym01" viewBox="0 0 150 110">
  <circle cx="50" cy="50" r="40" stroke-width="8"
      stroke="red" fill="red"/>
  <circle cx="90" cy="60" r="40" stroke-width="8"
      stroke="green" fill="white"/>
</symbol>

<!-- actual drawing by "use" element -->
<use xlink:href="#sym01"
     x="0" y="0" width="100" height="50"/>
</svg>

那麼 <symbol> 是怎麼來的呢?

一樣,在這個構建工具十分發達的時刻。
最開始咱們使用了 gulp-svg-symbols,它能夠將指定目錄中的 svg 自動合併到一個 svg 文件中,文件裏包括了全部 icon 的 symbol 模板,而後再將這個模板將其隱藏放到 index.html 中。

可是這個庫有個坑點是它依賴了一個 Unicode 的包,這個包在國內安裝炒雞慢,因而咱們棄用了它,使用了gulp-svgstore

按照這種方式咱們成功的開發一 salt-icon 這個組件,裏面包括了一些經常使用的 icon。使用方式:

<Icon name="success" className="icon-success"/>

這樣咱們在 mobile 端用 svg 替代了 Iconfont,解決了上述 Iconfont 提到的問題。

可是很快咱們就發現,在 index.html 中引入那一坨 symbol 模板是極其噁心的。

隨着 webpack 打包的成熟,各類 loader,咱們將那一坨 symbol 模板直接打包成一個 salt-icon-source.js 文件,在這個文件中將其 append 到 body 上。

同時發現了上述提到的 iconfont 網站也支持直接導出 symbol 文件。

import React from 'react';
import ReactDOM from 'react-dom';
import IconSource from './svg/salt-icon-symbols.svg';


const WRAPPER_ID = '__SaltIconSymbols__';
const doc = document;
let wrapper = doc.getElementById(WRAPPER_ID);
if (!wrapper) {
  wrapper = doc.createElement('div');
  wrapper.id = WRAPPER_ID;
  wrapper.style.display = 'none';
  doc.body.appendChild(wrapper);
  ReactDOM.render(<IconSource />, wrapper);
}

這樣雖然解決了引入模板的那個問題,可是後面又發現的 symbol 在安卓 4.3.x 下沒法顯示,也就是說 symbol 的兼容性並無直接使用 svg 好。

而後咱們經過使用一個叫 svg4everybody 的庫,解決了上述兼容性問題。(它的原理是若是發現不支持 symbol 的,它會經過 xlink:href 拿到 svg 的資源,而後動態建立一個 svg,插入到當前位置)

雖然解決了兼容性的問題,可是咱們深深的感受到了這種方式的不優雅。

講的這裏,可能會有人會有疑問,既然已經有 svg-react-loader 了,爲何不直接 import svg 文件?

業務中使用的圖片固然能夠直接 import 加載,但一些經過的圖標咱們但願是能統一塊兒來,作出組件,更方便的使用。

並且咱們組件中還會對 svg 處理了它事件不能冒泡的問題,也就是在某些低版本的安卓機上,svg 圖標是沒法點擊的。解決方案有兩種:

  • 貼膜,不過這樣會致使多一層結構嵌套

  • 去掉事件

svg {
  pointer-events: none;
}

不過,這個問題能夠給我帶來啓示,‘既然已經有 svg-react-loader 了’,那麼 svg-loader 裏作了什麼呢?symbol 的方式或許真的能夠淘汰了。

「智人」之 svg

看下 svg-react-loader 的實現
首先有一個模板

render () {
    const props = this.props;
      return (
        <svg {...props}>
          <%= innerXml %>
        </svg>
      );
    }

而後有一個 sanitize.js ,會對 svg 作一些處理,加上標準的 xml namespace, 把 React 特有的屬性 class / for 轉化爲 className / htmlFor, 把屬性名轉化爲駝峯。

最後根據模板生成這樣一段代碼

var React = require('react');

module.exports = React.createClass({

    displayName: "Test",

    getDefaultProps () {
        return {"width":"1024","height":"1024","viewBox":"0 0 1024 1024","version":"1.1","xmlns":"http://www.w3.org/2000/svg"};
    },

    render () {
        var props = this.props;

        return <svg {...props}>
          <path d="M512.002047... fill="#272636"/>
        </svg>;
    }
});"

這樣的代碼咱們就能夠直接在 react 中直接使用了。

因此咱們的組件藉助這樣的思想,徹底棄用了 symbol 模式。

咱們先掃描對應的 svg 文件,將其按上面的思路生成一個個單獨的 js 文件。
在組件層面能夠再封裝一層,統一引入,提供一個通用的調用方式,和上面同樣。

<Icon name="success" className="icon-success"/>

更好的是你也能夠單獨引用每個文件,減少使用體積。

最後咱們憧憬一下,隨着 react 15.x 的發佈,react 對 svg 的支持愈來愈好了,隨着 IE 8 也即將被遺棄,咱們的 PC 端也有望從 Iconfont 轉換到 svg 了。

相關文章
相關標籤/搜索