標籤:

SVG 圖標製作指南

原文:fvsch.com/code/svg-icon

備註:譯文已獲得作者授權,轉載請附上原文鏈接。

現在有很多種方法在網頁中使用 SVG 圖標,我並沒有把它們全部嘗試一遍,我將要介紹的方法是我們 Kaliop 的前端團隊所使用的,目前能夠很好的滿足我們的開發需求,比如:

  • 基於大型 CMS 系統的內容管理網站(非全棧 JS 的 Web App)。
  • 圖標通常簡單且單色(可能根據網站內容和交互來使用不同的顏色),也有可能是單個圖標有兩種不同顏色。
  • 支持 IE9+。

本文內容將按照以下展開:

  • 準備圖標
  • 製作 SVG sprite
  • 將圖標放入網頁
  • 在 Webpack 中使用 SVG 圖標 #譯者拓展部分
  • 用 CSS 給圖標增加樣式
  • 進階技巧
  • 部分瀏覽器存在的 bug

第一步:準備圖標

當你從設計師那裡或者繪圖工具(如 Illustrator、 Adobe Assets、 Sketch、 Inkscape等)中拿到 SVG 圖標時,你可能會直接放到網頁中,但是,如果能把圖標(用你常用的處理工具)稍微處理下,可以避免不少頭疼的問題。

圖標在 Illustrator (左) 和 Sketch (右)的畫板上顯示效果

新建一個文件或畫板

在常用的繪圖工具中新建一個文件或者畫板,將圖標複製粘貼到中間,最好確保圖標是純凈的,沒有隱藏圖層。

正方形更好

圖標不需要非得是正方形的,除非圖標太寬或者太高,否則還是建議做成正方形的圖標,更好處理。當你有像素級的需求時,比如想要在低解析度屏幕上獲得更好的顯示效果,就需要確定圖標尺寸。比如圖標需要適應 15x15 px 的網格,而且用的時候也多是這個尺寸時,就應該去創建 15x15 px 的畫板。不確定的時候,一般建議選擇 20x20 的尺寸。

毛邊問題

在邊緣區域留一點點空白,特別是對圓形圖標。瀏覽器渲染 SVG 時會做抗鋸齒處理,但是,有時抗鋸齒產生的額外像素點會跑到 viewBox 的外面,從而導致圖標的邊緣看上去被切掉了一部分,看起來有點方。

圖標邊緣未做留白處理,所以可能邊緣渲染出方形的邊,當瀏覽器對 SVG 的渲染不給力時,效果更糟糕。

因此,每次處理 16px 或 20px 的圖標時,要記得在每個邊緣留 0.5px 或 1px 的空白,還要記得導出整個畫板,而不是選中位於中間的路徑,否則邊緣的留白是不會導出。

導出 SVG

  • 在 Illustrator 中,選擇 「Save As」 並選擇格式為 「SVG」(也許選擇 「Export as…」 會更好)。
  • 在 Sketch 中,先選中畫板,點擊右下角 「Make Exportable」,並選擇格式為 「SVG」。
  • 在 Inkscape 中,選擇 「Save As」 並選擇格式為 「Optimized SVG」。

關於 SVG 的知識點

你可能學習過關於 SVG 的基礎知識,並且能讀懂 SVG 的結構。至少你知道:

  • SVG 元素: <svg>,<symbol> ,<g>, <path>
  • SVG 屬性: d, fill, stroke, stroke-width

注意:從繪圖工具中導出的 SVG 經常帶著一些不必要的內容和標籤等(其中 d 下面包含了清晰的路徑數據),可以使用工具比如 SVGOMG ,然後比較一下處理前後哪些東西是移除或簡化過的。

移除顏色數據

