sketch 一鍵生產 icon-font

從 sketch 一鍵生產 icon-font

Icon font 雖然不是什麼新的技術, 不過從這一連串的實作過程可以學到很多東西, 且順便分享一下使用在專案內會遇到的問題。


實作流程可以拆成以下:

  1. 使用 sketch 編輯管理所有的 icon。
  2. 使用 sketchtool 匯出所有 icon 並為 svg 格式。
  3. 使用 grunt 來實作一鍵生產的任務。
  4. 使用 grunt-webfont library 將 svg 圖案轉換成 font 和相關的 css。

Grunt 是什麼?

The JavaScript Task Runner In one word: automation. The less work you have to do when performing repetitive tasks like minification, compilation, unit testing, linting, etc, the easier your job becomes.

簡單說就是可以將任務自動化、簡化工作流程, 例如:壓縮檔案、編譯、測試、JSlint檢查等 …。


需要環境:

Node.js npm


使用 sketch 編輯管理所有的 icon

  1. 打開 sketch 並新增一個 document。
  2. 左上角有一個 insert 按鈕, 點選 artboard, 會看到滑鼠指標變成十字, 然後在右邊空白區域隨便拉一塊畫布。

  3. 點選右上角 View -> Grid Settings, 設定一下格線。

  4. 將設計師畫好的 svg icon, 拉進 artboard 裡面, 或者你也可以去 flaticon 下載免費的 icon。 調整一下大小, 圖例為 72x72, 一個上下置中對齊、一個貼齊底線。

  5. 左上角 insert 新增一塊 slice, 並將大小設為 96x96 對齊在該 icon 的區域, 並將 slice 改一下名字。 slice 所涵蓋的區域, 即為待會匯出時 icon font 的內容, 每一塊 slice 的名稱將會是 icon 的後綴字元。

  6. 存檔, 並命名為 icons.sketch, 假設放置在專案下 src/icons/icons.sketch

.
├── node_modules
├── package.json
├── src
│   ├── icons
│   │   └── icons.sketch

使用 sketchtool 匯出所有 icon 並為 svg 格式

  1. 安裝 sketchtool, 根據官方說法, sketchtoolsketch 是綁在一起的, 直接使用 sketch app 附隨的 sketchtool, 目的是為了要讓兩者的版本一致。 > SketchTool comes bundled with Sketch (and Sketch Beta). You can find it in Sketch.app/Contents/Resources/sketchtool/bin/sketchtool.

It is recommended that you use it from inside Sketch, and not copy it to another location, so that you’re always using the latest version (SketchTool is updated whenever Sketch is updated, and you’ll need to use the updated version to read documents saved with the latest version of Sketch).

以 Mac 為例, 你可以用以下這條路徑執行 sketchtool

$ /Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool -v

sketchtool Version 49.3 (51167)

2. 由於 sketchtool 路徑太長, 以下的描述會直接簡寫成 sketchtool, 可以由 $ sketchtool -h 得知, 要將 sketch 匯出, 指令為:

$ sketchtool export slices src/icons/icons.sketch --output=tmp/src/icons/svg --formats=svg

會將檔案以 slice 為單位匯出, 並指定格式為 svg。

.
├── node_modules
├── package.json
├── src
│   ├── icons
│   │   └── icons.sketch
├── tmp
│   └── src
│       └── icons
│           └── svg
│               ├── facebook.svg
│               └── twitter.svg

使用 grunt 來實作一鍵生產的任務

  1. 先來寫一個基本的 grunt task, 安裝一下 grunt 和 grunt-shell。
$ npm install grunt grunt-shell --save-dev

通常 grunt 都是在開發環境下執行, 使用 --save-dev 來宣告版本依賴在 devDependencies 底下。

  1. 新增一個 Gruntfile.js 在專案根目錄並加入以下程式碼。
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    shell: {
      lsDir: {
        command: 'ls src/icons'
      }
    }
  });

  // Load the plugin used in grunt
  grunt.loadNpmTasks('grunt-shell');

  // Default task(s).
  grunt.registerTask('default', ['shell:lsDir']);

};

