博客 / 詳情

返回

SVG基礎及其動畫應用淺析

本文作者:錢鴻昌(閃火)

一、我們為什麼使用svg

  1. 和高清png來做個對比


    繼續對比


    同樣高清的質地,矢量圖不畏懼放大,體積小。這裏要説明一點就是,因為 SVG 中保存的是點、線、面的信息,與分辨率和圖形大小無關,只是跟圖像的複雜程度有關,所以圖像文件所佔的存儲空間通常會比 png 小。

  2. 優化 SEO 和無障礙的利器,因為 SVG 圖像是使用XML(可擴展標記語言【英語:Extensible Markup Language,簡稱:XML】標記指計算機所能理解的信息符號,通過此種標記,計算機之間可以處理包含各種信息的文章等)來標記構建的,瀏覽器通過繪製每個點和線來打印它們,而不是用預定義的像素填充某些空間。這確保 SVG 圖像可以適應不同的屏幕大小和分辨率。
  3. 由於是在 XML 中定義的,SVG 圖像比 JPG 或 PNG 圖像更靈活,而且我們可以使用 CSS 和 JavaScript 與它們進行交互。SVG 圖像設置可以包含 CSS 和 JavaScript。在 react、vue 這種數據驅動視圖的框架下,對於 SVG 操作就更加如魚得水了。(下文會跟大家分享一些小的 SVG 動畫在我們項目中的實踐)
  4. 在運用層面上,SVG 提供了一些圖像編輯效果,比如屏蔽和剪裁、應用過濾器等等。並且SVG 只是文本,因此可以使用 GZip 對其進行有效壓縮。

二、瞭解 SVG 常用元素及其使用

大多數教程網上都能找到,這裏寫一些我覺得值得提及的點

2-1. svg 標籤

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg  width="300" height="300" viewBox="0, 0, 100, 200"  xmlns="http://www.w3.org/2000/svg" version="1.1">
    <circle cx="100" cy="50" r="49" stroke="black"
    stroke-width="2" fill="red" />
</svg>

這就是我們從設計手裏拿到的 SVG 源文件,我們掰開揉碎了説。首先我們把 SVG 內部代碼全部去掉不看,於是成了這樣

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="300" height="300" viewBox="0, 0, 100, 200"  xmlns="http://www.w3.org/2000/svg" version="1.1">
</svg>

這是99% SVG 都會表現出來的形式和及一些屬性,其中包含 width、height 這兩個視口屬性,viewBox 視圖屬性,xmlns 屬性。我們一行一行看

第一行:包含了 XML 聲明,XML 聲明其實和 HTML 文檔的 DTD 聲明是類似的。類比 HTML5 的聲明方式
<!DOCTYPE html>

SVG 的文檔聲明方式(劃重點:一般如果 SVG 運用在 HTML 裏,我們可以不寫這樣的文檔聲明,但如果是單獨的 SVG 文件,那就需要寫了,否則瀏覽器可能會不認識)

<?xml version="1.0" standalone="no"?>

我們看到的 standalone 屬性是在表明該 xml 聲明是否是獨立的,如果不是即 standalone="no",那後面會引入外部的 dtd ,如第二行第三行所示。 version 屬性用於指明 SVG 文檔遵循規範的版本。 它只允許在根元素<svg> 上使用。 它純粹是一個説明,對渲染或處理沒有任何影響。雖然它接受任何數字,但是隻有1.0 和 1.1.這兩個有效的選擇。

第四行:這是 SVG 內容的開始
<svg width="300" height="300" viewBox="0, 0, 100, 200"  xmlns="http://www.w3.org/2000/svg" version="1.1">
</svg>
  • xmlns 屬性是 SVG 的 XML 聲明空間,這一部分類似於 HTML 中的 xmlns="http://www.w3.org/1999/xhtml"

    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  • width & height 屬性,可以理解成畫布的大小。沒錯是畫布的大小。舉個例子:

    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width=100 height="100">
      <circle cx="50" cy="50" r="49" stroke="black"
      stroke-width="1" fill="red" />
    </svg>

當前這個 SVG 的畫布大小是 100 * 100 的畫布,我們畫上一個半徑為 49 再加 1 個單位的描邊的圓。剛好撐滿沒毛病。所見即所得。那我們試一下改變 width 和 height。發現

