SVG Sprite 使用簡介

網易考拉前端團隊發表於2019-03-02

SVG簡介

SVG即可縮放向量圖形 (Scalable Vector Graphics)的簡稱, 是一種用來描述二維向量圖形的XML標記語言. SVG圖形不依賴於解析度, 因此圖形不會因為放大而顯示出明顯的鋸齒邊緣.

icon sprite

當我們需要使用多個icon的時候, 為了節省請求和方便管理, 通常會把icon合併到一個檔案中, 在使用時再通過一定的方法從icon集合檔案中取出所需的圖形並顯示. 目前使用得最多的應該就是我們所熟悉的CSS Sprite和Icon Font.

CSS Sprite

CSS Sprite的原理是將多個icon按一定規律整理到一個圖片檔案中, 使用時利用background-imagebackground-position將圖片中特定部分顯示出來. CSS Sprite技術已經被廣泛應用了很長的一段時間, 目前有許多自動化生成Sprite圖片和CSS檔案的工具, 例如(gulp.spritesmith)[github.com/twolfson/gu…].

.icon1 {
    background-image: url(/res/icon1.png)
}
.icon1-increase {
    background-position: -10px -10px;
}複製程式碼
<i class="icon1 icon1-increase"/>複製程式碼

CSS Sprite技術成熟, 相容性好, 但是缺點也比較明顯. 如在實際需求中, 對應形狀相同但顏色不同的icon, 就需要為不同顏色的icon各儲存一份; 有時候需要對已有icon放大顯示時, 發現鋸齒嚴重, 那麼又要再儲存一份放大版的icon. 因此, Sprite檔案會隨著時間越變越大, 同時內容越來越亂, 逐漸變得難以管理.

Icon Font

Icon Font的基本原理是將Icon定義為圖片字型, 在CSS中用@font-face引入Icon Font自定義字型, 再利用font-family和字元碼顯示出指定的圖示.

@font-face {
    font-family: 'iconfont';
    src: url(/res/icon2.ttf) format('truetype');
}
.icon2 {
    font-family: 'iconfont';
}複製程式碼
<i class="icon2">&#33</i>複製程式碼

由於使用的是字型, 因此可以通過color, font-size設定icon的樣式. Icon Font擁有比CSS Sprite圖片更小的檔案體積, 維護也比圖片更方便, 但是icon font通常只能使用單一的顏色, 字型檔案生成也比CSS Sprite更復雜.

SVG Sprite

通常在使用SVG的時候, 我們是直接寫到svg標籤當中:

    <svg xmlns="http://www.w3.org/2000/svg" width="150" height="100" viewBox="0 0 3 2">
        <rect width="1" height="2" x="0" fill="#008d46" />
        <rect width="1" height="2" x="1" fill="#ffffff" />
        <rect width="1" height="2" x="2" fill="#d2232c" />
    </svg>複製程式碼