執行 $ grunt

$ grunt
Running "shell:lsDir" (shell) task
icons.sketch

Done.
  1. 參考 grunt-shell 的 document, 將以上程式碼改寫成匯出 sketch svg 的任務。
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    mySketchToolPath: '/Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool',
    shell: {
      lsDir: 'ls src/icons',
      exportSketchSvgIcons: {
        command: [
          'rm -rf tmp/src/icons/svg/*',
          '<%= mySketchToolPath %> export slices src/icons/icons.sketch --output=tmp/src/icons/svg --formats=svg'
        ].join(' ; ')
      }
    }
  });

  // Load the plugin used in grunt
  grunt.loadNpmTasks('grunt-shell');

  // Default task(s).
  grunt.registerTask('default', ['shell:lsDir']);
  grunt.registerTask('generate-icons', ['shell:exportSketchSvgIcons']);

};

10 行:避免舊的檔案存在, 一律在匯出前先刪除所有 tmp 底下的 icon。 11 行:grunt-shell 提供 template pattern, 可以插入在 grunt config 設定的內容。 22 行:註冊一個 task, 第二個參數為陣列, 亦即可以合併多個項目在同一個 task 裡面。

執行可以看到有兩個 svg 檔案正確被匯出:

$ grunt generate-icons
Running "shell:exportSketchSvgIcons" (shell) task
2018-04-23 03:00:34.616 sketchtool[58895:113407689] *** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
Exported twitter.svg
Exported facebook.svg

Done.

使用 grunt-webfont library 將 svg 圖案轉換成 font 和相關的 css

  1. 先安裝 grunt-webfont 指定版本為 1.7.0。
$ npm install grunt-webfont@1.7.0 --save-dev
  1. 根據 grunt-webfont configuration, 新增 webfont 的設定並加入到 task generate-icons(39行)。
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    mySketchToolPath: '/Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool',
    shell: {
      lsDir: 'ls src/icons',
      exportSketchSvgIcons: {
        command: [
          'rm -rf tmp/src/icons/svg/*',
          '<%= mySketchToolPath %> export slices src/icons/icons.sketch --output=tmp/src/icons/svg --formats=svg'
        ].join(' ; ')
      }
    },
    webfont: {
      compileIcons: {
        src: 'tmp/src/icons/svg/*.svg', // 從 sketch 匯出的 svg 來源
        dest: 'src/font', // 字型檔案匯出目的地
        destCss: 'src/stylesheets', // css 檔案匯出目的地
        options: {
          styles: ['font', 'icon'], // 匯出的 css 要包含哪些 css 宣告
          types: ['eot', 'ttf', 'woff', 'woff2', 'svg'], // 要匯出的字體格式
          syntax: 'bootstrap', // 以 boostrap 風格命名, class name 會是 icon-myicon
          engine: 'node', // 字體轉換的引擎, 使用 node 轉換
          fontHeight: 96, // 在 sketch 裡面, 一個 slice 的大小為 96x96, 故設為 96
          descent: 12, // 字體下邊線高度
          destHtml: 'src/icons' // 範例 html 檔案匯出目的地
        }
      }
    }
  });

  // Load the plugin used in grunt
  grunt.loadNpmTasks('grunt-shell');
  grunt.loadNpmTasks('grunt-webfont');

  // Default task(s).
  grunt.registerTask('default', ['shell:lsDir']);
  grunt.registerTask('generate-icons', ['shell:exportSketchSvgIcons', 'webfont:compileIcons']);

};

執行 task:

$ grunt generate-icons
Running "shell:exportSketchSvgIcons" (shell) task
2018-04-23 04:03:33.184 sketchtool[61955:113593325] *** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
Exported twitter.svg
Exported facebook.svg