<svg  
    style="background:#007fff"
    xmlns="http://www.w3.org/2000/svg"
    version="1.1"
    width="300"
    height="300">
    <circle cx="50" cy="50" r="49" stroke="black"
    stroke-width="1" fill="red" />
</svg>

我們可以看到藍色區域就是我們定的 width 和 height ,圖形部分依然是那個圓沒有變化。這樣我們就理解了 width 和 height 的作用。

  • viewBox 屬性,接下來配合 viewBox 這個屬性我們再來修改下代碼

    <svg 
      style="background:#007fff"
      xmlns="http://www.w3.org/2000/svg"
      version="1.1"
      <!-- viewBox定義-->
      viewBox="0, 0, 100, 100"
      width="300"
      height="300" >
      <circle cx="50" cy="50" r="49" stroke="black"
      stroke-width="1" fill="red" />
    </svg>

我們可以看到藍色區域大小不變,而我們的圓卻變得很大,大到撐滿了整個畫布。沒錯,你的想法是對的,所謂 viewBox 這個屬性可以理解為我們微信聊天時的截圖操作。viewBox 屬性的四個參數,前兩個表示截圖起點,後面兩個表示截圖終點,均是以左上角定點為原點。最後把截圖再拉伸放在 SVG 畫布上,就成了我們上面看到的 SVG 了。下面我們再修改一次 viewBox 成 0, 0, 50, 50 幫助理解

<svg 
    style="background:#007fff"
    xmlns="http://www.w3.org/2000/svg"
    version="1.1"
    viewBox="0, 0, 50, 50" 
    width="300"
    height="300" >
    <circle cx="50" cy="50" r="49" stroke="black"
    stroke-width="1" fill="red" />
</svg>

所以整個邏輯大概是這樣的

2-2.path 標籤

在 SVG 裏,你可以把 path 看成是最基本的繪製元素,正因為它是最基本的,萬變不離其宗,他能演化出各種複雜的繪製效果。所以 path 是最基本也是最複雜的繪製元素。

path 的基礎屬性和其代表的意義

我們知道一個 path 標籤,最重要的屬性是 d 屬性,它是一組指令和參數的集合。在 d 屬性的值裏,我們能看到一堆非常複雜的指令字符串。

<path d="
    M73.8616812,68.8664775
    L74.5015359,74.5939423
    L68.1746283,71.7969507
    C66.2299599,72.4159872 64.1377269,72.7711218 61.9444643,72.7711218
    C51.9719158,72.7711218 43.8883163,65.7823167 43.8883163,57.1611168
    C43.8883163,48.5399169 51.9719158,41.5511118 61.9444643,41.5511118
    C71.9164005,41.5511118 80,48.5399169 80,57.1611168
    C80,61.8286883 77.6181486,66.006419 73.8616812,68.8664775" id="Fill-1" fill="#FFFFFF"></path>

其實完全不用覺得噁心,這裏繼續掰開揉碎了説

  • d 屬性裏的那些指令
指令 參數 含義
M x y 將畫筆移動到點(x,y)
L x y 畫筆從當前的點繪製線段到點(x,y)
H x 畫筆從當前的點繪製水平線段到點(x,y0),y0 表示繪製前畫筆所在 y 軸座標,也就是 y 軸不變
V y 畫筆從當前的點繪製豎直線段到點(x0,y),x0 表示繪製前畫筆所在 x 軸座標,也就是 x 軸不變
A rx ry x-axis-rotation large-arc-flag sweep-flag x y 畫筆從當前的點繪製一段圓弧到點(x,y)
C x1 y1, x2 y2, x y 畫筆從當前的點繪製一段三次貝塞爾曲線到點(x,y)
S x2 y2, x y 特殊版本的三次貝塞爾曲線(省略第一個控制點)
Q x1 y1, x y 繪製二次貝塞爾曲線到點(x,y)
T x y 特殊版本的二次貝塞爾曲線(省略控制點)
Z 無參數 繪製閉合圖形,如果 d 屬性不指定Z命令,則繪製線段,而不是封閉圖形

以上是 path 路徑中的全部指令,其中加粗部分為常用基礎指令,相對來説比較好理解。每個指令都有對應的小寫指令。例如M 10,10 有對應的 m 10,10 。大寫代表絕對位置,所謂絕對位置即對 SVG 畫布左上角原點的絕對。小寫代表相對位置,所謂相對位置是以當前畫筆所在位置進行定位

  • A(arc)畫弧指令

    A rx ry x-axis-rotation large-arc-flag sweep-flag x y
    
    <svg width="100%" height="100%">
      <path d="M0 0 A 45 45, 0, 0, 0, 45 45 L 45 0 Z" fill="green"/>
    </svg>