對於單色圖標,確保:

  1. 在源文件中, path 的顏色都是黑色(#000000)。
  2. 在導出的 SVG 文件中,沒有 fill 屬性。

如果我們在 SVG 文件中設置了 fill 屬性,就不能通過 CSS 來改變顏色了,所以最好把顏色相關數據都刪掉,至少對單色的圖標這樣。

Illustrator 導出的SVG 中 path 都是黑色(#000000)且不帶 fill 屬性,但 Sketch 導出的文件會帶,得自己手動刪掉像 fill="#000000" 這種屬性。

第二步:製作 SVG sprite

這一部分會包含不少代碼,但內容其實並不複雜。我們將創建包含多個 <symbol> 元素的 SVG 文件,每個 <symbol> 都有id 和 viewBox 屬性,且包含圖標的 <path> 元素(或者其他元素如<circle> 、 <rect> 等)。

我將這個 SVG 文件稱為 SVG sprite(參考 sprites in computer games 和 CSS),也可以被稱為 sprite sheet 或者 symbol store。

下面的 SVG sprite 中僅包含一個圖標:

<svg xmlns="http://www.w3.org/2000/svg">n <symbol id="cross" viewBox="0 0 20 20">n <path d="M17.1 5.2l-2.6-2.6-4.6 4.7-4.7-4.7-2.5 2.6 4.7 4.7-4.7 4.7 2.5 2.5 4.7-4.7 4.6 4.7 2.6-2.5-4.7-4.7"/>n </symbol>n</svg>n

往 SVG sprite 中添加圖標

下面是 Illustrator 導出的 SVG 圖標的代碼:

<?xml version="1.0" encoding="utf-8"?>n<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->n<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"n viewBox="0 0 15 15" style="enable-background:new 0 0 15 15;" xml:space="preserve">n <path id="ARROW" d="M7.5,0.5c3.9,0,7,3.1,7,7c0,3.9-3.1,7-7,7c-3.9,0-7-3.1-7-7l0,0C0.5,3.6,3.6,0.5,7.5,0.5 C7.5,0.5,7.5,0.5,7.5,0.5L7.5,0.5L7.5,0.5z M6.1,4.7v5.6l4.2-2.8L6.1,4.7z"/>n</svg>n

我們把這個圖標簡化下(手工或者通過 SVGOMG 處理),只保留 viewBox 屬性及必要的部分:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15">n <path d="M7.5,0.5c3.9,0,7,3.1,7,7c0,3.9-3.1,7-7,7c-3.9,0-7-3.1-7-7l0,0C0.5,3.6,3.6,0.5,7.5,0.5 C7.5,0.5,7.5,0.5,7.5,0.5L7.5,0.5L7.5,0.5z M6.1,4.7v5.6l4.2-2.8L6.1,4.7z"/>n</svg>n

將 <svg viewBox="…"> 寫成 <symbol id="…" viewBox="…"> 格式,放到 SVG sprite 文件中去。

<svg xmlns="http://www.w3.org/2000/svg">n <symbol id="cross" viewBox="0 0 20 20">n <path d="M17.1 5.2l-2.6-2.6-4.6 4.7-4.7-4.7-2.5 2.6 4.7 4.7-4.7 4.7 2.5 2.5 4.7-4.7 4.6 4.7 2.6-2.5-4.7-4.7"/>n </symbol>n <symbol id="play" viewBox="0 0 15 15">n <path d="M7.5,0.5c3.9,0,7,3.1,7,7c0,3.9-3.1,7-7,7c-3.9,0-7-3.1-7-7l0,0C0.5,3.6,3.6,0.5,7.5,0.5 C7.5,0.5,7.5,0.5,7.5,0.5L7.5,0.5L7.5,0.5z M6.1,4.7v5.6l4.2-2.8L6.1,4.7z"/>n </symbol>n</svg>n

你可以選擇手動處理所有圖標,也可以選擇使用工具處理,我們用了 gulp-svg-sprite 插件(附上我們的gulpfile 配置示例),還有很多圖形工具和命令行工具可以導出 SVG sprite 文件,比如Icomoon。

建議:icon文件放到同一文件夾統一管理

如果你手動創建 SVG sprite,我建議為 SVG 圖標專門開一個文件夾。

assets/n icons/n cross.svgn play.svgn search.svgn ...npublic/n sprite/n icons.svgn

當你需要重新構建 icons.svg 或者修改某個圖標時,你仍然可以找到圖標的源文件(在icons 文件夾中)。請盡量保持 SVG sprite 文件與源文件同步。當然如果你有 Grunt/Gulp 做自動構建打包時,你只需要維護一份圖標源文件(即icons 文件夾)。

第三步:將圖標放到網頁中

為了使用 SVG 圖標,我們得把它放到 HTML 中去,我們不能用 CSS 的 background 相關屬性,不能使用 ::before 等偽元素。用法如下:

<svg><use xlink:href="/path/to/icons.svg#play"></use></svg>n

為圖標提供替代文本

目前有幾種方案可以為圖標增加替代文本,經過我們自己的屏幕語音閱讀測試,下面給出我們用的方案。

首先,當不需要增加替代文本時(網頁上經常已經存在相關文字內容了),可以設置 aria-hidden="true" 來確保屏幕語音閱讀時會跳過圖標:

<a href="/news/">n <svg aria-hidden="true">n <use xlink:href="/path/to/icons.svg#newspaper"></use>n </svg>n Latest Newsn</a>n

其次,當遇到 a 標籤或者 button 的內容是圖標時,我們會在上面設置aria-label 。

<a href="/news/" aria-label="Latest News">n <svg aria-hidden="true">n <use xlink:href="/path/to/icons.svg#newspaper"></use>n </svg>n</a>n

還有一種選擇是使用 <title> 標籤,尤其是標籤相互作用時導致 aria-label 失效。舉個例子,當你在 table 中使用 yes/no 標記,你可以這樣:

<tr>n <svg>n <title>Yes</title>n <use xlink:href="/path/to/icons.svg#tick"></use>n </svg>n</tr>n

最後,切記:

  • 替代文本因根據內容來定(比如放大鏡圖標的替代文本為「顯示搜索框「或者」提交搜索「)。
  • 替代文本要做國際化。

替代文本應該根據 HTML 內容的上下文而定,有人推薦在 SVG sprite 裡面增加 <title> 標籤,但是我們實踐後發現並不總是生效,很多屏幕語音閱讀都會忽略。

外部 sprite 和內聯 sprite

目前為止我們所提到的都是外部的 sprite,但是老版本的WebKit 內核瀏覽器和所有版本的 IE 瀏覽器(低於Edge 13),只支持 <use xlink:href="#some-id"/> 這種內聯的引用。可以考慮引入比如svg4everybody, svgxuse 等來 ployfill,或者將 SVG sprite 元素寫到每個頁面的 HTML 中去。

<body>n <!-- Hidden icon data -->n <svg aria-hidden="true" style="display:none">n <symbol id="icon-play"></symbol>n <symbol id="icon-cross"></symbol>n <symbol id="icon-search"></symbol>n </svg>nn <!-- A visible icon: -->n <button aria-label="Start playback">n <svg aria-hidden="true"><use xlink:href="#icon-play"/></svg>n </button>n</body>n

兩種方法各有利弊,比較如下:

我喜歡將兩種方法混起來用,創建兩個 SVG sprite:

  1. 一個小一點的 SVG sprite 包含常用的圖標,作為內聯元素放到每個頁面中,大小 5KB 以內。
  2. 一個大一點的 SVG sprite 包含全部的圖標,作為外部靜態資源,大小 50KB 以內。

在大一點的項目中,我們可以將圖標分組打包成多個 SVG sprite ,服務於網站的某一部分或者某一特定功能。

在 Webpack 中使用 SVG 圖標

譯者註:本部分內容與原文無關,是譯者為展示在 Webpack 中使用 SVG icon 的示例。

在日常開發中,我不知道各位前端朋友們有沒有碰到一個問題,就是使用 font-awesome 或類似的icon-font 包,無法滿足設計稿中的需求,比如說來了一個中國地圖形狀的 icon,你會怎麼辦?

如果專門為項目維護一份 icon-font 的話,可能需要設計師每次幫你做一份字體文件,每次增加圖標就要去找設計師幫忙,然後再打包生成 ttf 、woff 、 woff2 、eot 一堆文件,至少對於我來說是這樣的。

此外,目前而言,Vue.js 和 React.js 的兼容性都是 IE9 +,所以如果不用管IE 9以下的兼容性,果斷用 SVG sprite 來做圖標啊。

示例將分別介紹 Vue.js 和 React.js 中的用法,工具包括:

1. 使用了svg-sprite-loader 製作 SVG sprite。

2. 使用了 svgo-loader 來去除不必要屬性(如Sketch導出的 SVG 文件),以簡化 SVG 圖標源碼。

demo地址

圖標統一放在 assets/icons 文件夾目錄下:

assets/n icons/n cross.svgn play.svgn heart1.svgn ...n icon-set.jsn

在 icon-set.js 中 export 所有圖標:

import Check from ./icons/check.svgnimport Cross from ./icons/cross.svgnimport Heart1 from ./icons/heart1.svgn...nnexport {n Check,n Cross,n Heart1,n ...n}n

webpack 配置中增加 `svgo-loader` 和 `svg-sprite-loader`:

var svgoConfig = require(./svgo-config.json)nnmodule.exports = {n preLoaders: [n {n test: /.svg$/,n loader: svgo? + JSON.stringify(svgoConfig)n }n ],n loaders: [n ...n {n test: /.svg$/,n loader: svg-sprite,n include: /assets/icons/n },n ...n ]n}n

`svgo-config.json` 內容如下:

{n "plugins": [n { "cleanupAttrs": true },n { "cleanupEnableBackground": true },n { "cleanupIDs": true },n { "cleanupListOfValues": true },n { "cleanupNumericValues": true },n { "collapseGroups": true },n { "convertColors": true },n { "convertPathData": true },n { "convertShapeToPath": true },n { "convertStyleToAttrs": true },n { "convertTransform": true },n { "mergePaths": true },n { "moveElemsAttrsToGroup": true },n { "moveGroupAttrsToElems": true },n { "removeComments": true },n { "removeDesc": true },n { "removeDimensions": true },n { "removeDoctype": true },n { "removeEditorsNSData": true },n { "removeEmptyAttrs": true },n { "removeEmptyContainers": true },n { "removeEmptyText": true },n { "removeHiddenElems": true },n { "removeMetadata": true },n { "removeNonInheritableGroupAttrs": true },n { "removeRasterImages": true },n { "removeTitle": true },n { "removeUnknownsAndDefaults": true },n { "removeUselessDefs": true },n { "removeUnusedNS": true },n { "removeUselessStrokeAndFill": true },n { "removeXMLProcInst": true },n { "sortAttrs": true }n ]n}n

這段配置是根據 SVGOMG 里的配置文件 複製出來的,根據這段配置文件配合 `svgo-loader` 可以取代手工去做圖標 SVG 源文件的處理(svgo-loader禁止移除 `fill` 屬性 ;-D),當然你也可以自己去定義配置以簡化 SVG 源文件。

備註: 原本 SVGOMG 裡面有一項配置 `transformsWithOnePath: true` ,由於這項配置會導致已經處理的 「乾淨」 的 SVG 圖標報錯,所以移除了該選項。此外為保留 viewBox,還移除了 `removeViewBox: true` 選項。

此時的SVG文件通過 svg-sprite-loader 處理生成 SVG sprite,並插在 <body> 里為首個元素:

<body>n <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0;visibility:hidden">n <defs>n <symbol viewBox="0 0 20 20" id="check">n <path d="M10 1c-4.962 0-9 4.038-9 9 0 4.963 4.038 9 9 9 4.963 0 9-4.037 9-9 0-4.962-4.037-9-9-9zm0 16.615c-4.2 0-7.615-3.416-7.615-7.615C2.385 5.8 5.8 2.385 10 2.385c4.2 0 7.615 3.416 7.615 7.615 0 4.2-3.416 7.615-7.615 7.615z" fill="currentColor"></path>n <path d="M13.664 6.74l-5.05 5.05-2.278-2.28c-.27-.27-.71-.27-.98 0s-.27.71 0 .98l2.77 2.77c.135.134.312.202.49.202.177 0 .354-.068.49-.203l5.537-5.54c.27-.27.27-.708 0-.98-.27-.27-.708-.27-.98 0z"></path>n </symbol>n <symbol viewBox="0 0 20 20" id="cross">n <path d="M19 4.23L15.75 1 10 6.83 4.12 1 1 4.23l5.88 5.83L1 15.9 4.13 19 10 13.17 15.75 19 19 15.9l-5.88-5.84"></path>n </symbol>n <symbol viewBox="0 0 20 20" id="heart1">n <path d="M18.98 5.7c-.24-2.36-2.24-4.2-4.66-4.2-1.95 0-3.6 1.18-4.32 2.87-.7-1.7-2.37-2.87-4.32-2.87-2.42 0-4.42 1.84-4.66 4.2L1 6.18c0 5.7 6.98 8.38 9 12.17 2.02-3.8 9-6.48 9-12.17 0-.16 0-.32-.02-.48z"></path>n </symbol>n ...n </defs>n </svg>n ...n</body>n

而 `import Check from ./icons/check.svg ` 中 Check 就是對應的 <symbol> 的 id。

Vue 中 SVG icon 用法示例:

<template>n ...n <svg class="Icon" aria-hidden="true">n <use :xlink:href="iconSet.Check"></use>n </svg>n ...n</template>nn<script>nimport * as iconSet from ../assets/icon-setnnexport default {n data () {n return {n ...n iconSet: iconSetn ...n }n }n}n</script>n

React 中 SVG icon 用法示例:

import React from react;nimport * as iconSet from ../assets/icon-setnnexport default class App extends React.Component {nn render() {n return (n <div>n <svg className="Icon" aria-hidden="true">n <use xlinkHref={iconSet.Check}></use>n </svg>n ...n </div>n );n }n} n

第四步:用CSS給圖標增加樣式

我們已經花了大量時間在講 SVG 圖標和 SVG sprite的製作,如何將圖標放到網頁中,接下來將介紹如何通過 CSS 給圖標增加樣式。

增加 class 名

我們可以在 CSS 通過元素選擇器選中所有的 <svg> 標籤,但如果 SVG 有圖標以外的用途的話,就會出問題,此外FireFox 瀏覽器還存在相關的 bug (下文有相關解釋),所以最好不要這麼做。

而我建議給每個圖標增加兩個 class 名,一個通用型的 class 如 Icon,一個獨有的 class 如 Icon--arrow。

<svg class="Icon Icon--arrow" aria-hidden="true">n <use xlink:href="/path/to/icons.svg#arrow"></use>n</svg>n

我們推薦使用 SUIT CSS 命名規則(你可以選擇喜歡的命名風格),類似 class="icon-arrow" 這種,這樣就可以使用類似 svg[class*="icon-"] 的CSS選擇器選中圖標。

圖標的默認樣式

推薦的默認樣式如下:

.Icon {n /* 通過設置 font-size 來改變圖標大小 */n width: 1em; height: 1em;n /* 圖標和文字相鄰時,垂直對齊 */n vertical-align: -0.15em;n /* 通過設置 color 來改變 SVG 的顏色/fill */n fill: currentColor;n /* path 和 stroke 溢出 viewBox 部分在 IE 下會顯示n normalize.css 中也包含這行 */n overflow: hidden;n}n

上下兩行圖標都用了默認樣式,差別在於父元素的字體和顏色。

當需要定製某個圖標的樣式時,可以參考下面這段代碼:

.MyComponent-button .Icon {n /* 設置寬高 */n font-size: 40px;n /* 設置顏色 */n color: purple;n /* 可能需要重置垂直對齊 */n vertical-align: top;n}n

圖標的顏色與父元素的文本顏色相同,如果圖標沒有繼承父元素的文本顏色(currentColor),去看看圖標源碼中是否存在 fill 屬性。

SVG 繼承的樣式

SVG許多樣式屬性都是繼承來的,比如在最外層的 <svg> 標籤,通過CSS設置了 fill 屬性,內層的 <path>、 <circle> 等元素都會繼承該屬性,我們還可以在 <svg> 標籤設置其他CSS屬性,比如 stroke:

.Icon--goldstar {n fill: gold;n stroke: coral;n stroke-width: 5%;n stroke-linejoin: round;n}n

默認樣式和定製樣式的星形圖標

大多情況下不需要修改太多,只要設置 fill 屬性里改變圖標的顏色,有時可能會增加或調整下stroke 來加個邊框什麼的。

雙色圖標

當一個圖標包含兩個 <path> 時就可以設置兩種不同的 fill 值,即顯示兩種顏色。

<symbol id="check" viewBox="0 0 20 20">n <!-- 繼承 CSS 中設置的 fill 值 -->n <path d="…" />n <!-- 繼承 CSS 中設置的 color 值-->n <path fill="currentColor" d="…" />n</symbol>n

.Icon--twoColors {n fill: rebeccapurple;n color: mediumturquoise;n}n

雙色圖標

留點空間給 stroke

還記得前面提到的在圖標四周留白嗎?在用 stroke 時就顯得尤其重要了。

.Icon--strokespace {n fill: none;n stroke: currentColor;n stroke-width: 5%;n}n

在 SVG 中,stroke 在 path 兩側,如果 path 到了 viewport 的邊界,stroke 就會有一半被截斷。

這個例子里,第一個圖標四周並未留白,第二個四周有 0.5px 的留白(viewport 為 15px)

設置 stroke-width 為百分比值

如何設置 stroke 的尺寸是個難題,下面這個例子是兩個 stroke-width 為 1px 的圖標:

stroke-width 的值是跟圖標的尺寸有關,在上圖中:

  1. 第一個圖標的 viewBox 的寬高為20px,所以1px 的 stroke 是圖標尺寸的 1/20,粗細適中。
  2. 第二個圖標的 viewBox 的寬高為500px,所以1px 的 stroke 是圖標尺寸的 1/500,顯得很細。

如果所有圖標的 viewBox 值相同的話,倒不會有什麼問題。一旦它們的寬高差別很大時,使用像素單位或者無量綱量單位(stroke-width:1)就會出問題了。怎麼解決這個問題?

推薦使用百分比,同樣的例子,這回設置 stroke-width:5%:

對於正方形圖標,設置 stroke-width: N% 看起來完美解決問題(注意:在太寬或太高的圖標上可能會不太一樣)。

並非所有的 SVG 都是圖標

有些 SVG 並非圖標,就不用放到 SVG sprite 中,比如說:

  • 不需要修改樣式的 SVG 圖形,直接用在 <img> 標籤里就好了。
  • 需要增加動畫的 SVG 圖形,考慮將整個 <svg> 標籤作為內聯元素放入頁面中,這樣就可以選擇特定的部分或 <path> 增加樣式和動畫了。

注意:如果一個 SVG 圖形大小超過100 x100 ,或者內部有很多元素,就不要把它當做圖標來處理了。

第五步:進階技巧

看完前面幾個部分,你已經掌握 SVG 圖標的很多技巧了,接下來是一些的拓展內容。

不要用無樣式的巨型圖標

當樣式文件由於網路問題載入失敗時,網頁就失去了樣式,如果網頁內容結構化良好,頁面內容仍然可讀,但是圖標就會顯示成一個龐然大物了。

最新的瀏覽器默認將 SVG 元素顯示成 300x150px,其他瀏覽器可能會把 width 設置成100%

建議把樣式直接寫到 <head> 標籤裡面。

<style>.Icon{width:1em;height:1em}</style>n

預載入外部的 SVG sprite

在第三部分 將圖標放入網頁 中,我們提過外部 SVG sprite 可以延遲載入,因為瀏覽器預載入模塊不會識別處理 <use xlink:href="/path/to/icons.svg#something"></use> 這種形式。

那我們可以做點什麼:

  • 標準且前衛的方法:在 <head> 里加一個 <link rel="preload" href="/path/to/icons.svg" as="image"> (有關預載入的細節:支持最新 Chrome,其他瀏覽器即將支持)。
  • 保守的方法:在 <body> 里最前面的位置加上 <img stylex="display:none" alt="" src="/path/to/icons.svg"> 。

我沒有實際測試這些方案,通常來說把內聯和外部 SVG sprite 並用,就已經有足夠的性能表現,已不太需要再去關心預載入,但是偶爾探索下相關知識也不失為一件好事。

選中獨立的 path

我們已經學習了定製 symbol 中所有路徑的 fill、stroke ,為 path 增加多種顏色。但是可以直接選中特定的 path (使用 class 選擇器)繼而修改樣式嗎?

答案是:可以,也不可以。

  1. 如果使用了外部 SVG sprite,無法選中 <symbol> 里的任何 <path> 和其他元素。
  2. 如果使用了內聯 SVG sprite,可以選中 <path> 並修改樣式,但是所有地方都應用這些樣式。

所以,即使是內聯 SVG sprite,可以這麼寫:

#my-symbol .style1 {n /* Styles for one group of paths */n}n#my-symbol .style2 {n /* Styles for another */n}n

不可以這麼寫:

.MyComponent-button .Icon .style1 {n /* For 1 group of paths for this icon in this context */n}n.MyComponent-button .Icon .style2 {n /* For another group */n}n

譯者註:`.MyComponent-button .Icon .style1` 無法選中 class 名為 style1 的 path。

除非在火狐瀏覽器離,你可以輕鬆選中 <symbol> 中的實例,但這是屬於火狐瀏覽器的私有特性,意味著其他瀏覽器存在著兼容性問題,所以希望火狐瀏覽器能修復這個問題(或者說是 bug)。當新標準來臨時,也許可以通過 Shadow DOM 來選中,但標準本身也在不斷變化,所以這一點無法確定(/deep/ 連結符已被棄用)。

通過 CSS 變數繪製兩種以上顏色

到目前為止,我們學習了通過 CSS 繪製單色和雙色 SVG 圖標,那有沒有可能繪製三種、四種甚至更多顏色呢?我們可以通過 CSS 變數 (又稱 CSS custom properties)實現,這需要在 SVG 上寫不少東西。

<symbol id="iconic-aperture" viewBox="0 0 128 128">n <path fill="var(--icon-color1)" d="…" />n <path fill="var(--icon-color2)" d="…" />n <path fill="var(--icon-color3)" d="…" />n <path fill="var(--icon-color4)" d="…" />n <path fill="var(--icon-color5)" d="…" />n <path fill="var(--icon-color6)" d="…" />n</symbol>n

下面這個 demo 中,是從 Iconic 里「偷」來了一個圖標,嘗試模仿下 Iconic 中圖標多色效果。

一個 symbol 中使用了 6個不同的 CSS 變數(在支持 CSS 變數的 Firefox 、Chrome 或者 Safari 9.1+ 中打開)

上面的例子中只有一個圖標,第一個圖標沒有聲明變數,所以 fallback 成了currentColor,後面兩個圖標每個都聲明過一套顏色變數,記得在支持 CSS 變數 的瀏覽器中打開下實際效果。

stroke-width 的百分比值究竟是怎麼計算出來的?

當我們把 stroke-width 設置為 N% 時,這個百分比值是根據什麼來定的?是根據圖標的寬度或者高度嗎?根據 官方文檔,其實是和對角線有關,1% 的值為:對角線長度除以根號2(接近1.4)後取 1%。

這意味著對於正方形圖標,1% 就等於寬度或高度的 1%,對於較寬或較高的圖標的話結果不太一樣。

第二個圖標的寬高比為2:1,設置了 stroke-width:5% 後,輪廓的寬度約為寬度的 7.91%,高度的 3.95%。

總體來說,建議把 stroke-width 的值設為百分比。如果你在使用方形的話,那麼1%約為圖寬度的百分之一。

不能使用 漸變/gradient

有沒有可能使用 gradient 設置 fill 值呢?事實是不能,CSS的 linear-gradient() 產生的是一個圖片值,而 fill 屬性是不接受圖片值的。

SVG 的編碼及使用 gradient 有其特定語法,但跟咱們討論的主題(SVG 圖標)關係不是很大,可以嘗試下,但是這得花功夫,而且至少得硬編碼進入一些參數,有興趣的話可以嘗試下。

第六步:部分瀏覽器存在的bug

Safari:避免給 <svg> 設置 width / height 屬性

為了避免出現頁面未載入樣式時,部分圖標顯示巨大的問題,我們給 <svg> 設置 width 、height 屬性。

<svg width="20" height="20">n <use xlink:href="…"></use>n</svg>n

接著我們在 iOS 的 Safari 測試下,有一半 SVG 圖標掛了?什麼鬼?!

實際上,Safari/WebKit 不支持先給 SVG 設置寬高屬性,再通過 CSS 去改變尺寸的,特別是想把圖標變小時,圖標的容器會變小,但圖標的內容並不生效。

我們的解決方案是移除 SVG 上的 width 、 height 屬性,只通過 CSS 控制圖標尺寸。最新的 Safari 已經修復了這個問題(Safari 9.1 桌面版和 iOS 9.3)。

Safari:避免在 <svg> 標籤上設置 padding

如果你想要設置背景色、邊框、內邊距等,你應該往圖標的父元素上增加樣式,而不是圖標自身的<svg> 標籤,雖然看起來最新的瀏覽器上都沒有問題,但是老版本 WebKit 瀏覽器上渲染存在問題,因此建議在圖標外麵包一層,比如 <span>、 <button>、 <a>等。

樣式直接加到 svg 標籤上和加到父元素上。多數瀏覽器的渲染效果相同,但老版本 WebKit 瀏覽器渲染會有點偏差。Firefox:避免使用 svg 作為元素選擇器

為什麼呢?當我們使用 <use> 標籤時,瀏覽器會創建 Shadow DOM 去複製 <symbol> 中的內容,看下來就像這樣:

<svg class="Icon Icon--something" aria-hidden="true">n <use xlink:href="#something">n <svg viewBox="0 0 20 20">n <path d="…" />n </svg>n </use>n</svg>n

之前的部分提到過,Firefox 瀏覽器目前支持選中 <use> 標籤創建的 Shadow DOM 中的內容,所以如果寫了這樣的 CSS:

svg {n fill: red;n}n.Icon--something {n fill: green;n}n

在 Firefox 瀏覽器中就會變成類似這樣:

<svg class="Icon Icon--something" aria-hidden="true" fill="green;">n <use xlink:href="#something">n <svg viewBox="0 0 20 20" fill="red;">n <path d="…" />n </svg>n </use>n</svg>n

圖標在其他瀏覽器中是綠色的,但是在 Firefox 瀏覽器中會是紅色,因為內部的 <svg> 標籤按照 CSS 第一行中的 fill: red 去渲染。

還有一種寫法可以避免:

:not(use) > svg { }n


推薦閱讀:

外刊君談中國第三屆CSS大會
d3.js: 在曲線路徑上添加文本標記的正確方式
如何製作 svg 格式的 圖標字體?
為什麼svg格式在前端設計中未能普及?
SVG 與 HTML5 的 canvas 各有什麼優點,哪個更有前途?

TAG:CSS | SVG | 图标 |