[譯] 使用 SVG 符號和 CSS 變數實現多彩圖示

PTHFLY發表於2018-02-14

使用 SVG 符號和 CSS 變數實現多彩圖示

[譯] 使用 SVG 符號和 CSS 變數實現多彩圖示

使用圖片和 CSS 精靈製作 web 圖示的日子一去不復返了。隨著 web 字型的爆發,圖示字型已經成為在你的 web 專案中顯示圖示的第一解決方案。

字型是向量,所以你無須擔心解析度的問題。他們和文字一樣因為擁有 CSS 屬性,那就意味著,你完全可以應用 sizecolorstyle 。你可以新增轉換、特效和裝飾,比如旋轉、下劃線或者陰影。

[譯] 使用 SVG 符號和 CSS 變數實現多彩圖示

怪不得類似 Font Awesome 這類專案僅僅在 npm 至今已經被下載了超過 1500 萬次

可是圖示字型並不完美, 這就是為什麼越來越多的人使用行內 SVG 。CSS Tricks 寫了圖示字型劣於原生 SVG 元素的地方:銳利度、定位或者是因為跨域載入、特定瀏覽器錯誤和廣告遮蔽器等原因導致的失敗。現在你可以規避絕大多數這些問題了,總體上使用圖示字型是一個安全的選擇。

然而,還是有一件事情對於圖示字型來說是絕對不可能的:多色支援。只有 SVG 可以做到。

摘要 :這篇博文深入闡述怎麼做和為什麼。如果你想理解整個思維過程,推薦閱讀。否則你可以直接在 CodePen 看最終程式碼。

設定 SVG 標誌圖示

行內 SVG 的問題是,它會非常冗長。你肯定不想每次使用同一個圖示的時候,還需要複製/貼上所有座標。這將會非常重複,很難閱讀,更難維護。

通過 SVG 符號圖示,你只需擁有一個 SVG 元素,然後在每個需要的地方引用就好了。

先新增行內 SVG ,隱藏它之後,再用 <symbol> 包裹它,用 id 對其進行識別。

<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path d="..." />
  </symbol>
</svg>
複製程式碼

整個 SVG 標記被一次性包裹並且在 HTML 中被隱藏。

然後,所有你要做的是用一個 <use> 標籤將圖示例項化。

<svg>
  <use xlink:href="#my-first-icon" />
</svg>
複製程式碼

這將會顯示一個初始 SVG 圖示的副本。

[譯] 使用 SVG 符號和 CSS 變數實現多彩圖示

**就是這樣了!**看起來很棒,是吧?

你可能注意到了這個有趣的 xlink:href 屬性:這是你的例項與初始 SVG 之間的連結。

需要提到的是 xlink:href 是一個棄用的 SVG 屬性。儘管大多數瀏覽器仍然支援,你應該用 **href** 替代。現在的問題是,一些瀏覽器比如 Safari 不支援使用 href 進行 SVG 資源引用,因此你仍然需要提供 xlink:href 選項。

安全起見,兩個都用吧。

新增一些顏色

不像是字型, color 對於 SVG 圖示沒有任何作用:你必須使用 fill 屬性來定義一個顏色。這意味著他們將不會像圖示字型一樣繼承父文字顏色,但是你仍然可以在 CSS 中定義它們的樣式。

// HTML
<svg class="icon">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon {
  width: 100px;
  height: 100px;
  fill: red;
}
複製程式碼

在這裡,你可以使用不同的填充顏色建立同一個圖示的不同例項。

// HTML
<svg class="icon icon-red">
  <use xlink:href="#my-first-icon" />
</svg>
<svg class="icon icon-blue">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon {
  width: 100px;
  height: 100px;
}
.icon-red {
  fill: red;
}
.icon-blue {
  fill: blue;
}
複製程式碼

這樣就可以生效了,但是不完全符合我們的預期。目前為止,我們所有做的事情可以使用一個普通的圖示字型來實現。我們想要的是在圖示的位置可以有不同的顏色。我們想要向每個路徑上填充不同顏色,而不需要改變其他例項,我們想要能夠在必要的時候重寫它。

首先,你可能會受到依賴於特性的誘惑。

// HTML
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path class="path1" d="..." />
    <path class="path2" d="..." />
    <path class="path3" d="..." />
  </symbol>
</svg>
<svg class="icon icon-colors">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon-colors .path1 {
  fill: red;
}
.icon-colors .path2 {
  fill: green;
}
.icon-colors .path3 {
  fill: blue;
}
複製程式碼

