用CSS給SVG 的內容添加樣式

轉自:http://www.w3cplus.com/svg/styling-svg-use-content-css.html


一篇深入探究如何給SVG<use>元素的內容添加樣式的文章,並針對碰到的問題逐一解決。

svg use

SVG圖形的一個最常見用例是圖標系統,其中最常用的SVG sprite技術就是使用SVG<use> 元素在文檔中任意位置“實例化”圖標

使用<use>元素實例化圖標或任何其它的SVG元素或圖像,給元素添加樣式時經常會碰到一些問題。這篇文章的目的是儘可能給你介紹一些方法來解決:使用<use>引入的內容添加樣式受限的問題。

但是在開始之前,我們先快速瀏覽一下SVG的主要結構和分組元素,然後慢慢進入use的世界中,以及shadow DOM,然後重回CSS的懷抱。我們會逐步講解爲什麼給<use>內容添加樣式會比較麻煩,以及有什麼好的解決方案。

SVG結構化、分組,以及在SVG中引用(重用)元素速覽

SVG中有四個主要的元素用於在文檔中定義、結構化和引用SVG代碼。這些元素使得重用SVG元素變得容易,同時保持代碼的簡潔性和可讀性。因爲SVG的特性,這些元素和圖形編輯器中的某些命令具有相同的功能。

這四個用於SVG分組和引用的主要元素是:<g>, <defs>, <use><symbol>

<g>元素(“group”的簡寫),用於給邏輯上相聯繫的圖形元素分組。從圖形編輯器的角度,如Adobe Illustrator,<g>元素提供了類似於分組對象的功能。你也可以把它想象成圖形編輯器中圖層的概念,因爲一層也是一組元素。

當你想要應用某個樣式,並希望這個樣式能被組中的所有元素繼承,分組元素<g>非常好用,特別是當你想要給某組元素應用動畫,同時還需要保持它們彼此的空間關係的時候。

<defs>元素用來定義你之後要重用的元素。當你想要創建某一類在文檔中要多次使用的“模板”時,使用<defs>定義元素。在<defs>元件中定義的元素不會在畫布中渲染出來,除非你在文檔的某個位置調用了它們。

<defs>可以用於定義很多東西,但是最主要的使用情景之一是定義類似漸變的圖案,例如,使用這些漸變作爲其它SVG元素的描邊填充。它可以用來定義你想要在畫布上渲染的任何元素。

<symbol>元素結合了<defs><g>元素的優點,將定義模板的元素組合在一起,以便之後在文檔中的其他位置引用。和<defs>不同,<symbol>通常不用於定義圖案,但是經常用於定義例如圖標這樣的標誌,在整個文檔中都可以被引用。

<symbol>元素相比其它兩個元素有一個非常重要的優點:它接受一個viewBox屬性,可以讓它在任何視窗中自適應大小縮放渲染。

<use>元素用於引用文檔中其它位置定義的元素。你可以重用已有的元素,類似於圖形編輯器中的複製粘貼功能。它可以重用單個元素,也可以重用一組用<g><defs><symbol>定義的元素。

要使用一個元素,你需要通過一個標識對該元素進行引用——一個ID,即use中的xlink:href屬性,以及用來給該元素定位的xy屬性。你可以給use元素應用樣式,這些樣式也會級聯應用到use元素的內容中去。

<use>的內容是什麼呢?它被克隆到哪裏了?CSS級聯如何處理這些內容?

在我們回答這些問題之前,因爲我們目前只講了SVG結構化和分組元素,這裏還有幾篇值得我們繼續深入學習的文章,關於viewBox屬性和<symbol>的使用:

SVG <use>及shadow DOM

當你使用<use>引用元素時,代碼如下:

<symbol id="my-icon" viewBox="0 0 30 30">
    <!-- icon content / shapes here -->
</symbol>

<use xlink:href="#my-icon" x="100" y="300" />

渲染在屏幕上的東西是內容定義在<symbol>內的圖標,但是這不是真正渲染出的內容,而是<use>的內容,也就是<symbol>內容的一個副本或者克隆。

但是<use>元素只是一個元素,它是自閉合的。在use標籤的開閉區間內沒有任何內容,所以<symbol>的內容是克隆到哪裏了呢?

答案是:Shadow DOM。(不知道爲什麼,shadow DOM總是讓我想起蝙蝠俠(:зゝ∠)。)