此時SVG圖形會直接在頁面當中顯示. SVG屬性中, 可以利用(symbol)[developer.mozilla.org/zh-CN/docs/…, 並利用(use)[developer.mozilla.org/zh-CN/docs/…, 從而實現SVG Sprite的功能.

SVG Sprite例項:

<svg style="height:0;width:0;display:none;" version="1.1" xmlns="http://www.w3.org/2000/svg">
    <symbol id="icon-italy" width="150" height="100" viewBox="0 0 3 2">
        <rect width="1" height="2" x="0" fill="#008d46" />
        <rect width="1" height="2" x="1" fill="#ffffff" />
        <rect width="1" height="2" x="2" fill="#d2232c" />
    </symbol>

    <symbol id="icon-france" width="150" height="100" viewBox="0 0 3 2">
        <rect width="1" height="2" x="0" fill="#002496" />
        <rect width="1" height="2" x="1" fill="#ffffff" />
        <rect width="1" height="2" x="2" fill="#ee2839" />
    </symbol>
</svg>

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

這樣就實現了SVG Sprite.

原理

通過devtool可以觀察到, use標籤是利用shadow dom實現的.

use.shadowdom
use.shadowdom

它通過xlink:href這個XML的attribute引用指定的SVG symbol, 在渲染時指定symbol標籤中的內容就會被渲染顯示在頁面當中. 這意味著, 如果無法直接對use標籤中的shadow dom進行訪問和修改. 例如像use#rect這樣的選擇器是無法生效的, 因此不能通過一般的css選擇器對use中圖形不同的部分進行控制.

CSS樣式

更多的時候, 我們通常都只需要改變圖示的大小和顏色, 在SVG Sprite當中, 實現起來並不複雜.

  1. 大小

通過改變svg容器的大小, 可以輕鬆地對圖示大小進行控制.

.icon{
    width: 120px;
    height: 80px;
}
.icon.icon-small{
    width: 60px;
    height: 40px;
}複製程式碼
<svg class="icon"><use xlink:href="#icon-italy"/></svg>
<svg class="icon icon-small"><use xlink:href="#icon-italy"/></svg>複製程式碼

svg.icon.size
svg.icon.size

  1. 顏色
  • 單色

顏色由於不能直接對use的shadow root中的圖形標籤進行選擇, 因此在為圖示定義顏色時需要利用fill: inherit這個一屬性.

svg path{
    fill: inherit;
}
.icon2-green{
    fill: #008d46;
}
.icon2-red{
    fill: #dc352f;
}複製程式碼
<svg class="icon2 icon2-green">
    <use xlink:href="#icon-increase"/>
</svg>
<svg class="icon2 icon2-red">
    <use xlink:href="#icon-increase"/>
</svg>複製程式碼

svg.icon.color
svg.icon.color

利用inherit, 還可以定義stroke-width, stroke等屬性.

  • 多色

除了對圖示整體顏色進行定義以外, 還可以根據需要對圖形不同部分進行顏色定義. 這裡使用到了css 的自定義屬性(CSS Custom Properties).

這裡需要對icon.svg進行修改, 將圖形個部分的顏色設定為fill: var(--*[, default])的形式.

<symbol id="icon-flag" width="150" height="100" viewBox="0 0 3 2">
    <rect width="1" height="2" x="0" style="fill: var(--color0, #008d46)" />
    <rect width="1" height="2" x="1" style="fill: var(--color1, #fff)"/>
    <rect width="1" height="2" x="2" style="fill: var(--color2, #d2232c)"/>
</symbol>複製程式碼

定義css樣式

.flag-belgium {
    --color0: #201b18;
    --color1: #f1ee3d;
    --color2: #dc352f;
}複製程式碼
<svg class="icon">
    <use xlink:href="#icon-flag"/>
</svg>
<svg class="icon flag-belgium">
    <use xlink:href="#icon-flag"/>
</svg>複製程式碼

svg.icon.color.parts
svg.icon.color.parts

除此以外, 還有其他一些樣式定義的形式, 更詳細的內容可以閱讀Styling SVG Content with CSS.

實際使用

引用外部svg

上文介紹的是使用內聯svg, 但其實還可以使用外部svg的.

<svg class="icon">
    <use xlink:href="/res/svg/icon.svg#icon-flag"/>
</svg>複製程式碼

然而這種方法不相容IE9~10, 但如果非要使用外部svg的形式, 可以引入一個pollyfillsvg4everybody, 當執行在不支援外部svg的情況下, 這個pollyfill會通過非同步請求載入svg委檔案並將其注入到頁面當中, 將引用方法轉換成內聯svg.

在ftl中使用

可以在svg.ftl中定義一系列macro, 用以載入.svg檔案.

<#macro svgicon path>
    <#include "${path}">
</#macro>

<#macro svgicon3 >
    <@svgicon "./icon3.svg"/>
</#macro>複製程式碼

在頁面中引用demo.ftl:

<@svgicon1/>
<svg>
    <use xlink:href="#icon-italy"/>
</svg>複製程式碼

在regular中使用

xlink:href是使用了名稱空間的XML特性, 如果是寫在.html頁面的標籤, 該特效能夠正常被瀏覽器解析並完成svg渲染. 如果該svg變數是通過DOM API建立出來的話, 則需要使用特定的方法進行處理(SVG with USE tag not rendering).

即需要利用createElementNSsetAttributeNS方法在建立的同時宣告名稱空間.

var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#icon-increase');
document.querySelector('#svgid').appendChild(use);複製程式碼

只要是需要動態建立use元素, 都需要使用上面這種方法才能使SVG Sprite中的元素例項化. 但在目前的Regular(0.4.3)中, 不會對名稱空間進行處理, 這意味著, 如果直接將<svg><use xlink:href/></svg>寫在Regular元件的模版中時, 該標籤是無法正常渲染的. 為此可以增加一個指令r-xlink:href以完成手動設定名稱空間的操作.

Regular.directive('r-xlink:href', function (elem, val) {
    if (val&& val.type === 'expression') {
        this.$watch(val, function (newVal) {
            elem.setAttributeNS('http://www.w3.org/1999/xlink', 'href', newVal);
        });
    } else {
        elem.setAttributeNS('http://www.w3.org/1999/xlink', 'href', val);
    }
});複製程式碼

那麼在元件模版中就可以像在普通.html中那樣使用SVG Sprite了. 當然, 首先得確保SVG Sprite被寫到頁面中.

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

在Regular的SVG 實踐中得到了鄭海波大神的指點, 否則得繞更大的路才能把問題解決, 在此表示感謝.

小結

對比前文提到的CSS Sprite和Icon Font, SVG有著明顯的優勢:

  • 放大縮小不會失真
  • 大小, 顏色等屬性自定義靈活
  • 體積小, 同時管理方便

雖然SVG Sprite有著高度的靈活性, 但於此同時, SVG相容性有待考究, 同時其渲染效能也不及圖片和字型那麼高, 可能在某些情況下不適用. 不過在一般的場景中, svg sprite還能夠給開發帶來很大的便利的.

參考

  1. SVG元素參考
  2. SVG with USE tag not rendering
  3. 未來必熱:SVG Sprite技術介紹
  4. Styling SVG Content with CSS
  5. Icon System with SVG Sprites