不起作用。

我們嘗試設定 .path1.path2.path3 的樣式,彷彿他們被巢狀在 .icon-colors 裡,但是嚴格來說,並非如此<use> 標籤不是一個會被你的 SVG 定義替代的佔位符。這是一個引用將它所指向內容複製為 shadow DOM ?。

**那接下來我們該怎麼辦?**當子項不在 DOM 中時,我們如何才能用一個區域性的方式影響子項?

CSS 變數拯救世界

在 CSS 中,一些屬性從父元素繼承給子元素。如果你將一個文字顏色分配給 body ,這一頁中所有文字將會繼承那個顏色直到被重寫。父元素沒有意識到子元素,但是可繼承的樣式仍然繼續傳播。

在我們之前的例子裡,我們繼承了填充屬性。回頭看,你會看到我們宣告填充顏色的類被附加在了例項上,而不是定義上。這就是我們能夠為同一定義的每個不同實體賦予不同顏色的原因。

現在有個問題:我們想傳遞不同顏色給原始 SVG 的不同路徑,但是隻能從一個 fill 屬性裡繼承。

這就需要 CSS 變數了。

就像任何其它屬性一樣, CSS 變數在規則集裡被宣告。你可以用任意命名,分配任何有效的 CSS 值。然後,你為它自己或者其它子屬性,像一個值一樣宣告它,並且這將被繼承

.parent {
  --custom-property: red;
  color: var(--custom-property);
}
複製程式碼

所有 .parent 的子項都有紅色文字。

.parent {
  --custom-property: red;
}
.child {
  color: var(--custom-property);
}
複製程式碼

所有巢狀在 .parent 標籤裡的 .child 都有紅色文字。

現在,讓我們把這個概念應用到 SVG 符號裡去。我們將在 SVG 定義的每個部分使用 fill 屬性,並且設定成不同的 CSS 變數。然後,我們將給它們分配不同的顏色。

// HTML
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path fill="var(--color-1)" d="..." />
    <path fill="var(--color-2)" d="..." />
    <path fill="var(--color-3)" d="..." />
  </symbol>
</svg>
<svg class="icon icon-colors">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon-colors {
  --color-1: #c13127;
  --color-2: #ef5b49;
  --color-3: #cacaea;
}
複製程式碼

然後… 生效了 ?!

[譯] 使用 SVG 符號和 CSS 變數實現多彩圖示

現在開始,為了用不同的顏色方案建立例項,我們所需要做的是建立一個新類。

// HTML
<svg class="icon icon-colors-alt">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon-colors-alt {
  --color-1: brown;
  --color-2: yellow;
  --color-3: pink;
}
複製程式碼

如果你仍然想有單色圖示,你不必在每個 CSS 變數中重複同樣的顏色。相反,你可以宣告一個單一 fill 規則:因為如果 CSS 變數沒有被定義,它將會回到你的 fill 宣告。

.icon-monochrome {
  fill: grey;
}
複製程式碼

你的 fill 宣告將會生效,因為初始 SVG 的 fill 屬性被未設定的 CSS 變數值定義。

怎樣命名我的 CSS 變數?

當提到在 CSS 中命名,通常有兩條途徑:描述的或者語義的。描述的意思是告訴一個顏色是什麼:如果你儲存了 #ff0000 你可以叫它 --red 。語義的意思是告訴顏色它將會被如何應用:如果你使用 #ff0000 來給一個咖啡杯把手賦予顏色,你可以叫它 --cup-handle-color

描述的命名也許是你的本能。看起來更乾脆,因為#ff0000 除了咖啡杯把手還有更多地方可以被使用。一個 --red CSS 變數可被複用於其他需要變成紅色的圖示路徑。畢竟,這是實用主義在 CSS 中的工作方式。並且是一個良好的系統

問題是,在我們的案例裡,我們不能把零散的類應用於我們想設定樣式的標籤。實用主義原則不能應用,因為我們對於每個圖示有單獨的引用,我們不得不通過類的變化來設定樣式。

使用語義類命名,例如 --cup-handle-color ,對於這個情況更有用。當你想改變圖示一部分的顏色時,你立即知道這是什麼以及需要重寫什麼。無論你分配什麼顏色,類命名將會一直關聯。

預設還是不要預設,這是個問題

將你的圖示的多色版本設定成預設狀態是很有誘惑力的選擇。這樣,你無需設定額外樣式,只需要在必要的時候可以新增你自己的類。