什麼是shadow DOM?

shadow DOM和常規的DOM很類似,不同之處在於shadow DOM不是主文檔子樹的一員,shadow DOM中的結點屬於文檔片段,基本上等同於另一棵結點樹,不能像普通結點那樣添加腳本和樣式。這給了作者們一種方法來封裝和包裹樣式及腳本,當創建模塊化組件時。如果你使用過HTML5的video元素,或range input類型,也很好奇video控件或者範圍輸入組件是從哪裏來的,那麼你就已經接觸過shadow DOM了。

在SVG<use>元素中,引用元素的內容被複制到一個文檔片段中保存,這個文檔片段是由<use>保留着。<use>在這裏就是一個shadow Host。

所以,<use>的內容(克隆或複製那個它引用的元素的)都表示在一個shadow文檔片段中。

也就是說,它們就在那裏,但是並不可見。就像普通的DOM內容一樣,但是並不是在“高等級”的DOM中,並不能在主文檔中被CSS選擇器和JavaScript選中,它們被複制到由<use>保留的文檔片段中。

現在,如果你是一個設計師,你可能會想:“ok,我瞭解了這東西了,但是有什麼方法可以檢查子文檔,來看看它的真正的內容呢?”答案是:有的!你可以使用Chrome的開發者工具預覽shadow DOM的內容。(現在還無法在Firefox中查看shadow DOM的內容。)但是爲了完成這個,你需要先在“General”面板中勾選shadow DOM檢查的選項。也就是:打開 Chrome 的開發者工具,點擊右上角的“Settings”按鈕勾選“Show user agent shadow DOM”。

在開發者工具中勾選了shadow DOM檢查這一項之後,你可以在Elements面板中看到克隆的元素,和普通的DOM元素一樣。下面的圖片展示了<use>元素引用<symbol>元素的內容的示例。注意到有一個“#shadow-root”,而且當點開此片段的內容時——會發現它就是<symbol>內容的副本。

shadow DOM

檢查一下這些代碼,你可以看到shadow DOM和普通的DOM非常相似,除了在主文檔中用CSS和JavaScript處理時有不同的特性之外。它們之間還存在其它差異,但是這一節不可能完全在講shadow DOM,因爲這真的是一個很大的概念,所以如果你想要閱讀和了解更多關於它的內容的話,我推薦下面這幾篇文章:

圖靈社區的同學翻譯的shadow DOM系列文章

對我來說,考慮如何限制和shadow DOM的交互時,我把它當成普通DOM一樣,除了在用CSS(和JavaScript)添加樣式時需要不同地處理。但是對於SVG開發者來說就是一個問題:shadow DOM中<use>的內容如何存在,當需要給內容應用樣式或者改變樣式的時候,因爲我們希望可以爲它們添加樣式。使用<use>的目的是爲了可以創建某個元素的多個不同的“副本”,很多情境下,我們想要的是可以給差異化地給不同的副本添加樣式。例如,考慮一個有兩種樣式的logo(反轉顏色的主題)或多種顏色的icon,每一個都有自己的主題。這時,我們自然而然就會想到使用CSS來完成。

也就是說,我們前面提到的shadow DOM的內容在CSS看來不能像普通DOM一樣添加樣式。所以,我們要怎麼給它的內容添加樣式呢?我們不能像這樣指向<use>的路徑級聯:

use path#line {
    stroke: #009966;
}

因爲我們不能使用普通的CSS選擇器來獲取shadow DOM。

有一組特殊的選擇器可以讓我們打破普通DOM的界限,給它裏面的結點應用樣式,但是這些選擇器並沒有很好的瀏覽器支持,而且相比CSS中提供的一長串用來選中普通DOM元素的選擇器,它們是受限的。

此外,我們希望有一個更簡單的方式來給SVG<use>的內容添加樣式,而不需要去接觸shadow DOM的具體內容——只使用簡單的CSS和SVG。

爲了實現以及獲得更多一點控制,給<use>的內容添加樣式,我們需要從不同的角度思考,借用CSS級聯和繼承的優勢。

級聯樣式

因爲SVG元素可以使用CSS通過三種不同的方法之一進行添加樣式:外部的CSS樣式(在外部的CSS文件中),內部樣式塊(<style>元素包裹),以及內聯樣式(在元素的style屬性中)。重點在於這些級聯管理是如何將樣式應用到元素之上的。