畫了張圖,幫助理解

按照圖中的步驟,我們可以畫出兩個圓都滿足,於是再看到其中A指令有三個0我們沒有解釋,回顧下 A 指令,並結合這張圖我們可以更好的理解

A rx ry x-axis-rotation large-arc-flag sweep-flag x y

  • 貝塞爾曲線
    關於貝塞爾曲線,張老師這篇文章已經説得非常清楚了,説得非常易懂深度理解 SVG 路徑,推薦給希望更多瞭解 svg 路徑的同學

2-3.基本圖形

基本圖形這塊相對比較好理解,我們直接一張表總結下,不做過多贅述

圖形 標籤 模板 含義
矩形 < rect > <rect x="60" y="10" rx="10" ry="10" width="30" height="30"/> x:起點橫座標,y:起點縱座標,rx:倒角x軸方向半徑,ry:倒角x軸方向半徑,width:寬度,height:高度
圓形 < circle > <circle cx="100" cy="100" r="50" fill="#fff"></circle> cx:圓心橫座標,cy:圓心縱座標,r:半徑
橢圓 < ellipse > <ellipse cx="75" cy="75" rx="20" ry="5"/> cx:橢圓心橫座標,cy:橢圓心縱座標,rx:橢圓x軸方向半徑,ry:橢圓y軸方向半徑
直線 < line > <line x1="10" x2="50" y1="110" y2="150"/> x1,y1:起點,x2,y2:終點
折線 < polyline > <polyline points="60 110, 65 120, 70 115, 75 130, 80 125, 85 140, 90 135, 95 150, 100 145"/> 每兩個點以空格配對為一個座標點,逗號隔開形成座標集合。連成折線。
多邊形 < polygon > <polygon points="50 160, 55 180, 70 180, 60 190, 65 205, 50 195, 35 205, 40 190, 30 180, 45 180"/> 類似折線,不同的是,最後一個點會自動閉合第一個點,形成閉環。

2-4.symbol標籤

symbol 標籤是我們直播團隊 icon 管理平台實現的核心技術點,它的作用説白話點就是相當於是一個元件,放在我們的工具箱裏,就像下面這樣:

<svg class="svg-sprite">[工具箱]
    <symbol id="icon-wave_add" viewBox="0 0 76 76"><path d="M38 0a4 4 0 014 4v30h30a4 4 0 110 8H41.999L42 72a4 4 0 11-8 0l-.001-30H4a4 4 0 110-8h30V4a4 4 0 014-4z" fill="currentColor" fill-rule="evenodd" opacity="none"></path></symbol>
    <symbol id="icon-time" viewBox="0 0 10 10"><path d="M5 0a5 5 0 110 10A5 5 0 015 0zm0 1.5a.5.5 0 00-.5.5v3.02l.008.088a.5.5 0 00.238.343L7.02 6.794l.082.039a.5.5 0 00.603-.215l.039-.082a.5.5 0 00-.216-.603L5.5 4.735V2l-.008-.09A.5.5 0 005 1.5z" fill="rgba(153,153,153,1)" fill-rule="evenodd" class=" "></path></symbol>
    <symbol id="icon-wave_delete" viewBox="0 0 40 40"><g fill="none" fill-rule="evenodd"><circle fill="#000" opacity="0.2" cx="20" cy="20" r="20"></circle><path stroke="#FFF" stroke-width="4" stroke-linecap="round" d="M13 13l14 14M27 13L13 27"></path></g></symbol>
</svg>

放一份就可以無限引用。當它在工具箱裏時,我們是看不到它的(頁面不會渲染它),只有我們使用了<use>標籤對其進行實例引用時,我們才可以在頁面上看到它:

<use xlink:href="#icon-time"></use>

我們使用<symbol> + <use>的組合,來實現svg雪碧圖,是不是覺得很easy。

有的同學會有疑問,symbol 標籤和 g 標籤,放在 defs 裏彷彿都是在定義一個可複用的模塊,那麼兩者之間有什麼區別呢?在我的理解裏,symbol 相對於g 標籤最大的不同在於 symbol 可以給可複用代碼塊增加視圖屬性和視口屬性。方便在服用的時候直接調整到合適的運用(打印)尺寸。

