SVG 的使用

一倍速的夏味發表於2018-07-29

SVG 的使用

圖片來源:pixiv 38190692
SVG 圖示是向量圖,可以很方便的轉換顏色,修改文字,畫質無損進行縮放,而且檔案非常小,相比同樣的 png 圖片,大小僅為一成
SVG 通過可配置顏色和配置文字的方式,有效減少了圖示檔案的個數。從而使設計師和前端工程師工作量都減少,還能提升了載入速度

更新於 2018.7.29
首發於夏味的部落格: xiaweiss.com

介紹

說到效能優化,常見的一種方式就是壓縮圖片,通常圖片最多壓縮到原檔案的 50%。而且如果一個圖示有 7 種顏色呢?那麼設計就得給出 7 種顏色的圖片,前端程式碼也得引用不同的 7 個路徑。。。想想都覺得複雜。

SVG 圖完美地解決了這一痛點,大小僅為原檔案 10% 左右。還可以通過改寫程式碼來任意改變顏色,甚至支援在程式碼中動態地傳入顏色。動態地傳入顏色時,即便有 1萬種顏色,也僅僅一個檔案。

而生成 SVG 檔案也非常簡單,設計師可以使用 向量圖 繪圖軟體直接匯出 svg 格式的檔案,如 Adobe Illustrator(簡稱 AI)、sketch

當然對於 Adobe Photoshop 中繪製的圖片,手工轉換向量圖比較困難耗時,線上工具解決了這一難題

其中目前免費線上轉換工具裡,這款是最好的 https://www.vectorizer.io/

轉換後,可能會有一些冗餘的程式碼,可以使用命令列工具 svgo 進行批量壓縮。當然如果檔案數量不多,直接使用線上工具 https://jakearchibald.github.io/svgomg/ 即可。

這些個 SVG 壓縮工具只是靜態工具,不會被上傳到網路上去,不需要擔心被盜圖,有興趣的同學可以研究下原始碼,以及那個web 壓縮工具的原始碼

SVG 圖示示例

首先 svg 在瀏覽器裡和手機上,與圖片一樣的,可以正常顯示出來 下面這個就是一個完整的 SVG 圖,包括文字

xiaweiss.com/images/2018…

下面將它改寫為為 400px 寬度的 SVG 圖,仍然可以看到很清晰

xiaweiss.com/images/2018…

接下來,使用微信螢幕截圖,看看同樣 400px 寬度的 png 圖,並且使用智圖 壓縮圖片

SVG 的使用

接下來看看這三個檔案的大小

SVG 的使用

可以看出 SVG 圖可以任意改變尺寸,不損失清晰度 相比 png 圖體積小很多的情況下,仍然比 png 圖更清晰

接下來使用程式碼編輯器開啟 SVG 檔案,可以看到如下程式碼

也許看到這一堆程式碼要頭暈了。別擔心,實際應用時,並不需要自己手寫 SVG 程式碼,只是改改就足夠了 仔細看很類似前端常用的 html 標籤

<?xml version="1.0" encoding="UTF-8"?>
<svg width="60px" height="24px" viewBox="0 0 60 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 51 (57462) - http://www.bohemiancoding.com/sketch -->
    <title>按鈕/關注-紅色底</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="按鈕/關注-紅色底" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <rect id="Rectangle-3" fill="#FF4C6A" x="0" y="0" width="60" height="24" rx="12"></rect>
        <g id="icon/關注加號" transform="translate(3.000000, 0.000000)">
            <g id="Group" stroke-width="1" transform="translate(8.000000, 8.000000)" fill="#FFFFFF">
                <path d="M3,3 L3,1 C3,0.44771525 3.44771525,1.01453063e-16 4,0 C4.55228475,-1.01453063e-16 5,0.44771525 5,1 L5,3 L7,3 C7.55228475,3 8,3.44771525 8,4 C8,4.55228475 7.55228475,5 7,5 L5,5 L5,7 C5,7.55228475 4.55228475,8 4,8 C3.44771525,8 3,7.55228475 3,7 L3,5 L1,5 C0.44771525,5 6.76353751e-17,4.55228475 0,4 C-6.76353751e-17,3.44771525 0.44771525,3 1,3 L3,3 Z" id="Combined-Shape"></path>
            </g>
            <rect id="Rectangle-10" fill="#D8D8D8" opacity="0" x="0" y="0" width="24" height="24"></rect>
        </g>
        <text id="哈哈" font-family="PingFangSC-Regular, PingFang SC" font-size="12" font-weight="normal" line-spacing="18" fill="#FFFFFF">
            <tspan x="24" y="16">哈哈</tspan>
        </text>
    </g>