除了CSS屬性,SVG元素還可以使用描述屬性添加樣式。描述屬性是在元素上設置CSS屬性的簡寫方式。可以認爲它們是特殊的樣式屬性。它們的目的就是給樣式級聯做貢獻,但是可能正走在一個我們不太期望的方向上。

在下面的代碼片段中,簡單地展示了一個粉色的帶黃色描邊的圓。strokestroke-widthfill都是描述屬性。

<svg viewBox="0 0 100 100">
    <circle fill="deepPink" stroke="yellow" stroke-width="5" cx="50" cy="50" r="10"></circle>
</svg>

在SVG中,所有CSS屬性的子集可以通過SVG屬性設置,反之亦然。這意味着,不是所有的CSS屬性都可以被指定給SVG元素作爲描述屬性,也不是所有SVG支持的描述屬性都可以在CSS中指定,雖然有很多都可以。

SVG規範列出了可以設置爲CSS屬性的SVG屬性。其中一些屬性可以和CSS共享(也就是已經可以作爲CSS屬性),如opacitytransform,有一些還不行,如fillstrokestroke-width

在SVG 2中,這個列表將包括xywidthheightcxcy,以及一些其它的描述屬性,目前還不能在SVG 1.1中通過CSS來設置的。新的屬性列表可以在SVG 2規範中找到。

如果你和我一樣,那麼你一定會期待描述屬性可以有相比其它樣式聲明更高的特殊性。我的意思是,畢竟,外部的樣式可以被內部的樣式塊覆蓋,內部的樣式塊又可以被style屬性設置的內聯樣式覆蓋。那麼這看起來是不是越“內層”的樣式,優先級就越高。所以如果一個屬性有了自己的特性,它是不是就更強大,因此它也就可以覆蓋所有其它的樣式聲明。儘管這對我來說是非常棒的,但是它真正的工作原理卻不是這樣的。

事實上,描述屬性算是比較低層級的“作者樣式層疊表”,可以被其它所有的樣式定義覆蓋:外部的樣式表,內部的樣式塊以及內聯樣式。描述屬性唯一超過的就是繼承樣式。就是說,描述屬性只可以覆蓋文檔中的繼承樣式,但是會被其它所有的樣式聲明覆蓋(:зゝ∠)

好滴~既然我們現在弄清楚了,我們回到<use>元素以及它的內容上吧。

我們現在知道我們不同使用CSS選擇器給<use>中的元素設置樣式。

我們知道,正如<g>元素,你應用給<use>的樣式將會被它所有的後代內容繼承(也就是shadow DOM中的內容)。

所以第一個改變<use>內元素的fill顏色的嘗試就是給<use>元素本身應用此填充顏色,並讓其繼承和級聯。

但是,這帶來了兩個問題:

  • 該填充顏色將被<use>的所有後代內容繼承,甚至包括那些你並不想給它們應用樣式的內容(如果你的<use>中還沒有任何元素,那麼這就不成問題。)
  • 如果是通過圖形編輯器導出,或者是從其它設計師手中拿到的SVG,簡單來說,就是你不能接觸到SVG源碼,那麼你可能就沒辦法給SVG元素應用描述屬性了(除非你明確指出你不希望在輸出SVG的時候發生這個事情,但這是另一個話題了),這些屬性的值將覆蓋你給<use>應用的所有樣式。現在,我假設如果你給<use>指定了樣式,而且希望這些樣式可以被它的後代繼承,那麼描述屬性可能會給你帶來不便。

即使你可以獲取SVG的代碼,你也可以擺脫描述屬性,我強烈建議不要這樣做,因爲:

  • 刪除那些用於設置某些屬性的特性(:зゝ∠),將會導致這些屬性被重置爲初始的瀏覽器默認值——也就是,一般情況下,所有都是黑色填充和描邊(比如我們現在討論的是顏色)。
  • 通過重置所有值,你可以強迫自己去給所有屬性集指定樣式,所以除非你想這麼做,否則你不要想擺脫這些描述屬性了。
  • 描述屬性設計的初衷是作爲一項降級機制,用於當你的外部樣式因爲某些原因不能應用的時候。如果CSS因爲某些東西給搞砸而不能加載的時候,你的圖標至少可以有些默認的相對漂亮的樣式可以降級。這點我強烈建議保留它們。