三、svg 動畫及其運用

3-1.svg 動畫概要

其實關於 SVG 動畫,要説的有很多,本文我們主要説一下 SVG 動畫的一些基本屬性和運用技巧
1、SMIL 驅動
2、JavaScript 驅動
3、CSS 驅動
技術 描述 備註
SMIL 很強大且純粹的標籤化動畫 雖然 Chrome 45以後棄用了SMIL,但是依然支持,各大瀏覽器的支持度都挺好的
CSS CSS 還只能實現簡單的動畫 offset-path 的兼容性很差。css 動畫不適合做交互性很強的動畫
JavaScript 複雜動畫就要用到 JS 了,包括世面上的一些 SVG 動畫庫,也都是 JS 去實現的 -

SVG 是基於 XML 的矢量圖形描述語言,可以近似理解成 HTML,所以能和 JS 以及 CSS 進行交互。 特別是 CSS,我們可以使用 CSS3 來對 SVG 做動畫處理。但是要記住的是僅當 HTML 內聯包含 SVG 文件時,我們才可以使用 CSS 對其做樣式開發。本文我們針對平時 CSS3 + HTML不容易實現,而利用 SVG 可以快速簡便實現的幾種場景做相應介紹

3-2.SVG 動畫實踐

1、直線的變化
2、path 路徑實現圖形的平滑變幻
3、描邊動畫
4、指定軌跡運動

3-2-1、直線的變化

下面這張圖是一個 GIF 的 icon,體積大約是 156KB,壓縮之後。

living.gif

如果我們用 SVG 去實現的話。應該怎麼做呢。我們分為以下兩種方式,親測兼容性都 OK

CSS+SVG 實現的代碼實踐

基於 SMIL 實現的代碼實踐

總結&説明

知識點1:

SVG 中有很多屬性我們是可以用 CSS 去描述的。在基於 CSS 動畫三劍客(animation, transform, transition)的基礎上。我們對一些屬性進行控制,就達到我們想要的動畫效果。下面兩點值得説明:

  • transform:transform 有兩種用法,一個是在 SVG 標籤裏寫的 transform 屬性、另一種是在 CSS 文件裏寫的 transform,他們有着本質的區別。
<rect transform="rotate(45deg) ..."  ... />

rect {
    transform: rotate(45deg)
}
/** 行內的 transform 屬性,他的執行基點是在我們 svg 元素的左上角也就是 svg 的座標原點。**/
/** 而 CSS 的 transform 原點則在元素本身的中心點。**/
  • CSS可描述屬性: 很多文章告訴我們 CSS 可以控制 SVG 去做動畫,但是實際開發過程中我們會更想知道,到底哪些屬性我們可以做css控制,這裏給大家列出一些常用屬性並且可以放心使用的屬性

    | CSS 可控屬性名 |可實現場景
    | --- | --- |
    | 理論上所有的顯示屬性,都可以使用 CSS 控制包括:比如 stroke-width、color、fill 等等 SVG 的顯示屬性|大部分的顯示樣式動態變化
    |x|我們知道矩形有 x、y 屬性,其含義是起始點,控制 x,我們可以動態控制矩形的X軸位移
    |y|控制 y,我們可以動態控制矩形的 Y 軸向位移
    |cx|<circle cx="100" cy="100" r="50" fill="#fff" />這是一個圓形,控制 cx 可以控制圓形(或者橢圓)的 X 軸位移|
    |cy|控制 cy 可以控制圓形(或者橢圓)的 Y 軸位移
    |r|r 是圓的半徑,控制 r 可以控制圓形的大小
    |rx|rx 是橢圓的 X 軸方向半徑,控制 rx 可以控制橢圓的大小
    |ry|ry 是橢圓的 Y 軸方向半徑,控制 ry 可以控制橢圓的大小
    |d|path 標籤的 d 屬性,控制 d 的路徑信息,可以控制圖形的變幻(d 屬性在 safari 上是不支持 css 描述的。我們下文會詳細的説明)|
    |PS:如果各位看官們在日常開發中,不清楚該屬性是否可以通過 css 去控制,這邊給大家提供一個查詢鏈接|不支持 CSS 控制的 SVG 相關屬性