</svg>
複製程式碼

SVG 程式碼說明

<svg width="60px" height="24px" viewBox="0 0 60 24" ... > ... </svg>
複製程式碼

svg 標籤來控制寬高,width,height 是實際顯示的寬高,可以修改為你想要顯示的大小 而 viewBox 裡的大小,則是原始大小,可以理解為畫紙的大小位置,其中左上角座標為 0 0,右下角座標為 60 24(方向分別為 x, y)

<g id="Group" stroke-width="1" transform="translate(8.000000, 8.000000)" fill="#FFFFFF"> ... </g>
複製程式碼

g 標籤表示分組,也就是繪圖軟體中的圖層。類似程式碼中的繼承,它的屬性,如果子標籤裡沒有規定,就會使用它的屬性設定 例如下面這兩段程式碼是同樣的效果

<g fill="#FF4C6A">
  <rect x="0" y="0" width="60" height="24" rx="12"></rect>
</g>
複製程式碼
<g >
  <rect fill="#FF4C6A" x="0" y="0" width="60" height="24" rx="12"></rect>
</g>
複製程式碼

path 表示線條, reat 表示方形,text 表示文字 程式碼 d="M3,3 L3,1 ...' 是繪製線條的程式碼,也稱作路徑(path)

fill 表示填充色,類似於 css 裡的背景色(background-color)

stroke 表示描邊,類似於 css 的邊框顏色(border-color)

所以修改色值時,只需要修改這兩個顏色即可 色值與 css 相同,可以使用透明色 transparent,以及 rgba(0,0,0,0.5) 也可以新增屬性 opacity='0.5' 來控制透明度,值為 0 ~ 1

至於修改文字,找到對應的文字,直接替換即可

動態渲染 SVG

由於最近正在做 react-native, SVG 的配置難度較大,就用它來示例一下

這裡使用 react-native-svg 庫來渲染 SVG 圖片

注意設計師匯出 SVG 圖示前,請清除掉蒙層(mask)、顏色疊加和濾鏡(filter)、陰影(shadow),目前 react-native 是不支援的

首先使用 msvgc 庫來一鍵把 SVG 檔案轉換為 React 元件

nodejs 環境裡安裝 msvgc,原始檔放置到 App/Svg/目錄下,配置指令碼執行即可 匯出的元件位於 ./App/Components/Svg/svg 目錄

"scripts": {
  "svg": "msvgc --react-native -f ./App/Svg/ -o ./App/Components/Svg  && standard --fix './App/Components/Svg/svg/*.js'"
}
複製程式碼

再把修正屬性的語法,全部改為駝峰寫法,例如 fill-rule 改為 fillRule 修正後將檔案移動到 App/Components/Svg(其他目錄也昆蟲,因為每次新轉換時,會覆蓋App/Components/Svg/svg/ 目錄)

接下來,進一步修改程式碼,就可以通過元件的 props 值裡動態傳參了

import React from 'react'
import Svg, { G, Rect, Path, Text } from 'react-native-svg'
import { path } from 'ramda' // 根據鍵名取值,取不到或錯誤時,返回 undefined
// path([鍵名],被取值的物件)