好了,現在我們有這些屬性了,但是我們還想針對不同的實例應用不同的樣式,比如說,不同的圖標。

需要做的就是確保我們強制描述屬性繼承了設置於<use>之上的樣式,或者找到一個方法來讓它們覆蓋這些值。爲了做到這一點,我們需要利用CSS的優勢。

我們從最簡單的實例開始,然後慢慢進入到更復雜的情景。

CSS描述屬性值的介紹

描述屬性可以被其它任何的樣式聲明覆蓋。我們可以利用這個優勢,用一個外部的樣式聲明,強制描述屬性覆蓋從use繼承的值。

通過使用CSSinherit關鍵字,這會變得非常簡單。看看下面的例子,我們繪製了一個冰淇淋的圖標,只用一條路徑完成,而且可以根據不同的情況改變填充顏色。這個圖標是Erin Agnoli在Noun項目中創建的。

<svg>
  <symbol id="ic">
    <path fill="#000" d="M81,40.933c0-4.25-3-7.811-6.996-8.673c-0.922-5.312-3.588-10.178-7.623-13.844  c-2.459-2.239-5.326-3.913-8.408-4.981c-0.797-3.676-4.066-6.437-7.979-6.437c-3.908,0-7.184,2.764-7.979,6.442  c-3.078,1.065-5.939,2.741-8.396,4.977c-4.035,3.666-6.701,8.531-7.623,13.844C22.002,33.123,19,36.682,19,40.933  c0,2.617,1.145,4.965,2.957,6.589c0.047,0.195,0.119,0.389,0.225,0.568l26.004,43.873c0.383,0.646,1.072,1.04,1.824,1.04  c0.748,0,1.439-0.395,1.824-1.04L77.82,48.089c0.105-0.179,0.178-0.373,0.225-0.568C79.855,45.897,81,43.549,81,40.933z   M49.994,11.235c2.164,0,3.928,1.762,3.928,3.93c0,2.165-1.764,3.929-3.928,3.929s-3.928-1.764-3.928-3.929  C46.066,12.997,47.83,11.235,49.994,11.235z M27.842,36.301c0.014,0,0.027,0,0.031,0c1.086,0,1.998-0.817,2.115-1.907  c0.762-7.592,5.641-13.791,12.303-16.535c1.119,3.184,4.146,5.475,7.703,5.475c3.561,0,6.588-2.293,7.707-5.48  c6.664,2.742,11.547,8.944,12.312,16.54c0.115,1.092,1.037,1.929,2.143,1.907c2.541,0.013,4.604,2.087,4.604,4.631  c0,1.684-0.914,3.148-2.266,3.958H25.508c-1.354-0.809-2.268-2.273-2.268-3.958C23.24,38.389,25.303,36.316,27.842,36.301z   M50.01,86.723L27.73,49.13h44.541L50.01,86.723z"/>
  </symbol>
</svg>

這個冰淇淋圖標的內容(也就是path)是定義在一個<symbol>元素中的,也就是說它們不會直接在SVG畫布中渲染。

然後,我們使用<use>渲染出多個圖標實例。

<svg class="icon" viewBox="0 0 100 125"> 
    <use class="ic-1" xlink:href="#ic" x="0" y="0" />
</svg>
<svg class="icon" viewBox="0 0 100 125"> 
    <use class="ic-2" xlink:href="#ic" x="0" y="0" />
</svg>

我們在CSS中設置圖標的寬度和高度。我使用了viewBox一樣的尺寸,但它們也不是一定要相同的。但是,爲了避免SVG內多餘的空白太多,保持它們的寬高比。

.icon {
    width: 100px;
    height: 125px;
}

使用上面的代碼,你可以得到下面的結果:

注意我給SVG添加了一個黑色的邊框,這樣大家纔可以看到每個圖的邊界,我們定義的第一個SVG圖標的內容並沒有渲染。這裏可以提出一點:你在symbol中定義的SVG文檔也會在頁面中渲染出來,即使它沒有包括渲染圖形。爲了避免這一點,確保你在第一個SVG中設置了display: none。如果你沒有隱藏包含圖標定義的SVG,即使你沒有明確設置任何尺寸,它也會被渲染出來——瀏覽器默認尺寸是300x150px,這是在CSS中沒有替代元素時的默認尺寸,所以你會在頁面上得到一塊白色的區域,儘管你並不想要這塊東西。