Running "webfont:compileIcons" (webfont) task
Font icons with 2 glyphs created.

Done.

此時檔案結構如下:

.
├── Gruntfile.js
├── node_modules
├── package.json
├── src
│   ├── font
│   │   ├── icons.eot
│   │   ├── icons.svg
│   │   ├── icons.ttf
│   │   ├── icons.woff
│   │   └── icons.woff2
│   ├── icons
│   │   ├── icons.html
│   │   └── icons.sketch
│   └── stylesheets
│       ├── icons.css
├── tmp
│   └── src
│       └── icons
│           └── svg
│               ├── facebook.svg
│               └── twitter.svg

之後只要在專案內 css @import "src/stylesheets/icons.css"; 就可以使用了。

以後只需要: * 新增 icon 到 sketch * $ grunt generate-icons

完成!


你應該要知道

1. 為什麼要宣告這麼多種字型檔? 用意是什麼?

-> 主要是因為瀏覽器相容性問題, 目前沒有一種字型格式可以通用在各種版本的瀏覽器上。

將 WOFF 2.0 變體提供給支援的瀏覽器 將 WOFF 變體提供給大多數瀏覽器 將 TTF 變體提供給舊版 Android (4.4 版以下) 瀏覽器 將 EOT 變體提供給舊版 IE (IE9 之下) 瀏覽器 ^

參考:網頁字型最佳化

2. 自動產生的 icons.css 裡面的這一段, 為什麼值為 \f101 可以找的到 icon ?

.icon-facebook:before {
  content:"\f101";
}

-> 使用 fontforge 打開字型檔, 可以看到每個 Unicode 號碼一一對照著各種文字。 一般我們要在 html 輸入 Unicode 符號, 可以這樣在 html 這樣輸入:

<p>&#8594; Go!</p>

同理套用在 icon font 上面, 也可以這樣顯示出 icon

<p style="font-family: icons;">&#xf101; Hello World &#xf102;</p>

常見問題

1. Icon font 有時候無法與文字垂直置中對齊

-> icon font 也是一種字體, 所保有的特行和一般字體一樣, 一般我們在不同字體之間的對齊通常是用 baseline 來對齊, 亦即 vertical-align: baseline;

所以先前教學裡, 在 grunt webfont 設定裡面, 設定 descent: 12 也是有原因的, 如下圖 sketch 裡面的設置, 因為在 webfont 設定 descent: 12, 所以文字的 baseline 在 render 的時候會上移 12px。

以前面教學的 sketch 例子, 我故意把 twitter 的 icon 貼齊底線, 然後試試在 html render 的情況。

結論: * icon 的 vertical-align 設為 baseline 目的是要跟文字對齊, 設為 middle 目的是要跟整個段落行高對齊。 * 必須知道你在 sketch 檔案和 webfont icon font 轉換的 baseline 在哪裡, 進而調整你 icon 的大小與位子。 * grunt-webfont 在 v1.6.0 會在自動產生的 icons.scss 加上 vertical-align: middle , 這個 bug 在 v1.7.0 被修正, 所以記得升級一下 $ npm install grunt-webfont@1.7.0 -D。 * 文字與 vertical-align 的關係, 可以參考這篇文章 Deep dive CSS: font metrics, line-height and vertical-align (中文版)

2. 團隊合作裡, 處理 Git 檔案衝突

因為 sketch 檔案沒有辦法處理 conflict、也沒辦法 diff 看出兩個檔案的差異, 所以會有某A原本改好 icon 要 merge, 結果發現 merge 之前有人改過 icon 並 merge 進 develop, 由於沒辦法做 diff, 所以無法知道上一個人對 sketch 改了什麼, 只好 rebase 重來, 也不能使用 merge develop, 因為會覆寫別人改過的 sketch 檔案。

-> 比較好的解法是, 規定如果有人需要改 icon, 請另外優先發出修改 icon 的 Pull Request, 以確保在 develop 上, icon.sketch 都是最新的狀態。