const ButtonFollow = props => {
  const Color = {
    red: '#ff4c6a',
    grey: '#6c6c6c'
  }
  const fillColor = path([props.color], Color) || props.color || Color.red
  return (
    <Svg
      width={props.width || 60}
      height={props.height || 24}
      viewBox='0 0 60 24'>
      <G stroke='none' strokeWidth='1' fill='none' fillRule='evenodd'>
        <Rect fill={fillColor} x='0' y='0' width='60' height='24' rx='12' />
        <G strokeWidth='1' transform='translate(8, 8)' fill='#FFFFFF'>
          <Path d='M3,3 L3,1 C3,0.44771525 3.44771525,1.01453063e-16 4,0 C4.55228475,-1.01453063e-16 5,0.44771525 5,1 L5,3 L7,3 C7.55228475,3 8,3.44771525 8,4 C8,4.55228475 7.55228475,5 7,5 L5,5 L5,7 C5,7.55228475 4.55228475,8 4,8 C3.44771525,8 3,7.55228475 3,7 L3,5 L1,5 C0.44771525,5 6.76353751e-17,4.55228475 0,4 C-6.76353751e-17,3.44771525 0.44771525,3 1,3 L3,3 Z' />
        </G>
        <Text fontSize='12' lineSpacing='18' fill='#FFFFFF' x='24' y='16'>
          {props.text || '關注'}
        </Text>
      </G>
    </Svg>
  )
}

export default ButtonFollow
複製程式碼

呼叫

<ButtonFollow width={300} height={120} color='red' />
<ButtonFollow /> 取預設值 64,24,預設色 red
<ButtonFollow width={300} height={120} color='#ccc' /> 任意顏色
複製程式碼

react-art

(基於 react-naive 0.55.4 介紹)

facebook 自家也出品了一個庫 react-art,並且有 react-native-art,二者大體上使用的同一套 API 它支援的元素標籤交少,例如沒有方形。但更簡潔,不需要引入額外庫 (react-native-svg 壓縮後大約 200KB)

相關語法可以看 react-native-art-繪圖入門 瞭解一下 原始碼位於 node_modules/react-native/Libraries/ART/ReactNativeART.js 缺點是還沒有找到現成的轉換外掛,需要手工轉換

首先來封裝一個方形元件,width、height 控制寬高,r 控制圓角半徑(border-radius)

import React from 'react'
import { ART } from 'react-native'

const { Shape } = ART

function extractNumber (value, defaultValue) {
  if (value == null) {
    return defaultValue
  }
  return +value
}

const RectART = props => {
  let w = extractNumber(props.width, 0)
  let h = extractNumber(props.height, 0)
  let r = extractNumber(props.r, 0)

  if (w === 0 || h === 0) return null

  if (r > 0) {
    h -= r * 2
    w -= r * 2
    return <Shape {...props}
      d={`M${r},0 h${w} a${r},${r} 0 0,1 ${r},${r} v${h} a${r},${r} 0 0,1 ${-r},${r} h${-w}  a${r},${r} 0 0,1 ${-r},${-r} v${-h} a${r},${r} 0 0,1 ${r},${-r} z`}
    />
  } else {
    return <Shape {...props} d={`h${w} v${h} h${-w} z`} />
  }
}

export default RectART
複製程式碼