現在讓我們試試改變每個圖標實例的填充顏色:

use.ic-1 {
    fill: skyblue;
}
use.ic-2 {
    fill: #FDC646;
}

即使這樣寫了,圖標的填充顏色還是不會有任何改變,因爲繼承的顏色值被path元素的fill="#000"覆蓋了。爲了阻止這個東西,我們強制讓path繼承顏色值:

svg path {
    fill: inherit;
}

瞧!我們給<use>元素設置的顏色現在可以逐個應用於path了。查看下面的demo,可以照自己喜歡的去改變顏色值,創建更多實例:

現在這種技術已經非常好用,當你想要強制<use>的內容繼承你設置的樣式時。但是在大多數情況下,這可能不是你想要的。還有很多其它添加樣式的場景,所以我們接下來會介紹一些其它的方法。

使用CSS的all屬性給<use>的內容添加樣式

前段時間我使用一個引用自use的圖標,我想讓它裏面的其中一個元素可以繼承所有我給<use>設置的樣式,像fillstrokestroke-widthopacity甚至transform。基本上,我希望可以控制所有這些CSS屬性,同時保留標籤中的描述屬性作爲降級。

如果你發現你也處在這樣一個場景中,你可能會發現這用CSS做起來非常耗時間:

path#myPath {
    fill: inherit;
    stroke: inherit;
    stroke-width: inherit;
    transform: inherit;
    /* ... */
}

看看上面的代碼片段,你可以看到都是同一個模式,我們應該可以把所有這些屬性結合起來,放到一個屬性中,並把所有這些屬性的值設置爲inherit

幸運的是,這就是CSS的all屬性發光發熱的時候了。我之前寫過關於使用all屬性來給SVG的<use>內容添加樣式的參考條目,但是因爲我們現在的上下文環境,我們需要再看看。

使用all屬性,我們可以這樣寫:

path#myPath {
    all: inherit;
}

這在所有支持all屬性的瀏覽器中都工作得非常好(詳細信息請查看屬性參考條目),然而還有幾個重點要記住:這條聲明會真正地給元素的所有屬性都設置從父元素繼承值,包括那些你可能不想要的屬性。所以除非你想要在CSS中給元素的所有屬性都設置樣式,否則你就不要使用它——這是一種極端的措施,當你想要暴露你的元素,然後在CSS中對它的樣式屬性進行完全的控制的時候才使用,這種情況比較少見。如果你使用這條聲明,不在CSS中指定所有屬性的值,它們就會直接往上然後級聯,知道它們找到可以繼承的值,大多數情況下就是瀏覽器的默認樣式,從默認用戶代理樣式表加載而來。

注意這隻會影響到那些可以在CSS中設置的屬性,不包括那些SVG獨有的屬性。所以如果一個屬性可以作爲CSS屬性設置,它就會被設置爲inherit,否則就不會。

能夠強制描述屬性去從<use>繼承樣式是強大的,但是如果你的圖標是由多個元素組成的呢,你肯定不想要讓所有的這些元素都從use繼承同一個fill顏色吧?那如果你想要給不同的use級聯應用多個填充顏色怎麼辦呢?給use設置一個樣式已經不足夠了,我們需要一些其它的東西來幫助我們從正確的元素級聯正確的顏色。

使用CSS的currentColor變量來給<use>內容添加樣式

使用CSS的currentColor變量,並結合上面的技術,我們可以給一個元素指定兩種不同的顏色,而不僅是一種。Fabrice Weinberg在他的CodePen blog寫了一些關於這種技術的文章

有關於currentColor更多信息,可以參閱早前的一篇譯文《使用CSS的currentColor變量擴展顏色級聯》。

這種技術的內幕其實是在<use>上同時使用fillcolor屬性,然後利用currentColor的變量特性,讓這些顏色級聯到<use>的內容上。我們先看一個代碼實例,看看這是怎麼搞的先。

假設我們要給這個小小的Codrops的logo添加兩種顏色的樣式——一個用於前面的水滴,一個用於後面的——logo的每一個實例都是採用兩種顏色。

currentColor

首先,我們從上面的代碼截圖開始:用symbol包裹我們的圖標定義,然後使用三個<use>創建三個logo實例。