有兩個方法可以實現::rootvar() default

:root

:root 選擇器中你可以定義所有你的 CSS 變數。這將會把它們統一放在一個位置,允許你『分享』相似的顏色。 :root 擁有最低的優先度,因此可以很容易地被重寫。

:root {
  --color-1: red;
  --color-2: green;
  --color-3: blue;
  --color-4: var(--color-1);
}
.icon-colors-alt {
  --color-1: brown;
  --color-2: yellow;
  --color-3: pink;
  --color-4: orange;
}
複製程式碼

然而,這個方法有一個主要缺點。首先,將顏色定義與各自的圖示分離可能會有些讓人疑惑。當你決定重寫他們,你必須在類與 :root 選擇器之間來回操作。但是更重要的是,它不允許你去關聯你的 CSS 變數,因此讓你不能複用同一個名字。

大多數時候,當一個圖示只用一種顏色,我用 --fill-color 名稱。簡單,易懂,對於所有僅需要一種顏色的圖示非常有意義。如果我必須在 :root 宣告中宣告所有變數,我就不會有幾個 --fill-color。我將會被迫定義 --fill-color-1--fill-color-2 或者使用類似 --star-fill-color--cup-fill-color 的名稱空間。

var() 預設

你可以用 var() 功能來把一個 CSS 變數分配給一個屬性,並且它的第二個引數可以設定為某個預設值。

<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path fill="var(--color-1, red)" d="..." />
    <path fill="var(--color-2, blue)" d="..." />
    <path fill="var(--color-3, green)" d="..." />
  </symbol>
</svg>
複製程式碼

在你定義完成 --color-1--color-2--color-3 之前,圖示將會使用你為每個 <path> 設定的預設值。這解決了當我們使用 :root 時的全域性關聯問題,但是請小心:你現在有一個預設值,並且它將會生效。結果是,你再也不能使用單一的 fill 宣告來定義單色圖示了。你將不得不一個接一個地給每個使用於這個圖示的 CSS 變數分配顏色。

設定預設值會很有用,但是這是一個折中方案。我建議你不要形成習慣,只在對給定專案有幫助的時候做這件事情。

How browser-friendly is all that?

CSS 變數與大多數現代瀏覽器相容,但是就像你想的那樣, Internet Explorer 完全不相容。因為微軟要支援 Edge 終止了 IE11 開發, IE 以後也沒有機會趕上時代了。

現在,僅僅是因為一個功能不被某個瀏覽器(而你必須適配)相容,這不意味著你必須全盤放棄它。在這種情況下,考慮下優雅降級:給現代瀏覽器提供多彩圖示,給落後瀏覽器提供備份的填充顏色。

你想要做的是設定一個僅在 CSS 變數不被支援時觸發的宣告。這可以通過設定備份顏色的 fill 屬性實現:如果 CSS 變數不被支援,它甚至不會被納入考慮。如果它們不能被支援,你的 fill 宣告將會生效。

如果你使用 Sass 的話,這個可以被抽象為一個 @mixin

@mixin icon-colors($fallback: black) {
  fill: $fallback;
  @content;
}
複製程式碼

現在,你可以任意定義顏色方案而無需考慮瀏覽器相容問題了。

.cup {
  @include icon-colors() {
    --cup-color: red;
    --smoke-color: grey;
  };
}
.cup-alt {
  @include icon-colors(green) {
    --cup-color: green;
    --smoke-color: grey;
  };
}
複製程式碼

在 mixin 中通過 @content 傳遞 CSS 變數也是一個可選項。如果你在外面做這件事,被編譯的 CSS 將會變得一樣。但是它有助於被打包在一起:你可以在你編輯器中摺疊片段然後用眼睛分辨在一起的宣告。

在不同的瀏覽器中檢視這個 pen 。在最新版本的 Firefox , Chrome 和 Safari 中,最後兩隻杯子各自擁有紅色杯身灰色煙氣和藍色杯身灰色煙氣。在 IE 和 版本號小於 15 的 Edge 中,第三個杯子的杯身與煙氣全部都是紅色,第四個則全部是藍色! ✨

如果你想了解更多關於 SVG 符號圖示(或者一般的 SVG ),我強烈建議你閱讀 Sara Soueidan 寫的一切東西。如果你有任何關於 CSS 符號圖示的問題,不要猶豫,儘管在 Twitter 上聯絡我。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章