知識點2: 可以利用 SMIL 對 SVG 做動畫處理,舉個例子,同樣的動畫效果,下面這段代碼不用 CSS 也可以實現
export default function App() {
    return (
    <div className="App">
        <svg width="100%" height="100%" viewBox="0 0 100% 100%">
        {[1, 2, 3, 4, 5].map((it, index) => (
            <line
            key={index}
            stroke="#000"
            strokeWidth="2"
            x1={15 + index * 5}
            y1="8"
            x2={15 + index * 5}
            y2="22"
            >
            <animate
                attributeName="y1"
                values="8; 15; 8"
                dur="1s"
                begin={`${(5 % (index + 1)) * 0.2}s`}
                repeatCount="indefinite"
            />
            <animate
                attributeName="y2"
                values="22; 15; 22"
                dur="1s"
                begin={`${(5 % (index + 1)) * 0.2}s`}
                repeatCount="indefinite"
            />
            </line>
        ))}
        </svg>
    </div>
    );
}

那麼
什麼是 SVG 的 SMIL 呢?

這裏不想再對其做大篇幅的贅述,因為網上有很多文章都已經説得比較詳細了[SMIL 動畫指欄](https://css-tricks.com/guide-svg-animations-smil/)、[SVG SMIL animation 動畫詳解](https://www.zhangxinxu.com/wordpress/2014/08/so-powerful-svg-smil-animation/)。 本文更想和大家交流的是在SMIL驅動和CSS驅動如何做選擇的問題。  

雖然説早在 Chrome 45,chrome 就已經官宣要棄用 SMIL,但是到目前位置,各大瀏覽器廠商對它的支持度是這樣的

image.png
Chrom 宣佈棄用 SMIL 是因為要支持 CSS Animation 與 Web Animation 的發展,所以我們可以理解為當前是在一個過渡狀態,確實有一些暫時CSS 還沒法支持或者支持度很差的動畫效果,SMIL 可以輕鬆完成。但是基於 web 動畫技術發展的大趨勢,還是建議我們 SVG 動畫實現方案的選擇優先級是CSS 驅動 -> JS 驅動(我們可以採用一些框架,文末會給大家推薦一些好用的框架) -> SMIL 驅動

3-2-2、path 路徑的變化(圖形平滑變化)

image.png
image.png

CSS+SVG 實現的代碼實踐 Logo 變化

基於 SMIL 實現的代碼實踐 Logo 變化

CSS+SVG 實現的代碼實踐播放暫停

基於 SMIL 實現的代碼實踐播放暫停

總結&説明

知識點1:

通過對<path/> d 屬性的控制,我們可以實現很多動畫效果,對於 d 屬性的控制目前有兩種方式,一種是通過 CSS 控制,另一種是通過 SMIL 控制,但是目前由於 safari 不支持用 CSS 來描述<path>標籤的 d 屬性。所以在實現這種平滑的形狀變形效果上不推薦使用 CSS。更加推薦使用SMIL或者第三方庫去實現

基於 CSS:

    path {
      transition: ease all 0.3s; // 就像對dom一樣的對待svg

      &.play { //這裏是播放狀態下的<path />路徑
        d: path("M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z");
      }

      &.pause { //這裏是播放狀態下的<path />路徑
        d: path(
          "M 12,26 16.33,26 16.33,10 12,10 z M 20.66,26 25,26 25,10 20.66,10 z"
        );
      }
    }

基於 SMIL(即通過<animate>實現對<path> d 屬性的動態控制):

  const pathMap = {
    from: "M 12,26 16.33,26 16.33,10 12,10 z M 20.66,26 25,26 25,10 20.66,10 z",
    to: "M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z"
  };
  <svg class="icon" viewBox="0 0 120 120" width="400" height="400">
    <path
      d="M 12,26 16.33,26 16.33,10 12,10 z M 20.66,26 25,26 25,10 20.66,10 z"
      fill="#000000"
    >
      <animate
        attributeName="d"
        from={play ? pathMap.from : pathMap.to}
        to={play ? pathMap.to : pathMap.from}
        dur="0.3s"
        begin="indefinite" // 這裏設置開始時間為無限以達到不自動播放的效果
        fill="freeze"
      />
    </path>
  </svg>

以上兩個 path 路徑的切換,就可以帶來這種平滑過渡的效果。

play.gif

知識點2:
我們看到的圖形變幻,都需要遵循一個原則就是點數對齊原則,什麼意思呢?我們可以看下面的demo,五角星到 10 邊形(多邊形畫的不好,抱歉...😜)。,都是 10 個控制點到 10 個控制點的過度。所以效果平滑

image.png

image.png

而下圖的 10 個點到 3 個點就沒有這種平滑的過渡效果了(當然現在很多的 SVG 動畫框架已經解決了這個問題。見文末的框架推薦 )

image.png

3-2-3、描邊動畫的應用

image.png

image.png

CSS+SVG 實現的代碼實踐-星環

CSS+SVG 實現的代碼實踐- LOGO 描邊

基於 SMIL 實現的代碼實踐-進度環

總結&説明

知識點1:

類似的描邊動畫我們可以拿來做很多效果,比如各種形狀的進度條、比如文字的描邊、比如霓虹燈流水燈光等等流動動畫效果。而描邊動畫的核心點就在於 SVG 的兩個顯示屬性分別是 stroke-dasharray、stroke-dashoffset,我們上文説了,幾乎所有的顯示屬性都可以用 CSS 去控制,所以這種動畫,建議使用 CSS 去開發。

屬性 值舉例 描述 支持範圍
stroke-dasharray 1 3 4 4 它的值是一個序列,可以傳入多個值,分別指定描邊短線的長度和描邊線間距,多個值依次循環,如果傳入3個值,類似於 stroke-dasharray: 1,2,3。則會自動複製一份再生效 <circle>, <ellipse>, <line>, <mesh>, <path>, <polygon>, <polyline>, <rect> <altGlyph>, <altGlyphDef>, <altGlyphItem>, <glyph>, <glyphRef>, <textPath>, <text>, <tref>, <tspan>
stroke-dashoffset 10 描邊線段的起始位置距離圖形繪製起點的偏移量。正負值可以決定順時針還是逆時針走向 跟stroke-dasharray一致

設想一個場景,一個倒計時需要從 100 到 0,對應的視覺效果也就是從全描邊到無描邊。那麼我們初始狀態將 stroke-dasharray 的第一個值設為 2πr (周長),第二個值設也設為 2πr (周長)。那麼我們會得到一個整圓。

image.png

這時如果我們把圓展開就能看到這樣的場景

image.png
所以要實現進度的動態變化其實有兩種方案
第一種是將stroke-dasharray的第一個值從2πr(周長)調整到0。原展開圖中的黑色部分沒有了(可以理解為變成了一個點如下圖,看不見了),只剩下虛線部分是空白間隙了。

第二種是將stroke-dashoffset的值從0調整到-2πr(或者增加到2πr)。對比第一張圖成下圖的樣子
image.png

知識點2:
在實際開發中,我們會遇到一些比較複雜的圖形需要做描邊,這個時候我們沒辦法去得到它的周長是多少,這時候分兩種場景處理。一種是在CSS裏我們可以將stroke-dasharray的第二個值設置成一個非常大的數字,然後再去調整第一個值比如:

    path {
        stroke-dasharray: 0(調整到合適的值) 99999999999999
    }

如果在js裏我們需要動態去獲取周長的話,SVG提供了原生的api可以去獲取path的周長。

    const inPath = document.getElementById("inner-path");
    console.log(inPath.getTotalLength());

ps:有些資料説該方法只能用於<path />,但是筆者親測了在safair和chrome上,基本可以支持所有的基礎圖形以及<path />,但是<text />不支持,瀏覽器會報not a function。

既然説到了getTotalLength(),那麼順帶説下getPointAtLength()。getPointAtLength,顧名思義就是根據距離獲取點座標。意思就是根據到起始點的距離,獲取該指定距離對應的點的座標。座標系原點為該圖形的起始點。在一些指向型的動畫上我們可能會運用到這個api。

3-2-4、軌跡運動動畫的應用

fly.gif

基於SMIL實現的代碼實踐-軌跡運動

總結&説明
{/* 我們將整個飛機圖形元件用g標籤包起 */}
<g transform="translate(-100, -35) scale(0.1)">
  <path
    d="M164.485419 578.709313L196.274694 794.731891c0.722484 5.53904 8.188147 7.224835 11.078081 2.408278l75.860772-121.377234 740.063969-363.409219L164.485419 578.709313z"
    fill="#F68206"
  ></path>
  <path
    d="M2.167451 440.233302l159.668861 132.214487 857.828787-260.334901zM289.475071 679.375353l191.217309 153.407337 542.344309-518.743179z"
    fill="#FF9900"
  ></path>
  <path
    d="M204.222013 800.030103l125.23048-80.677328-48.888053-39.014111-76.342427 118.4873"
    fill="#D3650B"
  ></path>
  {/* 然後在這裏,我們利用animateMotion,去做這個軌跡運動 */}
  <animateMotion
    path="M 0 450 Q 150 50 250 50 Q 350 0 400 50 Q 500 50 450 200 C 300 350 250 200 500 50 C 600 50 750 200 650 250 A 50 50 0 1 1 800 50 "
    begin="0s"
    rotate="auto"
    dur="20s"
    repeatCount="indefinite"
  />
</g>

這裏我們用到了SMIL裏的<animateMotion />,animateMotion裏的path屬性,我們也可以像這樣去使用

    <defs>
        <path id="theMotionPath" d="xxx" />
    </defs>
    <animateMotion>
        <mpath xlink:href="#theMotionPath"/>
    </animateMotion>

實際生產中,我們這種軌跡運動的需求,是建議使用SMIL去實現的,當然CSS也是有實現方案的《使用CSS offset-path讓元素沿着不規則路徑運動》。但是CSS的兼容實在不敢恭維,勸退一波。

四、寫在最後

1、 建議將CSS動畫用於無變形的過渡或簡單動畫。尤其是在硬件加速時。CSS不需要加載其他資源(一般指三方庫),並且懸停時的小變換可以為交互帶來更好的效果。特別是當你不需要3d、物理體感、或進行大量堆疊動畫效果時建議選用CSS。另外,CSS方便調試也是很大的一個優勢。

2、對於較長的動畫,開發時會變得非常複雜且需要花精力去調試,而CSS調整時間尺度很困難,尤其是當你需要操縱一些細微幀時,個人覺得SMIL更合適做有序的,複雜的堆疊動畫羣的場景。

3、對於變形的動畫,建議使用SMIL或者第三方庫。推薦的比較優秀的三方庫有以下幾個。

| 庫名 | 描述|
| --- | --- |
| GSAP | 全稱是GreenSock Animation Platform,以前流行用 flash 的時候,GSAP就叱吒江湖的存在,GSAP有兩個版本一個是 flash 版本,一個是 javascript 版本,也就是我們説的 GSAP js。GSAP 速度快。GSAP專門優化了動畫性能,使之實現和css一樣的高性能動畫效果;輕量與模塊化;|
| Snap.svg、SVG.js、Velocity.js | 這三個庫一直會被開發者拿來對比,基本上會用jQuery,就會使用這三個庫,也就是説入手友好,Snap.svg 更偏向於支持現代瀏覽器,所以它的體量也會小一些。對比 Snap.svg 來看 SVG.js ,SVG.js 的寫法更加的清晰,使用時會有更好的體驗,且自稱提供接近完整的 SVG 規範覆蓋。Snap.svg 風格就更像一個俠客,寫起來會很瀟灑但是不好讀,Velocity 也很強大,簡單易用、高性能、功能豐富 |
| anime.js | anime.js 雖然功能沒有 GASP 強大,但是體積很樂觀,gzip壓縮完只有9kb左右,滿足日常需求開發還是足夠的 |
| D3 | Data-Driven Documents 顧名思義,更加適合用於創建數據可視化圖形場景去使用|

4、如何使用 SMIL 進行硬件加速,使用 <animateTransform>代替<animate>,並設置 x、y、z 值(z 為 0)。原理與 CSS 類似,這會將元素移到它自己的層,從而在其發生運動時不會重新繪製。

參考資料

  • SVG 動畫指欄(SMIL)
  • SVG 教程
  • CSS 支持的 SVG 屬性查尋
  • 以及文章內提及的一些文章
本文發佈自 網易雲音樂大前端團隊,文章未經授權禁止任何形式的轉載。我們常年招收前端、iOS、Android,如果你準備換工作,又恰好喜歡雲音樂,那就加入我們 grp.music-fe(at)corp.netease.com!
user avatar guizimo 頭像 susouth 頭像 ailim 頭像 pugongyingxiangyanghua 頭像
4 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.