<svg style="display: none;">
<symbol id="codrops" viewBox="0 0 23 30">
    <path class="back" fill="#aaa" d="M22.63,18.261c-0.398-3.044-2.608-6.61-4.072-9.359c-1.74-3.271-3.492-5.994-5.089-8.62l0,0   c-1.599,2.623-3.75,6.117-5.487,9.385c0.391,0.718,0.495,1.011,0.889,1.816c0.143,0.294,0.535,1.111,0.696,1.43   c0.062-0.114,0.582-1.052,0.643-1.162c0.278-0.506,0.54-0.981,0.791-1.451c0.823-1.547,1.649-2.971,2.469-4.33   c0.817,1.359,1.646,2.783,2.468,4.33c0.249,0.47,0.513,0.946,0.791,1.453c1.203,2.187,2.698,4.906,2.96,6.895   c0.292,2.237-0.259,4.312-1.556,5.839c-1.171,1.376-2.824,2.179-4.663,2.263c-1.841-0.084-3.493-0.887-4.665-2.263   c-0.16-0.192-0.311-0.391-0.448-0.599c-0.543,0.221-1.127,0.346-1.735,0.365c-0.56-0.019-1.095-0.127-1.599-0.313   c1.448,3.406,4.667,5.66,8.447,5.78C19.086,29.537,23.469,24.645,22.63,18.261z"/>
    <path class="front" fill="#ddd" d="M6.177,11.659c0.212,0.367,0.424,0.747,0.635,1.136c0.164,0.303,0.333,0.606,0.512,0.927   c0.683,1.225,1.618,2.898,1.755,3.937c0.144,1.073-0.111,2.056-0.716,2.769c-0.543,0.641-1.315,1.014-2.186,1.067   c-0.87-0.054-1.643-0.43-2.186-1.067c-0.604-0.713-0.858-1.695-0.715-2.771c0.137-1.036,1.072-2.712,1.755-3.936   c0.18-0.32,0.349-0.623,0.513-0.927C5.752,12.404,5.964,12.026,6.177,11.659 M6.177,5.966L6.177,5.966   c-1.02,1.649-2.138,3.363-3.247,5.419c-0.932,1.728-2.344,3.967-2.598,5.88c-0.535,4.014,2.261,7.09,5.846,7.203   c3.583-0.113,6.379-3.189,5.845-7.203c-0.255-1.912-1.666-4.152-2.598-5.88C8.314,9.329,7.196,7.617,6.177,5.966L6.177,5.966z"/>
</symbol>
</svg>
<svg height="90px" width="69px">
  <use xlink:href="#codrops" class="codrops-1"/>
</svg>
<svg height="90px" width="69px">
  <use xlink:href="#codrops" class="codrops-2"/>
</svg>
<svg height="90px" width="69px">
  <use xlink:href="#codrops" class="codrops-3"/>
</svg>

如果我們想要給<use>元素的每個實例設置fill顏色,該顏色將會兩條路徑都繼承,最後它們就會有相同的顏色——這不是我們想要的。

所以我們不僅要指定fill顏色,還要讓它按照默認方法級聯,然後使用currentColor變量來確保icon前面的小水滴獲取和背景不同的顏色值:該值通過color屬性指定。

首先,我們需要在我們想要應用該顏色值的地方插入currentColor;進入定義圖標內容的標籤,在<symbol>中。代碼如下:

<svg style="display: none;">
    <symbol id="codrops" viewBox="0 0 23 30">
        <path class="back" fill="#aaa" d="..."/>
        <path class="front" fill="currentColor" d="..."/>
    </symbol>
</svg>

下一步我們需要從另一個水滴中刪除fill描述屬性,並讓它從use繼承fill顏色,而不是使用inherit技術。

如果我們要使用inherit關鍵字來強制描述屬性從use繼承值,兩條路徑都會繼承相同的值,這樣currentColor就不會再產生任何效果。所以,在這種技術中,我們需要刪除我們想要在CSS中設置的屬性,只保留那個我們想要使用currentColor設置的值。

<svg style="display: none;">
    <symbol id="codrops" viewBox="0 0 23 30">
        <path class="back" d="..."/>
        <path class="front" fill="currentColor" d="..."/>
    </symbol>
</svg>

現在,在<use>上使用fillcolor屬性,給水滴添加樣式:

.codrops-1 {
  fill: #4BC0A5;
  color: #A4DFD1;
}
.codrops-2 {
  fill: #0099CC;
  color: #7FCBE5;
}
.codrops-3 {
  fill: #5F5EC0;
  color: #AEAFDD;
}