接下來轉換按鈕

  1. 將 svg 換為 Surface,寬高為顯示端寬高,不指定畫布大小
  2. Surface 內層寫上一層 Group ,新增屬性 scale 用於整體縮放,縮放倍數 X 基於畫布原始大小來計算
  3. g 轉換為 Group, Group 裡的屬性只支援 fill 和 transform,其他的都寫到子標籤裡去
  4. path 轉換為 Shape,d 仍然是路徑,複製過來即可(react-art 內建了一套 svg 轉換器,原始碼位於路徑 node_modules/art/core/path.js
  5. transform 轉換為這種形式,注意 scale 要放在左邊,與 css3 相同還支援 rotate
transform = new Transform().scale(2).translate(2, 3)
複製程式碼
  1. text 轉換為 Text,直接包裹文字即可,

其中 font 指定字型格式(注意必須指定字型) (react-native 裡指定多個字型只有第一個字型生效)

font='normal 12 PingFangSC-Regular'
複製程式碼

也可以寫為(注意必須指定字型)

font={fontFamily: 'PingFangSC-Regular',fontSize: '12'}
複製程式碼

還有兩個屬性是 fontWeight、fontStyle

import React from 'react'
import { path } from 'ramda' // 根據鍵名取值,取不到或錯誤時,返回 undefined
// path([鍵名],被取值的物件)
import { ART, View } from 'react-native'
import Rect from './RectART.js'

const { Group, Shape, Surface, Transform, Text } = ART

const ButtonFollow = props => {
  const Color = {
    red: '#ff4c6a',
    grey: '#6c6c6c'
  }

  const fillColor = path([props.color], Color) || props.color || Color.red
  const X = (Math.min(props.width / 60, props.height / 24)) || 1

  return (
    <View style={props.style}>
      <Surface width={props.width || 60} height={props.height || 24} >
        <Group scale={X} >
          <Rect fill={fillColor} r='12' width='60' height='24' />
          <Shape fill='#FFFFFF' transform={new Transform().translate(8, 8)} d='M3,3 L3,1 C3,0.44771525 3.44771525,1.01453063e-16 4,0 C4.55228475,-1.01453063e-16 5,0.44771525 5,1 L5,3 L7,3 C7.55228475,3 8,3.44771525 8,4 C8,4.55228475 7.55228475,5 7,5 L5,5 L5,7 C5,7.55228475 4.55228475,8 4,8 C3.44771525,8 3,7.55228475 3,7 L3,5 L1,5 C0.44771525,5 6.76353751e-17,4.55228475 0,4 C-6.76353751e-17,3.44771525 0.44771525,3 1,3 L3,3 Z' />
          <Text font={'normal 12 PingFangSC-Regular'} fill='#FFF' transform={new Transform().translate(24, 4)}>
            {props.text || '關注'}
          </Text>
        </Group>
      </Surface>
    </View>
  )
}

export default ButtonFollow
複製程式碼
<ButtonFollow width={300} height={120} />
複製程式碼

react-native-art API

(基於 react-naive 0.55.4 介紹) 下面是我看了原始碼之後羅列的 API,供參考使用

相關語法可以看 react-native-art-繪圖入門 瞭解一下 原始碼位於 node_modules/react-native/Libraries/ART/ReactNativeART.js

Surface

Property Type Description
width string
heigh string -

Group

Property Type Description
opacity number
scale number
scaleX number
scaleY number
transform transform
visible boolean false equals opacity 0

Shape

Property Type Description
d path
fill string Color
opacity number
scale number
scaleX number
scaleY number
strokeCap string butt, square, round(default)
strokeDash
strokeJoin string miter, bevel, round
strokeWidth number 1(default)
transform transform
visible boolean false equals opacity 0

Text

Property Type Description
alignment string right, center, left(default)
fill string Color
font string normal 10 PingFangSC-Regular
font object {fontFamily,fontSize,fontWeight,fontStyle}
opacity number
path path
scale number
scale number
scaleX number
scaleX number
scaleY number
scaleY number
strokeCap string butt, square, round(default)
strokeDash
strokeJoin string miter, bevel, round
strokeWidth number 1(default)
transform transform -

Transform

Property params Description
transform xx, yx, xy, yy, x, y
translate x, y
move x, y
moveTo x, y
scale x, y
scaleTo x, y
rotate deg, x, y
rotateTo deg, x, y
resizeTo width, height this.scaleTo(width / w, height / h)
point x, y
inversePoint x, y -

上面表格含 To 的都是絕對座標,例如 move 是相對座標,moveTo 是絕對座標

其他存在但不常用的 API Path ClippingRectangle LinearGradient Pattern RadialGradient

SVG path 說明

具體可以看 w3c 官方文件 https://www.w3.org/TR/SVG2/paths.html 大寫字母都代表絕對座標 小寫字母都代表相對座標

下面這幾個寫法效果相同 M 10 20 M 15 25 v 5 M 10,20 M 15,25 v 5 M10,20 M15,25 v5 M10,20 15,25 v5 (與前一個字母相同時,字母可以省略)

字母主要有以下幾種,可以對應到上面的表格

Command Name Parameters Description
M moveto x, y 移動
L lineto x, y 畫線
H horizontal lineto x 畫水平線
V vertical lineto y 畫垂直線
A elliptical arc 橢圓
Z closepath 連線到起始點,只能在最後使用

橢圓 A 有6個引數 rx ry x-axis-rotation large-arc-flag sweep-flag x y 含義分別為

Parameters Description
rx x 軸半徑
ry y 軸半徑
x-axis-rotation 相對於 x 軸的旋轉角度,例如 30 表示 30 度
large-arc-flag 1 繪製大圓,0 繪製小圓
sweep-flag 旋轉方式: 1 順時針,0 逆時針
x 結束點的 x 座標
y 結束點的 x 座標

相關文章