每個<use>元素有自己的fillcolor值。對單個水滴來說,fill顏色級聯並被沒有fill屬性的第一條路徑(後面的水滴)繼承,color屬性的值被作爲第二條路徑(前面的水滴)的fill屬性的值。

所以當前顏色值被引用到<use>元素裏邊,使用currentColor。漂亮整潔,對嗎?

這是上面代碼的demo:

這種雙色變量技術對於簡單的雙色標誌非常好用。在Fabrice的文章中,他創建了三個不同的sass logo,通過改變文本的顏色和背景顏色。

currentColor關鍵字目前只在CSS變量中支持。但是,如果我們有更多的變量,是不是就可以分配和釋放更多的值到<use>內容中呢?對的!Amelia Bellamy-Royds在CodePen blog的文章中介紹了這個概念,大概一年多之前。我們來看看它是如何工作的。

將來:使用CSS自定義屬性給<use>內容添加樣式,即CSS變量

使用CSS自定義屬性(即CSS變量),你可以給<use>的內容添加樣式,而不需要強制瀏覽器覆蓋任何描述屬性的值。

MDN中定義的,CSS變量可以是作者、或用戶,定義的,web頁面中包含整個文檔中指定的值的實體。它們被設置了使用自定義屬性,並通過一個指定的功能符號var()訪問。和CSS預處理器(如sass)的變量非常相似,但是更靈活,可以做預處理器變量不能做的事情。(CSS變量很快將被添加到Codrops CSS參考條目中,敬請期待。)

變量,即CSS變量或預處理器變量,都可以有很多使用示例,但是主題(顏色)是最常見的用例之一。在這一節中我們將講解在給SVG添加樣式時如何使用它。

我們先從一張symbol中定義並通過use實例化的圖像開始,並且只爲這張圖像應用這種技術;只要你想,這個示例中給<use>內容應用樣式的概念,也可以被應用到很多<use>元素中。

現在,假設我們有如下這個可愛時髦的機器人插畫,Freepik設計~

機器人插畫

機器人的代碼包括了這些組成顏色。

<svg style="display: none">
    <symbol id="robot" viewBox="0 0 340 536">
        <path d="..." fill="#fff" />
        <path d="..." fill="#D1312C" />
        <path d="..." fill="#1E8F90" />
        <path d="..." fill="#1E8F90" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" />
        <path d="..." fill="#1E8F90"  />
        <path d="..." fill="#6A4933" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" />
        <path d="..." fill="#F2B42B" />
        <path d="..." fill="#fff" />

       <!-- rest of the shapes -->
    </symbol>
</svg>

現在,我們不打算使用CSS變量作爲每條路徑的fill屬性的值;我們將使用CSSfill屬性作爲其填充顏色值,並保留原位置的fill屬性。這個屬性將作爲降級使用,在不支持CSS變量的瀏覽器中,這樣這個圖像在那些不支持變量的瀏覽器中,仍然能保留初始樣式。

添加了變量之後,上面的代碼變成如下:

<svg style="display: none">
    <symbol id="robot" viewBox="0 0 340 536">
        <path d="..." fill="#fff" />
        <path d="..." fill="#D1312C" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color)" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color)" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color)" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" />
        <path d="..." fill="#F2B42B" style="fill: var(--secondary-color)" />
        <path d="..." fill="#fff" />

        <!-- rest of the shapes -->
    </symbol>
</svg>

因爲內聯style標籤會覆蓋描述屬性,支持CSS變量的瀏覽器會使用這些變量作爲圖形的填充顏色。不支持CSS變量的瀏覽器將使用fill屬性值。

下一步,我們需要在CSS中定義變量的值。首先,插畫需要使用use實例化:

<svg width="340" height="536">
    <use xlink:href="#robot" id="robot-1" />
</svg>

然後,變量將會定義在use上,這樣它們會被級聯到內容上。你給變量選的顏色將會構成你的插畫內容的顏色主題。所以,對於上面的機器人,構成圖形有三種主要的顏色,我把它們命名爲primary、secondary和tertiary。

#robot-1 {
  --primary-color: #0099CC;
  --secondary-color: #FFDF34;
  --tertiary-color: #333;
}

有這些變量,你仍然可以使用fillcolor屬性,但是你可能不需要或者根本不想要。所以,有了以上通過變量定義的顏色,我們的機器人如下:

機器人插畫

你可以根據自己需要創建很多圖像的副本,每個都定義一組不同的顏色,然後使用不同的顏色主題。當你想要給同一個logo根據上下文,以不同的方式爲其添加樣式時,或者任何其它相似的用例。

現在,我們提到那些不支持CSS變量的瀏覽器會降級到描述屬性的初始樣式,而不支持變量的瀏覽器將會使用fill屬性來覆蓋屬性。ok!但如果瀏覽器不支持CSS變量,而且作者還沒有爲它們提供指定的變量值,或者他們提供的值是無效的,會發生什麼呢?

對於我們這裏的時髦可愛的機器人,我們定義了三個變量,只有圖像中的一小部分元素沒有獲取任何變量,因爲它們使用默認的顏色,任何顏色的主題都是非常漂亮的。所以,如果你在支持CSS變量的瀏覽器(目前只有Firefox)中展示上面的代碼,然後從CSS中刪除變量聲明,你將會得到:

機器人插畫

如果如果沒有設置變量值或者變量值無效,瀏覽器會使用默認顏色,通常是黑色的填充和描邊。

避免的方法是爲瀏覽器支持提供另一種降級顏色。事實上,CSS變量的語法就可以做到這一點:在var()函數內部不要只提供變量值一條聲明,你可以用逗號分隔提供兩條聲明:變量名和一個降級顏色值——在這裏就是指我們在描述屬性中使用的值。

所以,回到上面的機器人代碼,如下:

<svg style="display: none">
    <symbol id="robot" viewBox="0 0 340 536">
        <path d="..." fill="#fff" />
        <path d="..." fill="#D1312C" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" />
        <path d="..." fill="#F2B42B" style="fill: var(--secondary-color, #F2B42B)" />
        <path d="..." fill="#fff" />

         <!-- rest of the shapes -->
    </symbol>
</svg>

就是它了。對任何無法加載或沒有定義值的變量,瀏覽器將會降級使用標籤中定義的初始顏色。非常棒!

使用這種技術,你現在可以在頁面上任何你想要的地方,使用<use>引用機器人。在CSS中爲每一個新實例定義一組變量值,這樣每個實例都會有一組不同的顏色主題。

你可以看看上面的demo,創建很多機器人副本,然後指定不同的變量值,只要確保你使用的是Firefox瀏覽器,因爲在寫這篇文章的當前(2015.7.16)只有它支持CSS變量:

如果你是在Firefox中查看demo,你會看到我們用CSS變量定義的藍色+黃色版本的機器人。記得在Chrome中查看其降級機制(綠色版本),然後在Firefox中嘗試刪除變量聲明,看看降級如何。

總結

呼~寫了這麼長。

利用CSS樣式的優勢,給<use>的內容添加樣式——雖然在shadow DOM中可以不要這麼複雜。用CSS變量(僅一個currentColor或自定義屬性)我們可以進入到shadow DOM中,定製我們的圖形,並提供非常好的降級機制。

就我個人而言,我非常喜歡搗鼓CSS變量+SVG的組合。我喜歡他們組合在一起時非常地強大,特別是考慮到它們強大的降級機制。它們目前只在Firefox中支持,正如我們提到的,但是如果你想要看它們得到更多的支持,你可以在MS Edge User Voice forums上爲它們投票。

在將來我們可能會有其它的方法來給use內容添加樣式,因爲已經有很多這方面的討論,關於使用CSS變量作爲SVG的參數;所以這篇文章,雖然很長,而且可能沒有涵蓋到所有這方面的內容。如果你有什麼其它的想法,請在評論中留言。

處理重用SVG元素的內容已經成爲最熱門的SVG話題之一,很多人都覺得很難處理,因爲克隆的代碼在對應的位置如何自然展示。涉及的和相關的話題非常多,但是這是其它文章要討論的了。

希望你喜歡這篇文章,並能對你有所幫助,感謝閱讀!

本文根據@SaraSoueidan的《Styling SVG <use> Content with CSS》所譯,整個譯文帶有我們自己的理解與思想,如果譯得不好或有不對之處還請同行朋友指點。如需轉載此譯文,需註明英文出處:http://tympanus.net/codrops/2015/07/16/styling-svg-use-content-css


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章