电竞比分网-中国电竞赛事及体育赛事平台

分享

SVG 動(dòng)畫(huà)精髓

 筑心wup 2019-04-14

TL;DR

本文主要是講解關(guān)于 SVG 的一些高級(jí)動(dòng)畫(huà)特效,比如 SVG 動(dòng)畫(huà)標(biāo)簽,圖形漸變,路徑動(dòng)畫(huà),線條動(dòng)畫(huà),SVG 裁剪等。

例如:路徑動(dòng)畫(huà)

gif

圖形漸變:

fig

線條動(dòng)畫(huà):

test.gif-388.2kB

以及,相關(guān)的動(dòng)畫(huà)的矩陣知識(shí),這個(gè)也是現(xiàn)在 CSS 動(dòng)畫(huà)里面最重要,同時(shí)也是最為欠缺的知識(shí)點(diǎn):

image.png-7.1kB

文章會(huì)先從基本語(yǔ)法入手,然后,慢慢深入。介紹一些動(dòng)畫(huà)基本原理和對(duì)應(yīng)的數(shù)學(xué)原理知識(shí)點(diǎn)。并且文章后面,還附有相關(guān)語(yǔ)法的介紹,當(dāng)你在遇到不熟悉語(yǔ)法的時(shí)候可以參考參考。

前面一篇文章,主要介紹了一些 SVG 的基本概念和基本圖形。接下來(lái)我們需要了解一下,SVG 處理矢量這個(gè)特性之外,還有啥內(nèi)容吸引我們,能讓 SVG 現(xiàn)在普及度這么高?

原文參考:前端小吉米

SVG Animation

在 SVG 中,如果我們想實(shí)現(xiàn)一個(gè)動(dòng)畫(huà)效果,可以使用 CSS,JS,或者直接使用 SVG 中自帶的 animate 元素添加動(dòng)畫(huà)。

使用 CSS 的話,有兩種選擇一種是通過(guò) style 直接內(nèi)聯(lián)在里面,另外是直接使用相關(guān)的動(dòng)畫(huà)屬性-- transform。

 <use id="star" class="starStyle" xlink:href="#starDef"
       transform="translate(100, 100)"
       style="fill: #008000; stroke: #008000"/>

而使用 SVG 中自定的 animate 主要還是 SVG 自己的東西,比較好用。如果想用 CSS 的動(dòng)畫(huà),這都無(wú)所謂。

先看一個(gè) SVG animate DEMO:

<rect x="10" y="10" width="200" height="20" stroke="black" fill="none">
  <animate
    attributeName="width"
    attributeType="XML"
    from="200" to="20"
    begin="0s" dur="5s"
    fill="freeze" />
</rect>

通過(guò)將 animate 標(biāo)簽嵌套在指定的圖形里面,即可實(shí)現(xiàn)變換的效果。另外,還有 animateTransform,它主要是用來(lái)做變形動(dòng)畫(huà)的。

<rect x="-10" y="-10" width="20" height="20"
    style="fill: #ff9; stroke: black;">
    <animateTransform attributeType="XML"
      attributeName="transform" type="scale"
      from="1" to="4 2"
      begin="0s" dur="4s" fill="freeze"/>
</rect>

簡(jiǎn)單來(lái)說(shuō):

  • animate: 相當(dāng)于 CSS 中的 transition

  • animateTransform: 相當(dāng)于 CSS 中的 transform

里面一些技術(shù)細(xì)節(jié)我們這里就不過(guò)多講解了。這里,主要想介紹一下 animate 中的 morph 的效果。

animate morph

該效果主要做的就是圖形內(nèi)部的漸變。如圖:

fig

這種動(dòng)畫(huà)是怎么實(shí)現(xiàn)呢?

直接看代碼吧:

<path fill="#1EB287">
    <animate 
             attributeName="d" 
             dur="1440ms" 
             repeatCount="indefinite"
             keyTimes="0;
                       .0625;
                       .208333333;
                       .3125;
                       .395833333;
                       .645833333;
                       .833333333;
                       1"
             calcMode="spline" 
             keySplines="0,0,1,1;
                         .42,0,.58,1;
                         .42,0,1,1;
                         0,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1"
             values="M 0,0 
                     C 50,0 50,0 100,0
                     100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     Z;

                     M 0,0 
                     C 50,0 50,0 100,0
                     100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     Z;

                     M 50,0 
                     C 75,25 75,25 100,50 
                     75,75 75,75 50,100
                     25,75 25,75 0,50
                     25,25 25,25 50,0
                     Z;

                     M 25,50 
                     C 37.5,25 37.5,25 50,0 
                     75,50 75,50 100,100
                     50,100 50,100 0,100
                     12.5,75 12.5,75 25,50
                     Z;

                     M 25,50 
                     C 37.5,25 37.5,25 50,0 
                     75,50 75,50 100,100
                     50,100 50,100 0,100
                     12.5,75 12.5,75 25,50
                     Z;

                     M 50,0
                     C 77.6,0 100,22.4 100,50 
                     100,77.6 77.6,100 50,100
                     22.4,100, 0,77.6, 0,50
                     0,22.4, 22.4,0, 50,0
                     Z;
                     
                     M 50,0
                     C 77.6,0 100,22.4 100,50 
                     100,77.6 77.6,100 50,100
                     22.4,100, 0,77.6, 0,50
                     0,22.4, 22.4,0, 50,0
                     Z;
                     
                     M 100,0 
                     C 100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     50,0 50,0 100,0
                     Z;"/>
  </path>

這么多,是不是感覺(jué)有點(diǎn)懵逼。不過(guò),我們細(xì)分來(lái)看一下其實(shí)很簡(jiǎn)單。里面主要是利用 animate 中的 keyTimes,calcModekeySplines,以及 values 這幾個(gè)屬性。不急,我們一個(gè)一個(gè)來(lái)解釋一下。

  • keyTimes: 這其實(shí)和 CSS 中定義的 @keyframes 一樣。通過(guò) 0-1 之間的值,定義每段動(dòng)畫(huà)完成的時(shí)間。格式為:value;value...。例如 0;.0625;.208333333;.3125;.395833333;.645833333;.833333333;1。從第一個(gè)動(dòng)畫(huà),到第二個(gè)動(dòng)畫(huà)經(jīng)歷的時(shí)間比例為 6.25%。并且,keyTimes 需要和 values 里面定義的幀數(shù)一致。

  • calcMode: 用來(lái)定義動(dòng)畫(huà)具體的插值模型。取值有: discrete | linear[default] | paced | spline。具體可以參考 MDN。這里我們主要介紹一下 spline。該值表示每個(gè)動(dòng)畫(huà)間使用自定的貝塞爾變換曲線。如果沒(méi)有特殊要求,使用 linear 其實(shí)已經(jīng)足夠了,這樣就不用麻煩去定義下面的 keySplines 屬性。

  • keySplines:該值用來(lái)具體定義動(dòng)畫(huà)執(zhí)行時(shí)的 貝塞爾曲線。使用格式是通過(guò) ; 來(lái)分隔每一個(gè)值。即,cubic-bezier(.31,.57,.93,.46) 為一組。使用 keySplines 表達(dá),則為:keySplines = ".31,.57,.93,.46;"。當(dāng)然,里面的貝塞爾曲線組數(shù)為 整個(gè)動(dòng)畫(huà)幀數(shù) - 1。

而 values 就很簡(jiǎn)單了。它是直接結(jié)合 attributeName 屬性,來(lái)設(shè)置具體的值,每個(gè)值之間使用 ; 進(jìn)行分隔。

像上面那樣,可以在指定元素里面嵌套多個(gè) animate,既實(shí)現(xiàn)了形狀的改變,又實(shí)現(xiàn)了顏色的改變。Morph 比較常用于數(shù)字的更迭,比如,倒數(shù) 10s 的相關(guān)動(dòng)畫(huà)。到這里,Morpah 相關(guān)的知識(shí)點(diǎn)就結(jié)束了。

接著,讓我們來(lái)看一下 SVG 中,另外一非常重要的標(biāo)簽 -- animateMotion。

該標(biāo)簽可以讓指定的元素,繞著指定的路徑進(jìn)行運(yùn)動(dòng)。所以這對(duì)于復(fù)雜的路徑來(lái)說(shuō)非常有用,因?yàn)槲覀兒茈y使用 transform 去模擬復(fù)雜的變換路徑。看一個(gè) DEMO

gif

animateMotion

animateMotion 大致的屬性和 animate 差不多,不過(guò),它還擁有自己特有的屬性,比如 keyPoints、rotate、path 等。不過(guò),calcMode 在 AM(animateMotion) 中的默認(rèn)屬性由,linear 變?yōu)?paced。

這些屬性,我們慢慢介紹,先從最簡(jiǎn)單的開(kāi)始吧。首先,我們來(lái)看一個(gè) DEMO:

<g>
  <rect x="0" y="0" width="30" height="30" style="fill: #ccc;"/>
  <circle cx="30" cy="30" r="15" style="fill: #cfc; stroke: green;"/>
  <animateMotion from="0,0" to="60,30" dur="4s" fill="freeze"/>
</g>
  • from,to:指定兩點(diǎn)的位置,位置參數(shù)是以元素的坐標(biāo)為原點(diǎn)的。

  • dur:執(zhí)行渲染時(shí)間

  • fill:指定動(dòng)畫(huà)結(jié)束后停留的裝填。有 freezeremove 效果。remove 表示回到動(dòng)畫(huà)開(kāi)始的位置,freeze 表示停留在動(dòng)畫(huà)結(jié)束的位置。

如果,你想要更復(fù)雜的路徑,可以直接使用 path 屬性來(lái)指定路徑。用法和 path 標(biāo)簽中 d 屬性是一樣的。

<rect x="0" y="0" width="30" height="30" style="fill: #ccc;">
    <animateMotion
    path="M50,125 C 100,25 150,225, 200, 125"
    dur="6s" fill="freeze"/>
</rect>

或者使用 mpath 標(biāo)簽,引用外部的 path。

  <path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110"
      stroke="lightgrey" stroke-width="2" 
      fill="none" id="theMotionPath"/>
  <circle cx="10" cy="110" r="3" fill="lightgrey"  />
  <circle cx="110" cy="10" r="3" fill="lightgrey"  />

  <!-- Red circle which will be moved along the motion path. -->
  <circle cx="" cy="" r="5" fill="red">

    <!-- Define the motion path animation -->
    <animateMotion dur="6s" repeatCount="indefinite">
      <mpath xlink:href="#theMotionPath"/>
    </animateMotion>
  </circle>

動(dòng)畫(huà)效果為:

tuiche.gif-17.8kB

所以,一般而言我們?cè)诙x AM 的路徑的時(shí)候,只用一種方式定義即可,否則會(huì)發(fā)生相應(yīng)的覆蓋:mpath>path>values>from/to

在 AM 運(yùn)動(dòng)中,還有一個(gè)很重要的概念就是旋轉(zhuǎn)角。默認(rèn)情況下,運(yùn)動(dòng)物體的角度是按照它和坐標(biāo)軸的初始角度確定的。例如:

test.gif-23.7kB

這樣看起來(lái)確實(shí)有些別扭,那能不能讓物體垂直于路徑進(jìn)行運(yùn)動(dòng)呢?

有的,根據(jù) rotate 屬性值,一共有 3 個(gè)值可供選擇。

  • auto:讓物體垂直于路徑的切線方向運(yùn)動(dòng)。不過(guò),如果你的路徑是閉合曲線的話,需要注意起始點(diǎn)的位置。

例如:

test.gif-22.4kB

  • auto-reverse:讓物體垂直于路徑的切線方向并 + 180°。也就是和 auto 運(yùn)動(dòng)關(guān)于切線對(duì)稱。

test.gif-27.8kB

  • Number:讓物體以固定的旋轉(zhuǎn)角度運(yùn)動(dòng)。這個(gè)就相當(dāng)于使用 transform:rotate(deg) 進(jìn)行控制。

在動(dòng)畫(huà)設(shè)置標(biāo)簽中,還有一個(gè)更簡(jiǎn)單的--set。

set

該標(biāo)簽也是用來(lái)模擬 transition 效果的。它和 animate 的主要區(qū)別是,它僅僅需要 to 的指定屬性,而不需要其他的參考屬性,比如 from,by 等。那它有啥特別的存在意義嗎?

有的,因?yàn)?set 針對(duì)于所有屬性,甚至包括 style 里面的相關(guān) CSS 屬性。所以,可以靠它來(lái)很好描述一些非 number 的屬性值。

<text text-anchor="middle" x="60" y="60" style="visibility: hidden;">
  <set attributeName="visibility" attributeType="CSS"
    to="visible" begin="4.5s" dur="1s" fill="freeze"/>
  All gone!
</text>

矩陣動(dòng)畫(huà)

上面差不多簡(jiǎn)單闡述了關(guān)于 SVG 一些比較有特點(diǎn)的動(dòng)畫(huà)。當(dāng)然,還有比較重要的線條動(dòng)畫(huà),這個(gè)我們放到后面進(jìn)行講解。這里先來(lái)看一下所有動(dòng)畫(huà)中,非常重要的矩陣原理。線性代數(shù)應(yīng)該是大學(xué)里面來(lái)說(shuō),最容易學(xué)的一門科目,MD。。。還記得,大學(xué)線代期末考試的時(shí)候,100 分的同學(xué)應(yīng)該說(shuō)是如韭菜地般,一抓一大片(對(duì)不起,我沒(méi)能和他們同流合污。)

那矩陣是如何在動(dòng)畫(huà)中使用的呢?

簡(jiǎn)單的說(shuō),矩陣中的每個(gè)元素其實(shí)可以等價(jià)代換為每個(gè)因式里面的系數(shù):

image.png-6kB

上面也叫作 三維矩陣。即,它涉及到 x,y,z 軸的計(jì)算。那對(duì)于我們平面 2D 變換來(lái)說(shuō),那么此時(shí)矩陣又是哪種形式呢?

很簡(jiǎn)單,只要將 z 軸永遠(yuǎn)置為一個(gè)常數(shù)就 OK。這里,慣例上是直接取 0 0 1 來(lái)設(shè)置。

image.png-1.7kB

不信的話,大家只要代進(jìn)去乘以乘,應(yīng)該就可以得到結(jié)果了。所以,在二維中,具體變換方式為:

image.png-7.1kB

后面,我們也會(huì)依據(jù)這個(gè)公式進(jìn)行相關(guān)的變形操作。那矩陣變換是怎么運(yùn)用到 CSS/SVG 當(dāng)中呢?

在 CSS 中,是直接使用 transform 中的屬性:

transform: matrix(a,b,c,d,e,f);

當(dāng)然,在 SVG 中也是一樣的:

<g transform="matrix(1,2,3,4,5,6)">
    <line x1="10" y1="20" x2="30" y2="40" style="stroke-width: 10px; stroke: blue;"/>
  </g>

所以,我們主要的重點(diǎn)就是講解一下 matrix 這個(gè)屬性。它的格式為:

matrix(a,b,c,d,e,f);

對(duì)應(yīng)于我們上面的公式有:

image.png-2.5kB

在接觸 transform 的時(shí)候,大家應(yīng)該了解到 transform 里面有很多固定的動(dòng)畫(huà)屬性:

  • translate()

  • rotate()

  • scale()

  • skew()

實(shí)際上,在底層還是使用 matrix 實(shí)現(xiàn)的變換。就拿 translate 舉個(gè)例子吧。

translate 的格式為:

translate(dx,dy)

相當(dāng)于參考當(dāng)前原點(diǎn),在 x/y 軸上移動(dòng) dx/dy 的距離。那么映射到矩陣,應(yīng)該如何表示呢?

很簡(jiǎn)單,它等同于:

matrix(1 0 0 1 dx dy);

使用代數(shù)證明一下:

假設(shè)有 matrix(1 0 0 1 20 30)

變?yōu)榫仃嚍椋?/p>

image.png-3.2kB

根據(jù),上面的表達(dá)式有:

X = x'*1 + y'*0 + 20 = x' + 20
Y = x'*0 + y'*0 + 30 = y' + 30

所以,就是 X 在原有 X 軸坐標(biāo)上向右移動(dòng) 20 的距離,Y 相對(duì)于原有移動(dòng) 30 的距離。

那么其他幾個(gè)屬性呢?也是怎么變化的嗎?

恩,類似。只是里面取值不一樣:

  • scale(x,y): 放大 X/Y 軸,矩陣的表達(dá)為 matrix(x 0 0 y 0 0)。

  • rotate(θ): 坐標(biāo)旋轉(zhuǎn),矩陣的表達(dá)為 matrix(cosθ sinθ -sinθ cosθ 0 0)。

  • skew(θx,θy): X/Y 軸拉伸,矩陣的表達(dá)為 matrix(1 tanθx tanθy 1 0 0)。

注意,上面三個(gè)都會(huì)改變?cè)形矬w的坐標(biāo)系?。?! 這點(diǎn)很重要,換句話說(shuō),后面每次變換都是基于前面一個(gè)的變換結(jié)果的。

詳情看圖:

image.png-49.6kB

詳情可以參考:MDN matrix

不過(guò),這并不是我們使用 matrix 的重點(diǎn),也不是它的優(yōu)勢(shì)。它的優(yōu)勢(shì)在于可計(jì)算,即,能夠?qū)?fù)雜的動(dòng)畫(huà)集合到一個(gè)表達(dá)式中,并且,后續(xù)的變換可以直接基于當(dāng)前的 matrix。

我們先來(lái)了解一下,如果多個(gè)變換動(dòng)畫(huà)一起使用,matrix 應(yīng)該如何表達(dá)呢?

只需要找到我們變換動(dòng)畫(huà)對(duì)應(yīng)的矩陣,然后相乘即可。例如,先旋轉(zhuǎn) 45°,然后放大 1.5 倍,則有變換動(dòng)畫(huà)為:

transform: rotate(45deg) scale(1.5,1.5);

注意,雖然,你定義動(dòng)畫(huà)是分開(kāi)的,但此時(shí)的動(dòng)畫(huà)是同時(shí)進(jìn)行的。為啥?因?yàn)?,這兩個(gè)動(dòng)畫(huà)實(shí)際上可以整合成為一個(gè)變換矩陣:

image.png-7.4kB

并且,位置是不可以調(diào)換的。比如,transform: scale(2,2) translate(20px,30px)。即,你先放大兩倍,然后移動(dòng) 20,30 的距離。注意,這里移動(dòng)的 20,30 相對(duì)的是已經(jīng)放大過(guò)后的坐標(biāo),相對(duì)于原坐標(biāo)而言就是 40,60 了。 如果,你調(diào)換位置,即 transform: translate(20px,30px) scale(2,2)。就變成現(xiàn)在原坐標(biāo)移動(dòng) 20,30,然后再放大兩倍。

而上面強(qiáng)調(diào)的順序關(guān)系,實(shí)際上就可以理解為矩陣不滿足交換律的原則。因?yàn)橐坏┙粨Q,結(jié)果很可能不一樣。

矩陣高級(jí)用法

上面的內(nèi)容只是簡(jiǎn)單的描述了關(guān)于矩陣的概念。在實(shí)際中,矩陣可以說(shuō)是真正利器。

假設(shè)現(xiàn)在有一個(gè)動(dòng)畫(huà),要求你將一個(gè)物體從一個(gè)點(diǎn)通過(guò)拋物線的方式移動(dòng)到另外一個(gè)點(diǎn),那么此時(shí)要求 JS/CSS 隨你挑。此時(shí),你會(huì)不會(huì)感覺(jué),呼吸急促,頭腦發(fā)熱呢?

恩,matrix 可以治,而且包治百病。不過(guò),matrix 有一個(gè)限制點(diǎn),它只能用于一次線性動(dòng)畫(huà)表達(dá)式。即,針對(duì)于拋物線,橢圓曲線這類復(fù)雜曲線來(lái)說(shuō),不太合適。那么有什么辦法嗎?

有的,微分思想。每一段動(dòng)畫(huà)其實(shí)都可以通過(guò)一定范圍內(nèi)的直線拼接而成,那么這樣,我們就可以將一段拋物線拆分為由幾段線段構(gòu)成的曲線。當(dāng)然,如果你分的越細(xì),擬合度就越高。這里我們不打算過(guò)度你和,我們簡(jiǎn)單的將一段拋物線分為 5段。

如圖:

image.png-12.9kB

那么接下來(lái)就是摳細(xì)節(jié)。這里,依次取傾角為 45°,30°,0°,-45°,-30° 這 5 段直線。每段分配的時(shí)間比例為 20%、25%、10%、25%、20% 這主要是用于 keyframe 的設(shè)定?,F(xiàn)在,用數(shù)學(xué)來(lái)分析一下,這個(gè)動(dòng)畫(huà)到底該怎么弄。

現(xiàn)在,已知兩點(diǎn)之間的距離為 100px。那么我們同樣根據(jù)上述比例分,則有 20px, 25px, 10px, 25px, 20px。

這里我們以 45° 傾角為參考點(diǎn),則終點(diǎn)坐標(biāo)為 (20,20); 。那么,該段的矩陣為:

// 注意 Y 軸需要取負(fù)值!

 1 0 20
 0 1 -20
 0 0 1

CSS 中的變形動(dòng)畫(huà)為:

transform: matrix(1,0,0,1,20,-20);

然后,第二段就為:

1 0 25
0 1 -14.4
0 0 1

使用矩陣的乘法法,則有:

 1 0 45
 0 1 -34.4
 0 0 1

變形動(dòng)畫(huà)為:

transform: matrix(1,0,0,1,45,-34.4);

剩余幾段也是這樣的做法。最終,整個(gè) keyframe 就應(yīng)該表示為:

@keyframe Parabola{
    20%{
        transform: matrix(1,0,0,1,20,-20);
    }
    45%{
        transform: matrix(1,0,0,1,45,-34.4);
    }
    ...
}

整個(gè)動(dòng)畫(huà)過(guò)程差不多都是這樣。當(dāng)然,矩陣也不僅僅局限于這幾個(gè)動(dòng)畫(huà),憑借著高度定制化和靈活性的特點(diǎn),這它還常常用于進(jìn)行回彈,彈跳等動(dòng)畫(huà)中。如果大家有興趣,后期也可以對(duì)這類動(dòng)畫(huà)進(jìn)行簡(jiǎn)單的講解。

后面,我們最后來(lái)了解一下 SVG 中很重要的線條動(dòng)畫(huà)。

線條動(dòng)畫(huà)

SVG 中的線條動(dòng)畫(huà)常常用作過(guò)渡屏(splash screen)中。例如:

test.gif-35.7kB

或者,一些比較炫酷的 LOGO 中,比如 AllowTeam 的:

AT

看到這些炫酷的效果,大家有沒(méi)有動(dòng)心想學(xué)一學(xué),看看自己到底能否做的這么好呢?

OK,我們現(xiàn)在來(lái)正式介紹一下線條動(dòng)畫(huà)。在 SVG 中,最長(zhǎng)用到的線條標(biāo)簽就是 Path。這里我前面一篇文章已經(jīng)做了介紹,我這里就不贅述了。

而在具體變化當(dāng)中用到的是關(guān)于 stroke 的相關(guān)屬性:(下面的屬性都可以直接用在 CSS 當(dāng)中!)

  • stroke*:定義筆觸的顏色。例如:stroke="green"

  • stroke-dasharray*:定義 dash 和 gap 的長(zhǎng)度。它主要是通過(guò)使用 , 來(lái)分隔 實(shí)線間隔 的值。例如:stroke-dasharray="5, 5" 表示,按照 實(shí)線為 5,間隔為 5 的排布重復(fù)下去。如下圖:

image.png-0.2kB

放大看有:

image.png-5.3kB

另外,stroke-dasharray 并不局限于只能設(shè)置兩個(gè)值,要知道,它本身的含義是設(shè)置最小重復(fù)單元,即,dash,gap,dash,gap...。比如,我定義 stroke-dasharray="15, 10, 5" 則相當(dāng)于,[15,10,5] 為一段。則有:

image.png-0.3kB

放大看則有:

image.png-7.7kB

  • stroke-dashoffset*: 用來(lái)設(shè)置 dasharray 定義其實(shí) dash 線條開(kāi)始的位置。值可以為 number || percentage。百分?jǐn)?shù)是相對(duì)于 SVG 的 viewport。通常結(jié)合 dasharray 可以實(shí)現(xiàn)線條的運(yùn)動(dòng)。

  • stroke-linecap: 線條的端點(diǎn)樣式。

  • stroke-linejoin: 線條連接的樣式

  • stroke-miterlimit: 一個(gè)比較復(fù)雜的概念,如果我們只是畫(huà)一些一般的線段,使用上面 linejoin 即可。如果涉及對(duì)邊角要求比較高的,則可以使用該屬性進(jìn)行定義。它的值,其實(shí)就是角長(zhǎng)度比上線寬:

image.png-5.4kB

而實(shí)際理解的話,就是假設(shè)當(dāng) width 為 1。此時(shí)比例為 2。那么 miter = 2。那么超過(guò) 2 的 miter 部分則會(huì)被 cut 掉。可以參照:

image.png-15.6kB

他主要是配合 linejoin 一起使用。因?yàn)?linejoin 默認(rèn)取值就是 miter。所以,默認(rèn)情況下就可以使用該標(biāo)簽屬性。它默認(rèn)值為 4。其余的大家下去實(shí)踐一下即可。詳細(xì)可以參考: miter

  • stroke-opacity:線段的透明度

  • stroke-width:線的粗細(xì)。

OK,介紹完關(guān)于 path 的所有 stroke 屬性之后,我們就要開(kāi)始動(dòng)手寫(xiě)一下讓線條動(dòng)起來(lái)的代碼。簡(jiǎn)單來(lái)說(shuō),就是通過(guò) stroke-dashoffsetstroke-dasharray 來(lái)做。整個(gè)動(dòng)畫(huà)可以分為兩個(gè)過(guò)程:

  • 通過(guò) dasharray 將實(shí)線部分隱藏,空余為全線段長(zhǎng)。然后,將實(shí)線部分增加至全長(zhǎng)。比如:dasharray: 0,1000 變?yōu)?dasharray: 1000,1000。

  • 同時(shí),通過(guò) dashoffset 來(lái)移動(dòng)新增的實(shí)線部分,造成線段移動(dòng)的效果。有: dashoffset:0,變?yōu)?dashoffset:1000

不過(guò),這里我們不打算使用 Path 來(lái)做啥復(fù)雜的動(dòng)畫(huà),這主要考慮到手頭沒(méi)有一些 SVG 生成工具。所以,這里我們就以 Text 來(lái)做吧(因?yàn)樽銎饋?lái)真的簡(jiǎn)單)。

這里,先以 IV-WEB 這段文字來(lái)做動(dòng)畫(huà)。

先給大家看一下最終結(jié)果:

test.gif-61.9kB

那么這種動(dòng)畫(huà)是怎么做的呢?

這里,我主要介紹一下關(guān)于 CSS 相關(guān),SVG 就一個(gè) Text 我直接貼代碼了:

<svg viewBox="0 0 1320 300">

  <!-- Symbol -->
  <symbol id="s-text">
    <text text-anchor="middle"
          x="50%" y="50%" dy=".35em">
      IV-WEB
    </text>
  </symbol>  

  <!-- Duplicate symbols -->
  <use xlink:href="#s-text" class="text"
       ></use>
  <use xlink:href="#s-text" class="text"
       ></use>
  <use xlink:href="#s-text" class="text"
       ></use>
 

</svg>

上面是通過(guò)創(chuàng)建一個(gè)居中定位的字體,然后使用 3 個(gè) text 重疊。具體 CSS 我們下面來(lái)說(shuō)一下。首先,我們營(yíng)造的效果是從無(wú)到有,就需要使用 dasharray 將 gap 設(shè)置的足夠大。這里我取 300 即可。

stroke-dasharray: 0 300;

然后,通過(guò) nth-child 選擇器,給每一個(gè)文字使用不同的顏色值:

.text:nth-child(3n + 1) {
  stroke: #F60A0A;
}
.text:nth-child(3n + 2) {
  stroke: #F2FF14;
}

.text:nth-child(3n + 3) {
  stroke: #FB9505;
}

下面才是重點(diǎn)內(nèi)容。此時(shí),這 3 個(gè) text 的起始點(diǎn)重合。我現(xiàn)在既要他們?cè)谶\(yùn)行時(shí)不完全重合,又要他們的線條能進(jìn)行滾動(dòng)。不啰嗦了,直接看代碼吧:

@keyframes stroke {
  100% {
    stroke-dashoffset: 1000;
    stroke-dasharray: 80 160;
  }
}

@keyframes stroke1 {
  100% {
    stroke-dashoffset: 1080;
    stroke-dasharray: 80 160;
  }
}


@keyframes stroke2 {
  100% {
    stroke-dashoffset: 1160;
    stroke-dasharray: 80 160;
  }
}

這就是上面 3 個(gè)不同的 text 運(yùn)用的動(dòng)畫(huà)。dashoffet 由 0 到 1000。這完成了滾動(dòng)的目的。同時(shí),為了讓字體不重合,我還需要在對(duì)應(yīng)字體的 dashoffset 上,加上不同的間隔距離。比如,第一個(gè)字體 offset 為 1000。那么第二個(gè)字體,我需要加上前一個(gè)字體 dash 的長(zhǎng)度,即,80。所以,第二個(gè)字體就變?yōu)?1080。那么第三個(gè)就是加上前兩個(gè)的 dash 長(zhǎng)度,即 1160。

大致過(guò)程就是這樣,詳情可以查看: IVWEB 線條動(dòng)畫(huà)

這里再給大家布置一個(gè)練習(xí)作業(yè),如何實(shí)現(xiàn)無(wú)線連續(xù)的分段動(dòng)畫(huà)呢?

具體效果如圖:

test.gif-388.2kB

給點(diǎn)提示:

將多個(gè)文字重疊,取不同的 offset 和 array 即可。動(dòng)畫(huà)的終止位置一般取一個(gè) gap + dash 的周期長(zhǎng)即可。

后面看看這篇文章反響如何,到時(shí)候再?zèng)Q定是否再寫(xiě)一篇續(xù)集,介紹該作業(yè)的原理。

SVG 文字

在 SVG 中定義文字直接使用 text 標(biāo)簽即可。關(guān)于文字來(lái)說(shuō),一般而言需要注意的點(diǎn)就那么即可,文字的排列,間距等等。這些都可以直接使用 CSS 進(jìn)行控制。不過(guò),有幾個(gè)屬性比較特殊,這里需要額外提一下。

text-anchor

用來(lái)定義參考點(diǎn)和實(shí)際字符之間的定位關(guān)系。格式為:

  • text-anchor: start | middle | end | inherit

直接看代碼解釋吧:

<!-- Anchors in action -->
    <text text-anchor="start"
          x="60" y="40">A</text>

    <text text-anchor="middle"
          x="60" y="75">A</text>

    <text text-anchor="end"
          x="60" y="110">A</text>

第一個(gè) A,參考的是 (60,40) 的點(diǎn),定義為 start ,那么參考點(diǎn)應(yīng)該在字符的前面。

image.png-1kB

而剩下兩個(gè)也是同樣的道理:

image.png-1.9kB

tspan

現(xiàn)在,假如我們想在 text 里面添加一些特殊的字符效果,比如斜體,加粗等。由于,text 標(biāo)簽不能實(shí)現(xiàn)嵌套,所以,為了解決這個(gè)痛點(diǎn),提出了 tspan 的標(biāo)簽。它其實(shí)就是一個(gè)可以嵌套的 text 標(biāo)簽。

<text x="10" y="30" style="font-size:12pt;">
  Switch among
  <tspan style="font-style:italic">italic</tspan>, normal,
  and <tspan style="font-weight:bold">bold</tspan> text.
</text>

tspan 里面同樣可以自定義相關(guān)的自身屬性。詳細(xì)的可以參考 tspan 我這里就不詳述了。

在 Path 展示 text

Text 一般可以橫放,豎放。那有沒(méi)有啥辦法讓文字可以按照一定的路徑任意排放呢?

有的,這里可以使用 textPath 標(biāo)簽,來(lái)定義具體參考路徑。

<path id="sharp-corner"
    d="M 30 110 100 110 100 160"
    style="stroke: gray; fill: none;"/>

<text>
    <textPath xlink:href="#sharp-corner">
    Making a quick turn
    </textPath>
</text>

如圖:

image.png-11kB

具體細(xì)節(jié)我這里就不多說(shuō)了。

Clip

在 DOM 中如果想展示一個(gè)圖片的部分,或者以某種形狀展示圖片的部分,一般是通過(guò)一個(gè) cover div 來(lái)實(shí)現(xiàn)的。不過(guò),如果涉及到不規(guī)則圖形的話,那么 DOM 就有天生缺陷了(當(dāng)然使用 CSS 里的 clip-path 可以完成,不過(guò)兼容性不太好)。而在 SVG 中,提供了 clipPath 標(biāo)簽,能夠讓我們自定義裁剪圖片的范圍和形狀。

clipPath 里面可以接任何圖形,比如,path,rect 甚至是 text。使用的時(shí)候,直接在 style 中,指定 clip-path 即可,或者直接使用 clip-path 屬性指定。

<defs>
  <clipPath id="textClip">
    <text id="text1" x="20" y="20" transform="rotate(60)"
      style="font-family: 'Liberation Sans';
        font-size: 48pt; stroke: black; fill: none;">
CLIP
    </text>
  </clipPath>
 </defs>
 
 <use transform="translate(100, 0)"
  xlink:href="#shapes" style="clip-path: url(#textClip);"/>
  
   <use transform="translate(100, 0)"
  xlink:href="#shapes" clip-path="url(#textClip);"/>

image.png-66.6kB

或者說(shuō),如果我們想畫(huà)一個(gè)圓的裁剪區(qū)域的話:

<defs>
     <clipPath id="circularPath" clipPathUnits="objectBoundingBox">
     <circle cx="0.5" cy="0.5" r="0.5"/>
    </clipPath>
</defs>

<use xlink:href="#shapes" style="clip-path: url(#circularPath);" />

Appendix 參考標(biāo)簽

g

分組標(biāo)簽應(yīng)該毫無(wú)意外排第一,因?yàn)槠鋵?shí)作為繪制圖形中最常和最基本的標(biāo)簽。前面一篇文章也主要介紹過(guò)了,這里做點(diǎn)補(bǔ)充。

每一個(gè)分組標(biāo)簽都帶有 id 屬性,唯一標(biāo)識(shí)該分組,為什么呢?

因?yàn)?,后面我們可以使用?id 標(biāo)簽添加動(dòng)畫(huà),重用該分組等。

<g id="demo" stroke="green" fill="white" stroke-width="5">
     <circle cx="25" cy="25" r="15"/>
     <circle cx="40" cy="25" r="15"/>
     <circle cx="55" cy="25" r="15"/>
     <circle cx="70" cy="25" r="15"/>
   </g>

每個(gè)分組里面可以含有一些描述標(biāo)簽,比如 desc。 這些描述內(nèi)容是不會(huì)被渲染的。

<g id="demo" stroke="green" fill="white" stroke-width="5">
    <desc>Just Demo</desc>
     <circle cx="25" cy="25" r="15"/>
   </g>

use

該標(biāo)簽就是結(jié)合 g 標(biāo)簽一起使用,作用是可以復(fù)用 g 分組的樣式。

<g id="Port">
      <circle style="fill: inherit;" r="10"/>
</g>
<use x="50" y="30" xlink:href="#Port" class="classA"/>

里面使用 xlink:href 加上指定 group 的 id,然后通過(guò) x,y 屬性指定副本放置的位置。不過(guò),有一個(gè)限制,use 標(biāo)簽的 style 屬性,并不能覆蓋點(diǎn)原始的 group style 樣式。而且,有時(shí)候,我們只是想使用一些模板,即,圖形并未被解析,只有代碼存在。這時(shí)候,就需要使用 defs 來(lái)包裹了。

defs

用來(lái)保存一些代碼,使其不會(huì)被瀏覽器解析。并且里面的分組可以被 use 屬性的 style 樣式所覆蓋。

<defs>
    <g id="Port">
      <circle style="fill: inherit;" r="10"/>
    </g>
  </defs>

 <use x="50" y="50" xlink:href="#Port" style="fill: blue;"/>

symbol

該標(biāo)簽和 g 標(biāo)簽類似,也是用來(lái)進(jìn)行分組。不過(guò),它有個(gè)特點(diǎn),即,不會(huì)被瀏覽器所渲染。那它不和 defs 差不多嗎?

恩,確實(shí)。不過(guò),defs 是官方推薦,用來(lái)包裹一些模板 svg 代碼而創(chuàng)造出來(lái),用來(lái)增加可讀性的標(biāo)簽。而 symbol 是存粹的作為一個(gè)模板。它可以獨(dú)立于 svg 的 viewbox 來(lái)自定義子 viewbox 和 preserveAspectRatio。

<symbol id="sym01" viewBox="0 0 150 110">
  <circle cx="50" cy="50" r="40" stroke-width="8"
      stroke="red" fill="red"/>
  <circle cx="90" cy="60" r="40" stroke-width="8"
      stroke="green" fill="white"/>
</symbol>

<use href="#sym01"
     x="0" y="0" width="100" height="50"/>

同樣使用該模板,也是使用 use 標(biāo)簽來(lái)完成。

image

既然 use 可以重用 SVG 代碼,那么 SVG 里面能不能重用已經(jīng)畫(huà)好的 png/jpg 的圖片呢?

這時(shí)候,就需要用到 image 標(biāo)簽。其可以用來(lái)加載外部的 PNG, JPEG 圖片,注意,官方規(guī)定是前兩種,其它圖片支持不支持官方?jīng)]做答復(fù)。即,如果你使用 GIF 圖片,并不能保證所有的瀏覽器都能正常顯示。

<image xlink:href="kwanghwamun.jpg"
  x="72" y="92"
  width="160" height="120"/>
</svg>

同樣,該 image 標(biāo)簽也具有自定義 preserveAspectRatio 的效果。

  • x: 定義水平位置

  • y: 定義垂直位置

  • width: 圖片渲染的寬度,必須有。

  • height: 圖片渲染的高度,必須有。

  • preserveAspectRatio: 控制圖片的縮放

marker

marker 一般是用來(lái)畫(huà)箭頭或者線段始末的標(biāo)識(shí)圖形。

<defs>
    <marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5"
        markerWidth="6" markerHeight="6" orient="auto">
      <path d="M 0 0 L 10 5 L 0 10 z" />
    </marker>
  </defs>

  <polyline points="10,90 50,80 90,20" fill="none" stroke="black" 
      stroke-width="2" marker-end="url(#Triangle)" />

如圖:

image.png-1.5kB

這里我們只需要里了解即可,因?yàn)樵趯?shí)際畫(huà)的時(shí)候,直接使用相關(guān)工具生成更加方便。

a

這里的 a 標(biāo)簽和我們直接在 HTML 使用的超鏈接 a 標(biāo)簽類似。也是用來(lái)定義一個(gè)外鏈的。

<a xlink:href="https://developer.mozilla.org/en-US/docs/SVG"
      target="_blank">
    <rect height="30" width="120" y="0" x="0" rx="15"/>
    <text fill="white" text-anchor="middle" 
          y="21" x="60">SVG on MDN</text>
  </a>

個(gè)人公眾號(hào)

24

TL;DR

本文主要是講解關(guān)于 SVG 的一些高級(jí)動(dòng)畫(huà)特效,比如 SVG 動(dòng)畫(huà)標(biāo)簽,圖形漸變,路徑動(dòng)畫(huà),線條動(dòng)畫(huà),SVG 裁剪等。

例如:路徑動(dòng)畫(huà)

gif

圖形漸變:

fig

線條動(dòng)畫(huà):

test.gif-388.2kB

以及,相關(guān)的動(dòng)畫(huà)的矩陣知識(shí),這個(gè)也是現(xiàn)在 CSS 動(dòng)畫(huà)里面最重要,同時(shí)也是最為欠缺的知識(shí)點(diǎn):

image.png-7.1kB

文章會(huì)先從基本語(yǔ)法入手,然后,慢慢深入。介紹一些動(dòng)畫(huà)基本原理和對(duì)應(yīng)的數(shù)學(xué)原理知識(shí)點(diǎn)。并且文章后面,還附有相關(guān)語(yǔ)法的介紹,當(dāng)你在遇到不熟悉語(yǔ)法的時(shí)候可以參考參考。

前面一篇文章,主要介紹了一些 SVG 的基本概念和基本圖形。接下來(lái)我們需要了解一下,SVG 處理矢量這個(gè)特性之外,還有啥內(nèi)容吸引我們,能讓 SVG 現(xiàn)在普及度這么高?

原文參考:前端小吉米

SVG Animation

在 SVG 中,如果我們想實(shí)現(xiàn)一個(gè)動(dòng)畫(huà)效果,可以使用 CSS,JS,或者直接使用 SVG 中自帶的 animate 元素添加動(dòng)畫(huà)。

使用 CSS 的話,有兩種選擇一種是通過(guò) style 直接內(nèi)聯(lián)在里面,另外是直接使用相關(guān)的動(dòng)畫(huà)屬性-- transform。

 <use id="star" class="starStyle" xlink:href="#starDef"
       transform="translate(100, 100)"
       style="fill: #008000; stroke: #008000"/>

而使用 SVG 中自定的 animate 主要還是 SVG 自己的東西,比較好用。如果想用 CSS 的動(dòng)畫(huà),這都無(wú)所謂。

先看一個(gè) SVG animate DEMO:

<rect x="10" y="10" width="200" height="20" stroke="black" fill="none">
  <animate
    attributeName="width"
    attributeType="XML"
    from="200" to="20"
    begin="0s" dur="5s"
    fill="freeze" />
</rect>

通過(guò)將 animate 標(biāo)簽嵌套在指定的圖形里面,即可實(shí)現(xiàn)變換的效果。另外,還有 animateTransform,它主要是用來(lái)做變形動(dòng)畫(huà)的。

<rect x="-10" y="-10" width="20" height="20"
    style="fill: #ff9; stroke: black;">
    <animateTransform attributeType="XML"
      attributeName="transform" type="scale"
      from="1" to="4 2"
      begin="0s" dur="4s" fill="freeze"/>
</rect>

簡(jiǎn)單來(lái)說(shuō):

  • animate: 相當(dāng)于 CSS 中的 transition

  • animateTransform: 相當(dāng)于 CSS 中的 transform

里面一些技術(shù)細(xì)節(jié)我們這里就不過(guò)多講解了。這里,主要想介紹一下 animate 中的 morph 的效果。

animate morph

該效果主要做的就是圖形內(nèi)部的漸變。如圖:

fig

這種動(dòng)畫(huà)是怎么實(shí)現(xiàn)呢?

直接看代碼吧:

<path fill="#1EB287">
    <animate 
             attributeName="d" 
             dur="1440ms" 
             repeatCount="indefinite"
             keyTimes="0;
                       .0625;
                       .208333333;
                       .3125;
                       .395833333;
                       .645833333;
                       .833333333;
                       1"
             calcMode="spline" 
             keySplines="0,0,1,1;
                         .42,0,.58,1;
                         .42,0,1,1;
                         0,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1"
             values="M 0,0 
                     C 50,0 50,0 100,0
                     100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     Z;

                     M 0,0 
                     C 50,0 50,0 100,0
                     100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     Z;

                     M 50,0 
                     C 75,25 75,25 100,50 
                     75,75 75,75 50,100
                     25,75 25,75 0,50
                     25,25 25,25 50,0
                     Z;

                     M 25,50 
                     C 37.5,25 37.5,25 50,0 
                     75,50 75,50 100,100
                     50,100 50,100 0,100
                     12.5,75 12.5,75 25,50
                     Z;

                     M 25,50 
                     C 37.5,25 37.5,25 50,0 
                     75,50 75,50 100,100
                     50,100 50,100 0,100
                     12.5,75 12.5,75 25,50
                     Z;

                     M 50,0
                     C 77.6,0 100,22.4 100,50 
                     100,77.6 77.6,100 50,100
                     22.4,100, 0,77.6, 0,50
                     0,22.4, 22.4,0, 50,0
                     Z;
                     
                     M 50,0
                     C 77.6,0 100,22.4 100,50 
                     100,77.6 77.6,100 50,100
                     22.4,100, 0,77.6, 0,50
                     0,22.4, 22.4,0, 50,0
                     Z;
                     
                     M 100,0 
                     C 100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     50,0 50,0 100,0
                     Z;"/>
  </path>

這么多,是不是感覺(jué)有點(diǎn)懵逼。不過(guò),我們細(xì)分來(lái)看一下其實(shí)很簡(jiǎn)單。里面主要是利用 animate 中的 keyTimes,calcMode,keySplines,以及 values 這幾個(gè)屬性。不急,我們一個(gè)一個(gè)來(lái)解釋一下。

  • keyTimes: 這其實(shí)和 CSS 中定義的 @keyframes 一樣。通過(guò) 0-1 之間的值,定義每段動(dòng)畫(huà)完成的時(shí)間。格式為:value;value...。例如 0;.0625;.208333333;.3125;.395833333;.645833333;.833333333;1。從第一個(gè)動(dòng)畫(huà),到第二個(gè)動(dòng)畫(huà)經(jīng)歷的時(shí)間比例為 6.25%。并且,keyTimes 需要和 values 里面定義的幀數(shù)一致。

  • calcMode: 用來(lái)定義動(dòng)畫(huà)具體的插值模型。取值有: discrete | linear[default] | paced | spline。具體可以參考 MDN。這里我們主要介紹一下 spline。該值表示每個(gè)動(dòng)畫(huà)間使用自定的貝塞爾變換曲線。如果沒(méi)有特殊要求,使用 linear 其實(shí)已經(jīng)足夠了,這樣就不用麻煩去定義下面的 keySplines 屬性。

  • keySplines:該值用來(lái)具體定義動(dòng)畫(huà)執(zhí)行時(shí)的 貝塞爾曲線。使用格式是通過(guò) ; 來(lái)分隔每一個(gè)值。即,cubic-bezier(.31,.57,.93,.46) 為一組。使用 keySplines 表達(dá),則為:keySplines = ".31,.57,.93,.46;"。當(dāng)然,里面的貝塞爾曲線組數(shù)為 整個(gè)動(dòng)畫(huà)幀數(shù) - 1。

而 values 就很簡(jiǎn)單了。它是直接結(jié)合 attributeName 屬性,來(lái)設(shè)置具體的值,每個(gè)值之間使用 ; 進(jìn)行分隔。

像上面那樣,可以在指定元素里面嵌套多個(gè) animate,既實(shí)現(xiàn)了形狀的改變,又實(shí)現(xiàn)了顏色的改變。Morph 比較常用于數(shù)字的更迭,比如,倒數(shù) 10s 的相關(guān)動(dòng)畫(huà)。到這里,Morpah 相關(guān)的知識(shí)點(diǎn)就結(jié)束了。

接著,讓我們來(lái)看一下 SVG 中,另外一非常重要的標(biāo)簽 -- animateMotion。

該標(biāo)簽可以讓指定的元素,繞著指定的路徑進(jìn)行運(yùn)動(dòng)。所以這對(duì)于復(fù)雜的路徑來(lái)說(shuō)非常有用,因?yàn)槲覀兒茈y使用 transform 去模擬復(fù)雜的變換路徑??匆粋€(gè) DEMO

gif

animateMotion

animateMotion 大致的屬性和 animate 差不多,不過(guò),它還擁有自己特有的屬性,比如 keyPoints、rotate、path 等。不過(guò),calcMode 在 AM(animateMotion) 中的默認(rèn)屬性由,linear 變?yōu)?paced

這些屬性,我們慢慢介紹,先從最簡(jiǎn)單的開(kāi)始吧。首先,我們來(lái)看一個(gè) DEMO:

<g>
  <rect x="0" y="0" width="30" height="30" style="fill: #ccc;"/>
  <circle cx="30" cy="30" r="15" style="fill: #cfc; stroke: green;"/>
  <animateMotion from="0,0" to="60,30" dur="4s" fill="freeze"/>
</g>
  • from,to:指定兩點(diǎn)的位置,位置參數(shù)是以元素的坐標(biāo)為原點(diǎn)的。

  • dur:執(zhí)行渲染時(shí)間

  • fill:指定動(dòng)畫(huà)結(jié)束后停留的裝填。有 freezeremove 效果。remove 表示回到動(dòng)畫(huà)開(kāi)始的位置,freeze 表示停留在動(dòng)畫(huà)結(jié)束的位置。

如果,你想要更復(fù)雜的路徑,可以直接使用 path 屬性來(lái)指定路徑。用法和 path 標(biāo)簽中 d 屬性是一樣的。

<rect x="0" y="0" width="30" height="30" style="fill: #ccc;">
    <animateMotion
    path="M50,125 C 100,25 150,225, 200, 125"
    dur="6s" fill="freeze"/>
</rect>

或者使用 mpath 標(biāo)簽,引用外部的 path。

  <path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110"
      stroke="lightgrey" stroke-width="2" 
      fill="none" id="theMotionPath"/>
  <circle cx="10" cy="110" r="3" fill="lightgrey"  />
  <circle cx="110" cy="10" r="3" fill="lightgrey"  />

  <!-- Red circle which will be moved along the motion path. -->
  <circle cx="" cy="" r="5" fill="red">

    <!-- Define the motion path animation -->
    <animateMotion dur="6s" repeatCount="indefinite">
      <mpath xlink:href="#theMotionPath"/>
    </animateMotion>
  </circle>

動(dòng)畫(huà)效果為:

tuiche.gif-17.8kB

所以,一般而言我們?cè)诙x AM 的路徑的時(shí)候,只用一種方式定義即可,否則會(huì)發(fā)生相應(yīng)的覆蓋:mpath>path>values>from/to。

在 AM 運(yùn)動(dòng)中,還有一個(gè)很重要的概念就是旋轉(zhuǎn)角。默認(rèn)情況下,運(yùn)動(dòng)物體的角度是按照它和坐標(biāo)軸的初始角度確定的。例如:

test.gif-23.7kB

這樣看起來(lái)確實(shí)有些別扭,那能不能讓物體垂直于路徑進(jìn)行運(yùn)動(dòng)呢?

有的,根據(jù) rotate 屬性值,一共有 3 個(gè)值可供選擇。

  • auto:讓物體垂直于路徑的切線方向運(yùn)動(dòng)。不過(guò),如果你的路徑是閉合曲線的話,需要注意起始點(diǎn)的位置。

例如:

test.gif-22.4kB

  • auto-reverse:讓物體垂直于路徑的切線方向并 + 180°。也就是和 auto 運(yùn)動(dòng)關(guān)于切線對(duì)稱。

test.gif-27.8kB

  • Number:讓物體以固定的旋轉(zhuǎn)角度運(yùn)動(dòng)。這個(gè)就相當(dāng)于使用 transform:rotate(deg) 進(jìn)行控制。

在動(dòng)畫(huà)設(shè)置標(biāo)簽中,還有一個(gè)更簡(jiǎn)單的--set

set

該標(biāo)簽也是用來(lái)模擬 transition 效果的。它和 animate 的主要區(qū)別是,它僅僅需要 to 的指定屬性,而不需要其他的參考屬性,比如 from,by 等。那它有啥特別的存在意義嗎?

有的,因?yàn)?set 針對(duì)于所有屬性,甚至包括 style 里面的相關(guān) CSS 屬性。所以,可以靠它來(lái)很好描述一些非 number 的屬性值。

<text text-anchor="middle" x="60" y="60" style="visibility: hidden;">
  <set attributeName="visibility" attributeType="CSS"
    to="visible" begin="4.5s" dur="1s" fill="freeze"/>
  All gone!
</text>

矩陣動(dòng)畫(huà)

上面差不多簡(jiǎn)單闡述了關(guān)于 SVG 一些比較有特點(diǎn)的動(dòng)畫(huà)。當(dāng)然,還有比較重要的線條動(dòng)畫(huà),這個(gè)我們放到后面進(jìn)行講解。這里先來(lái)看一下所有動(dòng)畫(huà)中,非常重要的矩陣原理。線性代數(shù)應(yīng)該是大學(xué)里面來(lái)說(shuō),最容易學(xué)的一門科目,MD。。。還記得,大學(xué)線代期末考試的時(shí)候,100 分的同學(xué)應(yīng)該說(shuō)是如韭菜地般,一抓一大片(對(duì)不起,我沒(méi)能和他們同流合污。)

那矩陣是如何在動(dòng)畫(huà)中使用的呢?

簡(jiǎn)單的說(shuō),矩陣中的每個(gè)元素其實(shí)可以等價(jià)代換為每個(gè)因式里面的系數(shù):

image.png-6kB

上面也叫作 三維矩陣。即,它涉及到 x,y,z 軸的計(jì)算。那對(duì)于我們平面 2D 變換來(lái)說(shuō),那么此時(shí)矩陣又是哪種形式呢?

很簡(jiǎn)單,只要將 z 軸永遠(yuǎn)置為一個(gè)常數(shù)就 OK。這里,慣例上是直接取 0 0 1 來(lái)設(shè)置。

image.png-1.7kB

不信的話,大家只要代進(jìn)去乘以乘,應(yīng)該就可以得到結(jié)果了。所以,在二維中,具體變換方式為:

image.png-7.1kB

后面,我們也會(huì)依據(jù)這個(gè)公式進(jìn)行相關(guān)的變形操作。那矩陣變換是怎么運(yùn)用到 CSS/SVG 當(dāng)中呢?

在 CSS 中,是直接使用 transform 中的屬性:

transform: matrix(a,b,c,d,e,f);

當(dāng)然,在 SVG 中也是一樣的:

<g transform="matrix(1,2,3,4,5,6)">
    <line x1="10" y1="20" x2="30" y2="40" style="stroke-width: 10px; stroke: blue;"/>
  </g>

所以,我們主要的重點(diǎn)就是講解一下 matrix 這個(gè)屬性。它的格式為:

matrix(a,b,c,d,e,f);

對(duì)應(yīng)于我們上面的公式有:

image.png-2.5kB

在接觸 transform 的時(shí)候,大家應(yīng)該了解到 transform 里面有很多固定的動(dòng)畫(huà)屬性:

  • translate()

  • rotate()

  • scale()

  • skew()

實(shí)際上,在底層還是使用 matrix 實(shí)現(xiàn)的變換。就拿 translate 舉個(gè)例子吧。

translate 的格式為:

translate(dx,dy)

相當(dāng)于參考當(dāng)前原點(diǎn),在 x/y 軸上移動(dòng) dx/dy 的距離。那么映射到矩陣,應(yīng)該如何表示呢?

很簡(jiǎn)單,它等同于:

matrix(1 0 0 1 dx dy);

使用代數(shù)證明一下:

假設(shè)有 matrix(1 0 0 1 20 30)

變?yōu)榫仃嚍椋?/p>

image.png-3.2kB

根據(jù),上面的表達(dá)式有:

X = x'*1 + y'*0 + 20 = x' + 20
Y = x'*0 + y'*0 + 30 = y' + 30

所以,就是 X 在原有 X 軸坐標(biāo)上向右移動(dòng) 20 的距離,Y 相對(duì)于原有移動(dòng) 30 的距離。

那么其他幾個(gè)屬性呢?也是怎么變化的嗎?

恩,類似。只是里面取值不一樣:

  • scale(x,y): 放大 X/Y 軸,矩陣的表達(dá)為 matrix(x 0 0 y 0 0)。

  • rotate(θ): 坐標(biāo)旋轉(zhuǎn),矩陣的表達(dá)為 matrix(cosθ sinθ -sinθ cosθ 0 0)。

  • skew(θx,θy): X/Y 軸拉伸,矩陣的表達(dá)為 matrix(1 tanθx tanθy 1 0 0)。

注意,上面三個(gè)都會(huì)改變?cè)形矬w的坐標(biāo)系?。?! 這點(diǎn)很重要,換句話說(shuō),后面每次變換都是基于前面一個(gè)的變換結(jié)果的。

詳情看圖:

image.png-49.6kB

詳情可以參考:MDN matrix

不過(guò),這并不是我們使用 matrix 的重點(diǎn),也不是它的優(yōu)勢(shì)。它的優(yōu)勢(shì)在于可計(jì)算,即,能夠?qū)?fù)雜的動(dòng)畫(huà)集合到一個(gè)表達(dá)式中,并且,后續(xù)的變換可以直接基于當(dāng)前的 matrix。

我們先來(lái)了解一下,如果多個(gè)變換動(dòng)畫(huà)一起使用,matrix 應(yīng)該如何表達(dá)呢?

只需要找到我們變換動(dòng)畫(huà)對(duì)應(yīng)的矩陣,然后相乘即可。例如,先旋轉(zhuǎn) 45°,然后放大 1.5 倍,則有變換動(dòng)畫(huà)為:

transform: rotate(45deg) scale(1.5,1.5);

注意,雖然,你定義動(dòng)畫(huà)是分開(kāi)的,但此時(shí)的動(dòng)畫(huà)是同時(shí)進(jìn)行的。為啥?因?yàn)?,這兩個(gè)動(dòng)畫(huà)實(shí)際上可以整合成為一個(gè)變換矩陣:

image.png-7.4kB

并且,位置是不可以調(diào)換的。比如,transform: scale(2,2) translate(20px,30px)。即,你先放大兩倍,然后移動(dòng) 20,30 的距離。注意,這里移動(dòng)的 20,30 相對(duì)的是已經(jīng)放大過(guò)后的坐標(biāo),相對(duì)于原坐標(biāo)而言就是 40,60 了。 如果,你調(diào)換位置,即 transform: translate(20px,30px) scale(2,2)。就變成現(xiàn)在原坐標(biāo)移動(dòng) 20,30,然后再放大兩倍。

而上面強(qiáng)調(diào)的順序關(guān)系,實(shí)際上就可以理解為矩陣不滿足交換律的原則。因?yàn)橐坏┙粨Q,結(jié)果很可能不一樣。

矩陣高級(jí)用法

上面的內(nèi)容只是簡(jiǎn)單的描述了關(guān)于矩陣的概念。在實(shí)際中,矩陣可以說(shuō)是真正利器。

假設(shè)現(xiàn)在有一個(gè)動(dòng)畫(huà),要求你將一個(gè)物體從一個(gè)點(diǎn)通過(guò)拋物線的方式移動(dòng)到另外一個(gè)點(diǎn),那么此時(shí)要求 JS/CSS 隨你挑。此時(shí),你會(huì)不會(huì)感覺(jué),呼吸急促,頭腦發(fā)熱呢?

恩,matrix 可以治,而且包治百病。不過(guò),matrix 有一個(gè)限制點(diǎn),它只能用于一次線性動(dòng)畫(huà)表達(dá)式。即,針對(duì)于拋物線,橢圓曲線這類復(fù)雜曲線來(lái)說(shuō),不太合適。那么有什么辦法嗎?

有的,微分思想。每一段動(dòng)畫(huà)其實(shí)都可以通過(guò)一定范圍內(nèi)的直線拼接而成,那么這樣,我們就可以將一段拋物線拆分為由幾段線段構(gòu)成的曲線。當(dāng)然,如果你分的越細(xì),擬合度就越高。這里我們不打算過(guò)度你和,我們簡(jiǎn)單的將一段拋物線分為 5段。

如圖:

image.png-12.9kB

那么接下來(lái)就是摳細(xì)節(jié)。這里,依次取傾角為 45°,30°,0°,-45°,-30° 這 5 段直線。每段分配的時(shí)間比例為 20%、25%、10%、25%、20% 這主要是用于 keyframe 的設(shè)定?,F(xiàn)在,用數(shù)學(xué)來(lái)分析一下,這個(gè)動(dòng)畫(huà)到底該怎么弄。

現(xiàn)在,已知兩點(diǎn)之間的距離為 100px。那么我們同樣根據(jù)上述比例分,則有 20px, 25px, 10px, 25px, 20px。

這里我們以 45° 傾角為參考點(diǎn),則終點(diǎn)坐標(biāo)為 (20,20); 。那么,該段的矩陣為:

// 注意 Y 軸需要取負(fù)值!

 1 0 20
 0 1 -20
 0 0 1

CSS 中的變形動(dòng)畫(huà)為:

transform: matrix(1,0,0,1,20,-20);

然后,第二段就為:

1 0 25
0 1 -14.4
0 0 1

使用矩陣的乘法法,則有:

 1 0 45
 0 1 -34.4
 0 0 1

變形動(dòng)畫(huà)為:

transform: matrix(1,0,0,1,45,-34.4);

剩余幾段也是這樣的做法。最終,整個(gè) keyframe 就應(yīng)該表示為:

@keyframe Parabola{
    20%{
        transform: matrix(1,0,0,1,20,-20);
    }
    45%{
        transform: matrix(1,0,0,1,45,-34.4);
    }
    ...
}

整個(gè)動(dòng)畫(huà)過(guò)程差不多都是這樣。當(dāng)然,矩陣也不僅僅局限于這幾個(gè)動(dòng)畫(huà),憑借著高度定制化和靈活性的特點(diǎn),這它還常常用于進(jìn)行回彈,彈跳等動(dòng)畫(huà)中。如果大家有興趣,后期也可以對(duì)這類動(dòng)畫(huà)進(jìn)行簡(jiǎn)單的講解。

后面,我們最后來(lái)了解一下 SVG 中很重要的線條動(dòng)畫(huà)。

線條動(dòng)畫(huà)

SVG 中的線條動(dòng)畫(huà)常常用作過(guò)渡屏(splash screen)中。例如:

test.gif-35.7kB

或者,一些比較炫酷的 LOGO 中,比如 AllowTeam 的:

AT

看到這些炫酷的效果,大家有沒(méi)有動(dòng)心想學(xué)一學(xué),看看自己到底能否做的這么好呢?

OK,我們現(xiàn)在來(lái)正式介紹一下線條動(dòng)畫(huà)。在 SVG 中,最長(zhǎng)用到的線條標(biāo)簽就是 Path。這里我前面一篇文章已經(jīng)做了介紹,我這里就不贅述了。

而在具體變化當(dāng)中用到的是關(guān)于 stroke 的相關(guān)屬性:(下面的屬性都可以直接用在 CSS 當(dāng)中!)

  • stroke*:定義筆觸的顏色。例如:stroke="green"

  • stroke-dasharray*:定義 dash 和 gap 的長(zhǎng)度。它主要是通過(guò)使用 , 來(lái)分隔 實(shí)線間隔 的值。例如:stroke-dasharray="5, 5" 表示,按照 實(shí)線為 5,間隔為 5 的排布重復(fù)下去。如下圖:

image.png-0.2kB

放大看有:

image.png-5.3kB

另外,stroke-dasharray 并不局限于只能設(shè)置兩個(gè)值,要知道,它本身的含義是設(shè)置最小重復(fù)單元,即,dash,gap,dash,gap...。比如,我定義 stroke-dasharray="15, 10, 5" 則相當(dāng)于,[15,10,5] 為一段。則有:

image.png-0.3kB

放大看則有:

image.png-7.7kB

  • stroke-dashoffset*: 用來(lái)設(shè)置 dasharray 定義其實(shí) dash 線條開(kāi)始的位置。值可以為 number || percentage。百分?jǐn)?shù)是相對(duì)于 SVG 的 viewport。通常結(jié)合 dasharray 可以實(shí)現(xiàn)線條的運(yùn)動(dòng)。

  • stroke-linecap: 線條的端點(diǎn)樣式。

  • stroke-linejoin: 線條連接的樣式

  • stroke-miterlimit: 一個(gè)比較復(fù)雜的概念,如果我們只是畫(huà)一些一般的線段,使用上面 linejoin 即可。如果涉及對(duì)邊角要求比較高的,則可以使用該屬性進(jìn)行定義。它的值,其實(shí)就是角長(zhǎng)度比上線寬:

image.png-5.4kB

而實(shí)際理解的話,就是假設(shè)當(dāng) width 為 1。此時(shí)比例為 2。那么 miter = 2。那么超過(guò) 2 的 miter 部分則會(huì)被 cut 掉??梢詤⒄眨?/p>

image.png-15.6kB

他主要是配合 linejoin 一起使用。因?yàn)?linejoin 默認(rèn)取值就是 miter。所以,默認(rèn)情況下就可以使用該標(biāo)簽屬性。它默認(rèn)值為 4。其余的大家下去實(shí)踐一下即可。詳細(xì)可以參考: miter

  • stroke-opacity:線段的透明度

  • stroke-width:線的粗細(xì)。

OK,介紹完關(guān)于 path 的所有 stroke 屬性之后,我們就要開(kāi)始動(dòng)手寫(xiě)一下讓線條動(dòng)起來(lái)的代碼。簡(jiǎn)單來(lái)說(shuō),就是通過(guò) stroke-dashoffsetstroke-dasharray 來(lái)做。整個(gè)動(dòng)畫(huà)可以分為兩個(gè)過(guò)程:

  • 通過(guò) dasharray 將實(shí)線部分隱藏,空余為全線段長(zhǎng)。然后,將實(shí)線部分增加至全長(zhǎng)。比如:dasharray: 0,1000 變?yōu)?dasharray: 1000,1000。

  • 同時(shí),通過(guò) dashoffset 來(lái)移動(dòng)新增的實(shí)線部分,造成線段移動(dòng)的效果。有: dashoffset:0,變?yōu)?dashoffset:1000。

不過(guò),這里我們不打算使用 Path 來(lái)做啥復(fù)雜的動(dòng)畫(huà),這主要考慮到手頭沒(méi)有一些 SVG 生成工具。所以,這里我們就以 Text 來(lái)做吧(因?yàn)樽銎饋?lái)真的簡(jiǎn)單)。

這里,先以 IV-WEB 這段文字來(lái)做動(dòng)畫(huà)。

先給大家看一下最終結(jié)果:

test.gif-61.9kB

那么這種動(dòng)畫(huà)是怎么做的呢?

這里,我主要介紹一下關(guān)于 CSS 相關(guān),SVG 就一個(gè) Text 我直接貼代碼了:

<svg viewBox="0 0 1320 300">

  <!-- Symbol -->
  <symbol id="s-text">
    <text text-anchor="middle"
          x="50%" y="50%" dy=".35em">
      IV-WEB
    </text>
  </symbol>  

  <!-- Duplicate symbols -->
  <use xlink:href="#s-text" class="text"
       ></use>
  <use xlink:href="#s-text" class="text"
       ></use>
  <use xlink:href="#s-text" class="text"
       ></use>
 

</svg>

上面是通過(guò)創(chuàng)建一個(gè)居中定位的字體,然后使用 3 個(gè) text 重疊。具體 CSS 我們下面來(lái)說(shuō)一下。首先,我們營(yíng)造的效果是從無(wú)到有,就需要使用 dasharray 將 gap 設(shè)置的足夠大。這里我取 300 即可。

stroke-dasharray: 0 300;

然后,通過(guò) nth-child 選擇器,給每一個(gè)文字使用不同的顏色值:

.text:nth-child(3n + 1) {
  stroke: #F60A0A;
}
.text:nth-child(3n + 2) {
  stroke: #F2FF14;
}

.text:nth-child(3n + 3) {
  stroke: #FB9505;
}

下面才是重點(diǎn)內(nèi)容。此時(shí),這 3 個(gè) text 的起始點(diǎn)重合。我現(xiàn)在既要他們?cè)谶\(yùn)行時(shí)不完全重合,又要他們的線條能進(jìn)行滾動(dòng)。不啰嗦了,直接看代碼吧:

@keyframes stroke {
  100% {
    stroke-dashoffset: 1000;
    stroke-dasharray: 80 160;
  }
}

@keyframes stroke1 {
  100% {
    stroke-dashoffset: 1080;
    stroke-dasharray: 80 160;
  }
}


@keyframes stroke2 {
  100% {
    stroke-dashoffset: 1160;
    stroke-dasharray: 80 160;
  }
}

這就是上面 3 個(gè)不同的 text 運(yùn)用的動(dòng)畫(huà)。dashoffet 由 0 到 1000。這完成了滾動(dòng)的目的。同時(shí),為了讓字體不重合,我還需要在對(duì)應(yīng)字體的 dashoffset 上,加上不同的間隔距離。比如,第一個(gè)字體 offset 為 1000。那么第二個(gè)字體,我需要加上前一個(gè)字體 dash 的長(zhǎng)度,即,80。所以,第二個(gè)字體就變?yōu)?1080。那么第三個(gè)就是加上前兩個(gè)的 dash 長(zhǎng)度,即 1160。

大致過(guò)程就是這樣,詳情可以查看: IVWEB 線條動(dòng)畫(huà)

這里再給大家布置一個(gè)練習(xí)作業(yè),如何實(shí)現(xiàn)無(wú)線連續(xù)的分段動(dòng)畫(huà)呢?

具體效果如圖:

test.gif-388.2kB

給點(diǎn)提示:

將多個(gè)文字重疊,取不同的 offset 和 array 即可。動(dòng)畫(huà)的終止位置一般取一個(gè) gap + dash 的周期長(zhǎng)即可。

后面看看這篇文章反響如何,到時(shí)候再?zèng)Q定是否再寫(xiě)一篇續(xù)集,介紹該作業(yè)的原理。

SVG 文字

在 SVG 中定義文字直接使用 text 標(biāo)簽即可。關(guān)于文字來(lái)說(shuō),一般而言需要注意的點(diǎn)就那么即可,文字的排列,間距等等。這些都可以直接使用 CSS 進(jìn)行控制。不過(guò),有幾個(gè)屬性比較特殊,這里需要額外提一下。

text-anchor

用來(lái)定義參考點(diǎn)和實(shí)際字符之間的定位關(guān)系。格式為:

  • text-anchor: start | middle | end | inherit

直接看代碼解釋吧:

<!-- Anchors in action -->
    <text text-anchor="start"
          x="60" y="40">A</text>

    <text text-anchor="middle"
          x="60" y="75">A</text>

    <text text-anchor="end"
          x="60" y="110">A</text>

第一個(gè) A,參考的是 (60,40) 的點(diǎn),定義為 start ,那么參考點(diǎn)應(yīng)該在字符的前面。

image.png-1kB

而剩下兩個(gè)也是同樣的道理:

image.png-1.9kB

tspan

現(xiàn)在,假如我們想在 text 里面添加一些特殊的字符效果,比如斜體,加粗等。由于,text 標(biāo)簽不能實(shí)現(xiàn)嵌套,所以,為了解決這個(gè)痛點(diǎn),提出了 tspan 的標(biāo)簽。它其實(shí)就是一個(gè)可以嵌套的 text 標(biāo)簽。

<text x="10" y="30" style="font-size:12pt;">
  Switch among
  <tspan style="font-style:italic">italic</tspan>, normal,
  and <tspan style="font-weight:bold">bold</tspan> text.
</text>

tspan 里面同樣可以自定義相關(guān)的自身屬性。詳細(xì)的可以參考 tspan 我這里就不詳述了。

在 Path 展示 text

Text 一般可以橫放,豎放。那有沒(méi)有啥辦法讓文字可以按照一定的路徑任意排放呢?

有的,這里可以使用 textPath 標(biāo)簽,來(lái)定義具體參考路徑。

<path id="sharp-corner"
    d="M 30 110 100 110 100 160"
    style="stroke: gray; fill: none;"/>

<text>
    <textPath xlink:href="#sharp-corner">
    Making a quick turn
    </textPath>
</text>

如圖:

image.png-11kB

具體細(xì)節(jié)我這里就不多說(shuō)了。

Clip

在 DOM 中如果想展示一個(gè)圖片的部分,或者以某種形狀展示圖片的部分,一般是通過(guò)一個(gè) cover div 來(lái)實(shí)現(xiàn)的。不過(guò),如果涉及到不規(guī)則圖形的話,那么 DOM 就有天生缺陷了(當(dāng)然使用 CSS 里的 clip-path 可以完成,不過(guò)兼容性不太好)。而在 SVG 中,提供了 clipPath 標(biāo)簽,能夠讓我們自定義裁剪圖片的范圍和形狀。

clipPath 里面可以接任何圖形,比如,path,rect 甚至是 text。使用的時(shí)候,直接在 style 中,指定 clip-path 即可,或者直接使用 clip-path 屬性指定。

<defs>
  <clipPath id="textClip">
    <text id="text1" x="20" y="20" transform="rotate(60)"
      style="font-family: 'Liberation Sans';
        font-size: 48pt; stroke: black; fill: none;">
CLIP
    </text>
  </clipPath>
 </defs>
 
 <use transform="translate(100, 0)"
  xlink:href="#shapes" style="clip-path: url(#textClip);"/>
  
   <use transform="translate(100, 0)"
  xlink:href="#shapes" clip-path="url(#textClip);"/>

image.png-66.6kB

或者說(shuō),如果我們想畫(huà)一個(gè)圓的裁剪區(qū)域的話:

<defs>
     <clipPath id="circularPath" clipPathUnits="objectBoundingBox">
     <circle cx="0.5" cy="0.5" r="0.5"/>
    </clipPath>
</defs>

<use xlink:href="#shapes" style="clip-path: url(#circularPath);" />

Appendix 參考標(biāo)簽

g

分組標(biāo)簽應(yīng)該毫無(wú)意外排第一,因?yàn)槠鋵?shí)作為繪制圖形中最常和最基本的標(biāo)簽。前面一篇文章也主要介紹過(guò)了,這里做點(diǎn)補(bǔ)充。

每一個(gè)分組標(biāo)簽都帶有 id 屬性,唯一標(biāo)識(shí)該分組,為什么呢?

因?yàn)?,后面我們可以使用?id 標(biāo)簽添加動(dòng)畫(huà),重用該分組等。

<g id="demo" stroke="green" fill="white" stroke-width="5">
     <circle cx="25" cy="25" r="15"/>
     <circle cx="40" cy="25" r="15"/>
     <circle cx="55" cy="25" r="15"/>
     <circle cx="70" cy="25" r="15"/>
   </g>

每個(gè)分組里面可以含有一些描述標(biāo)簽,比如 desc。 這些描述內(nèi)容是不會(huì)被渲染的。

<g id="demo" stroke="green" fill="white" stroke-width="5">
    <desc>Just Demo</desc>
     <circle cx="25" cy="25" r="15"/>
   </g>

use

該標(biāo)簽就是結(jié)合 g 標(biāo)簽一起使用,作用是可以復(fù)用 g 分組的樣式。

<g id="Port">
      <circle style="fill: inherit;" r="10"/>
</g>
<use x="50" y="30" xlink:href="#Port" class="classA"/>

里面使用 xlink:href 加上指定 group 的 id,然后通過(guò) x,y 屬性指定副本放置的位置。不過(guò),有一個(gè)限制,use 標(biāo)簽的 style 屬性,并不能覆蓋點(diǎn)原始的 group style 樣式。而且,有時(shí)候,我們只是想使用一些模板,即,圖形并未被解析,只有代碼存在。這時(shí)候,就需要使用 defs 來(lái)包裹了。

defs

用來(lái)保存一些代碼,使其不會(huì)被瀏覽器解析。并且里面的分組可以被 use 屬性的 style 樣式所覆蓋。

<defs>
    <g id="Port">
      <circle style="fill: inherit;" r="10"/>
    </g>
  </defs>

 <use x="50" y="50" xlink:href="#Port" style="fill: blue;"/>

symbol

該標(biāo)簽和 g 標(biāo)簽類似,也是用來(lái)進(jìn)行分組。不過(guò),它有個(gè)特點(diǎn),即,不會(huì)被瀏覽器所渲染。那它不和 defs 差不多嗎?

恩,確實(shí)。不過(guò),defs 是官方推薦,用來(lái)包裹一些模板 svg 代碼而創(chuàng)造出來(lái),用來(lái)增加可讀性的標(biāo)簽。而 symbol 是存粹的作為一個(gè)模板。它可以獨(dú)立于 svg 的 viewbox 來(lái)自定義子 viewbox 和 preserveAspectRatio。

<symbol id="sym01" viewBox="0 0 150 110">
  <circle cx="50" cy="50" r="40" stroke-width="8"
      stroke="red" fill="red"/>
  <circle cx="90" cy="60" r="40" stroke-width="8"
      stroke="green" fill="white"/>
</symbol>

<use href="#sym01"
     x="0" y="0" width="100" height="50"/>

同樣使用該模板,也是使用 use 標(biāo)簽來(lái)完成。

image

既然 use 可以重用 SVG 代碼,那么 SVG 里面能不能重用已經(jīng)畫(huà)好的 png/jpg 的圖片呢?

這時(shí)候,就需要用到 image 標(biāo)簽。其可以用來(lái)加載外部的 PNG, JPEG 圖片,注意,官方規(guī)定是前兩種,其它圖片支持不支持官方?jīng)]做答復(fù)。即,如果你使用 GIF 圖片,并不能保證所有的瀏覽器都能正常顯示。

<image xlink:href="kwanghwamun.jpg"
  x="72" y="92"
  width="160" height="120"/>
</svg>

同樣,該 image 標(biāo)簽也具有自定義 preserveAspectRatio 的效果。

  • x: 定義水平位置

  • y: 定義垂直位置

  • width: 圖片渲染的寬度,必須有。

  • height: 圖片渲染的高度,必須有。

  • preserveAspectRatio: 控制圖片的縮放

marker

marker 一般是用來(lái)畫(huà)箭頭或者線段始末的標(biāo)識(shí)圖形。

<defs>
    <marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5"
        markerWidth="6" markerHeight="6" orient="auto">
      <path d="M 0 0 L 10 5 L 0 10 z" />
    </marker>
  </defs>

  <polyline points="10,90 50,80 90,20" fill="none" stroke="black" 
      stroke-width="2" marker-end="url(#Triangle)" />

如圖:

image.png-1.5kB

這里我們只需要里了解即可,因?yàn)樵趯?shí)際畫(huà)的時(shí)候,直接使用相關(guān)工具生成更加方便。

a

這里的 a 標(biāo)簽和我們直接在 HTML 使用的超鏈接 a 標(biāo)簽類似。也是用來(lái)定義一個(gè)外鏈的。

<a xlink:href="https://developer.mozilla.org/en-US/docs/SVG"
      target="_blank">
    <rect height="30" width="120" y="0" x="0" rx="15"/>
    <text fill="white" text-anchor="middle" 
          y="21" x="60">SVG on MDN</text>
  </a>


TL;DR

本文主要是講解關(guān)于 SVG 的一些高級(jí)動(dòng)畫(huà)特效,比如 SVG 動(dòng)畫(huà)標(biāo)簽,圖形漸變,路徑動(dòng)畫(huà),線條動(dòng)畫(huà),SVG 裁剪等。

例如:路徑動(dòng)畫(huà)

gif

圖形漸變:

fig

線條動(dòng)畫(huà):

test.gif-388.2kB

以及,相關(guān)的動(dòng)畫(huà)的矩陣知識(shí),這個(gè)也是現(xiàn)在 CSS 動(dòng)畫(huà)里面最重要,同時(shí)也是最為欠缺的知識(shí)點(diǎn):

image.png-7.1kB

文章會(huì)先從基本語(yǔ)法入手,然后,慢慢深入。介紹一些動(dòng)畫(huà)基本原理和對(duì)應(yīng)的數(shù)學(xué)原理知識(shí)點(diǎn)。并且文章后面,還附有相關(guān)語(yǔ)法的介紹,當(dāng)你在遇到不熟悉語(yǔ)法的時(shí)候可以參考參考。

前面一篇文章,主要介紹了一些 SVG 的基本概念和基本圖形。接下來(lái)我們需要了解一下,SVG 處理矢量這個(gè)特性之外,還有啥內(nèi)容吸引我們,能讓 SVG 現(xiàn)在普及度這么高?

原文參考:前端小吉米

SVG Animation

在 SVG 中,如果我們想實(shí)現(xiàn)一個(gè)動(dòng)畫(huà)效果,可以使用 CSS,JS,或者直接使用 SVG 中自帶的 animate 元素添加動(dòng)畫(huà)。

使用 CSS 的話,有兩種選擇一種是通過(guò) style 直接內(nèi)聯(lián)在里面,另外是直接使用相關(guān)的動(dòng)畫(huà)屬性-- transform

 <use id="star" class="starStyle" xlink:href="#starDef"
       transform="translate(100, 100)"
       style="fill: #008000; stroke: #008000"/>

而使用 SVG 中自定的 animate 主要還是 SVG 自己的東西,比較好用。如果想用 CSS 的動(dòng)畫(huà),這都無(wú)所謂。

先看一個(gè) SVG animate DEMO:

<rect x="10" y="10" width="200" height="20" stroke="black" fill="none">
  <animate
    attributeName="width"
    attributeType="XML"
    from="200" to="20"
    begin="0s" dur="5s"
    fill="freeze" />
</rect>

通過(guò)將 animate 標(biāo)簽嵌套在指定的圖形里面,即可實(shí)現(xiàn)變換的效果。另外,還有 animateTransform,它主要是用來(lái)做變形動(dòng)畫(huà)的。

<rect x="-10" y="-10" width="20" height="20"
    style="fill: #ff9; stroke: black;">
    <animateTransform attributeType="XML"
      attributeName="transform" type="scale"
      from="1" to="4 2"
      begin="0s" dur="4s" fill="freeze"/>
</rect>

簡(jiǎn)單來(lái)說(shuō):

  • animate: 相當(dāng)于 CSS 中的 transition

  • animateTransform: 相當(dāng)于 CSS 中的 transform

里面一些技術(shù)細(xì)節(jié)我們這里就不過(guò)多講解了。這里,主要想介紹一下 animate 中的 morph 的效果。

animate morph

該效果主要做的就是圖形內(nèi)部的漸變。如圖:

fig

這種動(dòng)畫(huà)是怎么實(shí)現(xiàn)呢?

直接看代碼吧:

<path fill="#1EB287">
    <animate 
             attributeName="d" 
             dur="1440ms" 
             repeatCount="indefinite"
             keyTimes="0;
                       .0625;
                       .208333333;
                       .3125;
                       .395833333;
                       .645833333;
                       .833333333;
                       1"
             calcMode="spline" 
             keySplines="0,0,1,1;
                         .42,0,.58,1;
                         .42,0,1,1;
                         0,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1;
                         .42,0,.58,1"
             values="M 0,0 
                     C 50,0 50,0 100,0
                     100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     Z;

                     M 0,0 
                     C 50,0 50,0 100,0
                     100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     Z;

                     M 50,0 
                     C 75,25 75,25 100,50 
                     75,75 75,75 50,100
                     25,75 25,75 0,50
                     25,25 25,25 50,0
                     Z;

                     M 25,50 
                     C 37.5,25 37.5,25 50,0 
                     75,50 75,50 100,100
                     50,100 50,100 0,100
                     12.5,75 12.5,75 25,50
                     Z;

                     M 25,50 
                     C 37.5,25 37.5,25 50,0 
                     75,50 75,50 100,100
                     50,100 50,100 0,100
                     12.5,75 12.5,75 25,50
                     Z;

                     M 50,0
                     C 77.6,0 100,22.4 100,50 
                     100,77.6 77.6,100 50,100
                     22.4,100, 0,77.6, 0,50
                     0,22.4, 22.4,0, 50,0
                     Z;
                     
                     M 50,0
                     C 77.6,0 100,22.4 100,50 
                     100,77.6 77.6,100 50,100
                     22.4,100, 0,77.6, 0,50
                     0,22.4, 22.4,0, 50,0
                     Z;
                     
                     M 100,0 
                     C 100,50 100,50 100,100
                     50,100 50,100 0,100
                     0,50 0,50 0,0
                     50,0 50,0 100,0
                     Z;"/>
  </path>

這么多,是不是感覺(jué)有點(diǎn)懵逼。不過(guò),我們細(xì)分來(lái)看一下其實(shí)很簡(jiǎn)單。里面主要是利用 animate 中的 keyTimes,calcMode,keySplines,以及 values 這幾個(gè)屬性。不急,我們一個(gè)一個(gè)來(lái)解釋一下。

  • keyTimes: 這其實(shí)和 CSS 中定義的 @keyframes 一樣。通過(guò) 0-1 之間的值,定義每段動(dòng)畫(huà)完成的時(shí)間。格式為:value;value...。例如 0;.0625;.208333333;.3125;.395833333;.645833333;.833333333;1。從第一個(gè)動(dòng)畫(huà),到第二個(gè)動(dòng)畫(huà)經(jīng)歷的時(shí)間比例為 6.25%。并且,keyTimes 需要和 values 里面定義的幀數(shù)一致。

  • calcMode: 用來(lái)定義動(dòng)畫(huà)具體的插值模型。取值有: discrete | linear[default] | paced | spline。具體可以參考 MDN。這里我們主要介紹一下 spline。該值表示每個(gè)動(dòng)畫(huà)間使用自定的貝塞爾變換曲線。如果沒(méi)有特殊要求,使用 linear 其實(shí)已經(jīng)足夠了,這樣就不用麻煩去定義下面的 keySplines 屬性。

  • keySplines:該值用來(lái)具體定義動(dòng)畫(huà)執(zhí)行時(shí)的 貝塞爾曲線。使用格式是通過(guò) ; 來(lái)分隔每一個(gè)值。即,cubic-bezier(.31,.57,.93,.46) 為一組。使用 keySplines 表達(dá),則為:keySplines = ".31,.57,.93,.46;"。當(dāng)然,里面的貝塞爾曲線組數(shù)為 整個(gè)動(dòng)畫(huà)幀數(shù) - 1。

而 values 就很簡(jiǎn)單了。它是直接結(jié)合 attributeName 屬性,來(lái)設(shè)置具體的值,每個(gè)值之間使用 ; 進(jìn)行分隔。

像上面那樣,可以在指定元素里面嵌套多個(gè) animate,既實(shí)現(xiàn)了形狀的改變,又實(shí)現(xiàn)了顏色的改變。Morph 比較常用于數(shù)字的更迭,比如,倒數(shù) 10s 的相關(guān)動(dòng)畫(huà)。到這里,Morpah 相關(guān)的知識(shí)點(diǎn)就結(jié)束了。

接著,讓我們來(lái)看一下 SVG 中,另外一非常重要的標(biāo)簽 -- animateMotion

該標(biāo)簽可以讓指定的元素,繞著指定的路徑進(jìn)行運(yùn)動(dòng)。所以這對(duì)于復(fù)雜的路徑來(lái)說(shuō)非常有用,因?yàn)槲覀兒茈y使用 transform 去模擬復(fù)雜的變換路徑??匆粋€(gè) DEMO

gif

animateMotion

animateMotion 大致的屬性和 animate 差不多,不過(guò),它還擁有自己特有的屬性,比如 keyPoints、rotatepath 等。不過(guò),calcMode 在 AM(animateMotion) 中的默認(rèn)屬性由,linear 變?yōu)?paced。

這些屬性,我們慢慢介紹,先從最簡(jiǎn)單的開(kāi)始吧。首先,我們來(lái)看一個(gè) DEMO:

<g>
  <rect x="0" y="0" width="30" height="30" style="fill: #ccc;"/>
  <circle cx="30" cy="30" r="15" style="fill: #cfc; stroke: green;"/>
  <animateMotion from="0,0" to="60,30" dur="4s" fill="freeze"/>
</g>
  • from,to:指定兩點(diǎn)的位置,位置參數(shù)是以元素的坐標(biāo)為原點(diǎn)的。

  • dur:執(zhí)行渲染時(shí)間

  • fill:指定動(dòng)畫(huà)結(jié)束后停留的裝填。有 freezeremove 效果。remove 表示回到動(dòng)畫(huà)開(kāi)始的位置,freeze 表示停留在動(dòng)畫(huà)結(jié)束的位置。

如果,你想要更復(fù)雜的路徑,可以直接使用 path 屬性來(lái)指定路徑。用法和 path 標(biāo)簽中 d 屬性是一樣的。

<rect x="0" y="0" width="30" height="30" style="fill: #ccc;">
    <animateMotion
    path="M50,125 C 100,25 150,225, 200, 125"
    dur="6s" fill="freeze"/>
</rect>

或者使用 mpath 標(biāo)簽,引用外部的 path

  <path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110"
      stroke="lightgrey" stroke-width="2" 
      fill="none" id="theMotionPath"/>
  <circle cx="10" cy="110" r="3" fill="lightgrey"  />
  <circle cx="110" cy="10" r="3" fill="lightgrey"  />

  <!-- Red circle which will be moved along the motion path. -->
  <circle cx="" cy="" r="5" fill="red">

    <!-- Define the motion path animation -->
    <animateMotion dur="6s" repeatCount="indefinite">
      <mpath xlink:href="#theMotionPath"/>
    </animateMotion>
  </circle>

動(dòng)畫(huà)效果為:

tuiche.gif-17.8kB

所以,一般而言我們?cè)诙x AM 的路徑的時(shí)候,只用一種方式定義即可,否則會(huì)發(fā)生相應(yīng)的覆蓋:mpath>path>values>from/to。

在 AM 運(yùn)動(dòng)中,還有一個(gè)很重要的概念就是旋轉(zhuǎn)角。默認(rèn)情況下,運(yùn)動(dòng)物體的角度是按照它和坐標(biāo)軸的初始角度確定的。例如:

test.gif-23.7kB

這樣看起來(lái)確實(shí)有些別扭,那能不能讓物體垂直于路徑進(jìn)行運(yùn)動(dòng)呢?

有的,根據(jù) rotate 屬性值,一共有 3 個(gè)值可供選擇。

  • auto:讓物體垂直于路徑的切線方向運(yùn)動(dòng)。不過(guò),如果你的路徑是閉合曲線的話,需要注意起始點(diǎn)的位置。

例如:

test.gif-22.4kB

  • auto-reverse:讓物體垂直于路徑的切線方向并 + 180°。也就是和 auto 運(yùn)動(dòng)關(guān)于切線對(duì)稱。

test.gif-27.8kB

  • Number:讓物體以固定的旋轉(zhuǎn)角度運(yùn)動(dòng)。這個(gè)就相當(dāng)于使用 transform:rotate(deg) 進(jìn)行控制。

在動(dòng)畫(huà)設(shè)置標(biāo)簽中,還有一個(gè)更簡(jiǎn)單的--set。

set

該標(biāo)簽也是用來(lái)模擬 transition 效果的。它和 animate 的主要區(qū)別是,它僅僅需要 to 的指定屬性,而不需要其他的參考屬性,比如 from,by 等。那它有啥特別的存在意義嗎?

有的,因?yàn)?set 針對(duì)于所有屬性,甚至包括 style 里面的相關(guān) CSS 屬性。所以,可以靠它來(lái)很好描述一些非 number 的屬性值。

<text text-anchor="middle" x="60" y="60" style="visibility: hidden;">
  <set attributeName="visibility" attributeType="CSS"
    to="visible" begin="4.5s" dur="1s" fill="freeze"/>
  All gone!
</text>

矩陣動(dòng)畫(huà)

上面差不多簡(jiǎn)單闡述了關(guān)于 SVG 一些比較有特點(diǎn)的動(dòng)畫(huà)。當(dāng)然,還有比較重要的線條動(dòng)畫(huà),這個(gè)我們放到后面進(jìn)行講解。這里先來(lái)看一下所有動(dòng)畫(huà)中,非常重要的矩陣原理。線性代數(shù)應(yīng)該是大學(xué)里面來(lái)說(shuō),最容易學(xué)的一門科目,MD。。。還記得,大學(xué)線代期末考試的時(shí)候,100 分的同學(xué)應(yīng)該說(shuō)是如韭菜地般,一抓一大片(對(duì)不起,我沒(méi)能和他們同流合污。)

那矩陣是如何在動(dòng)畫(huà)中使用的呢?

簡(jiǎn)單的說(shuō),矩陣中的每個(gè)元素其實(shí)可以等價(jià)代換為每個(gè)因式里面的系數(shù):

image.png-6kB

上面也叫作 三維矩陣。即,它涉及到 x,y,z 軸的計(jì)算。那對(duì)于我們平面 2D 變換來(lái)說(shuō),那么此時(shí)矩陣又是哪種形式呢?

很簡(jiǎn)單,只要將 z 軸永遠(yuǎn)置為一個(gè)常數(shù)就 OK。這里,慣例上是直接取 0 0 1 來(lái)設(shè)置。

image.png-1.7kB

不信的話,大家只要代進(jìn)去乘以乘,應(yīng)該就可以得到結(jié)果了。所以,在二維中,具體變換方式為:

image.png-7.1kB

后面,我們也會(huì)依據(jù)這個(gè)公式進(jìn)行相關(guān)的變形操作。那矩陣變換是怎么運(yùn)用到 CSS/SVG 當(dāng)中呢?

在 CSS 中,是直接使用 transform 中的屬性:

transform: matrix(a,b,c,d,e,f);

當(dāng)然,在 SVG 中也是一樣的:

<g transform="matrix(1,2,3,4,5,6)">
    <line x1="10" y1="20" x2="30" y2="40" style="stroke-width: 10px; stroke: blue;"/>
  </g>

所以,我們主要的重點(diǎn)就是講解一下 matrix 這個(gè)屬性。它的格式為:

matrix(a,b,c,d,e,f);

對(duì)應(yīng)于我們上面的公式有:

image.png-2.5kB

在接觸 transform 的時(shí)候,大家應(yīng)該了解到 transform 里面有很多固定的動(dòng)畫(huà)屬性:

  • translate()

  • rotate()

  • scale()

  • skew()

實(shí)際上,在底層還是使用 matrix 實(shí)現(xiàn)的變換。就拿 translate 舉個(gè)例子吧。

translate 的格式為:

translate(dx,dy)

相當(dāng)于參考當(dāng)前原點(diǎn),在 x/y 軸上移動(dòng) dx/dy 的距離。那么映射到矩陣,應(yīng)該如何表示呢?

很簡(jiǎn)單,它等同于:

matrix(1 0 0 1 dx dy);

使用代數(shù)證明一下:

假設(shè)有 matrix(1 0 0 1 20 30)

變?yōu)榫仃嚍椋?/p>

image.png-3.2kB

根據(jù),上面的表達(dá)式有:

X = x'*1 + y'*0 + 20 = x' + 20
Y = x'*0 + y'*0 + 30 = y' + 30

所以,就是 X 在原有 X 軸坐標(biāo)上向右移動(dòng) 20 的距離,Y 相對(duì)于原有移動(dòng) 30 的距離。

那么其他幾個(gè)屬性呢?也是怎么變化的嗎?

恩,類似。只是里面取值不一樣:

  • scale(x,y): 放大 X/Y 軸,矩陣的表達(dá)為 matrix(x 0 0 y 0 0)。

  • rotate(θ): 坐標(biāo)旋轉(zhuǎn),矩陣的表達(dá)為 matrix(cosθ sinθ -sinθ cosθ 0 0)。

  • skew(θx,θy): X/Y 軸拉伸,矩陣的表達(dá)為 matrix(1 tanθx tanθy 1 0 0)。

注意,上面三個(gè)都會(huì)改變?cè)形矬w的坐標(biāo)系!??! 這點(diǎn)很重要,換句話說(shuō),后面每次變換都是基于前面一個(gè)的變換結(jié)果的。

詳情看圖:

image.png-49.6kB

詳情可以參考:MDN matrix

不過(guò),這并不是我們使用 matrix 的重點(diǎn),也不是它的優(yōu)勢(shì)。它的優(yōu)勢(shì)在于可計(jì)算,即,能夠?qū)?fù)雜的動(dòng)畫(huà)集合到一個(gè)表達(dá)式中,并且,后續(xù)的變換可以直接基于當(dāng)前的 matrix。

我們先來(lái)了解一下,如果多個(gè)變換動(dòng)畫(huà)一起使用,matrix 應(yīng)該如何表達(dá)呢?

只需要找到我們變換動(dòng)畫(huà)對(duì)應(yīng)的矩陣,然后相乘即可。例如,先旋轉(zhuǎn) 45°,然后放大 1.5 倍,則有變換動(dòng)畫(huà)為:

transform: rotate(45deg) scale(1.5,1.5);

注意,雖然,你定義動(dòng)畫(huà)是分開(kāi)的,但此時(shí)的動(dòng)畫(huà)是同時(shí)進(jìn)行的。為啥?因?yàn)椋@兩個(gè)動(dòng)畫(huà)實(shí)際上可以整合成為一個(gè)變換矩陣:

image.png-7.4kB

并且,位置是不可以調(diào)換的。比如,transform: scale(2,2) translate(20px,30px)。即,你先放大兩倍,然后移動(dòng) 20,30 的距離。注意,這里移動(dòng)的 20,30 相對(duì)的是已經(jīng)放大過(guò)后的坐標(biāo),相對(duì)于原坐標(biāo)而言就是 40,60 了。 如果,你調(diào)換位置,即 transform: translate(20px,30px) scale(2,2)。就變成現(xiàn)在原坐標(biāo)移動(dòng) 20,30,然后再放大兩倍。

而上面強(qiáng)調(diào)的順序關(guān)系,實(shí)際上就可以理解為矩陣不滿足交換律的原則。因?yàn)橐坏┙粨Q,結(jié)果很可能不一樣。

矩陣高級(jí)用法

上面的內(nèi)容只是簡(jiǎn)單的描述了關(guān)于矩陣的概念。在實(shí)際中,矩陣可以說(shuō)是真正利器。

假設(shè)現(xiàn)在有一個(gè)動(dòng)畫(huà),要求你將一個(gè)物體從一個(gè)點(diǎn)通過(guò)拋物線的方式移動(dòng)到另外一個(gè)點(diǎn),那么此時(shí)要求 JS/CSS 隨你挑。此時(shí),你會(huì)不會(huì)感覺(jué),呼吸急促,頭腦發(fā)熱呢?

恩,matrix 可以治,而且包治百病。不過(guò),matrix 有一個(gè)限制點(diǎn),它只能用于一次線性動(dòng)畫(huà)表達(dá)式。即,針對(duì)于拋物線,橢圓曲線這類復(fù)雜曲線來(lái)說(shuō),不太合適。那么有什么辦法嗎?

有的,微分思想。每一段動(dòng)畫(huà)其實(shí)都可以通過(guò)一定范圍內(nèi)的直線拼接而成,那么這樣,我們就可以將一段拋物線拆分為由幾段線段構(gòu)成的曲線。當(dāng)然,如果你分的越細(xì),擬合度就越高。這里我們不打算過(guò)度你和,我們簡(jiǎn)單的將一段拋物線分為 5段。

如圖:

image.png-12.9kB

那么接下來(lái)就是摳細(xì)節(jié)。這里,依次取傾角為 45°,30°,0°,-45°,-30° 這 5 段直線。每段分配的時(shí)間比例為 20%、25%、10%、25%、20% 這主要是用于 keyframe 的設(shè)定?,F(xiàn)在,用數(shù)學(xué)來(lái)分析一下,這個(gè)動(dòng)畫(huà)到底該怎么弄。

現(xiàn)在,已知兩點(diǎn)之間的距離為 100px。那么我們同樣根據(jù)上述比例分,則有 20px, 25px, 10px, 25px, 20px。

這里我們以 45° 傾角為參考點(diǎn),則終點(diǎn)坐標(biāo)為 (20,20); 。那么,該段的矩陣為:

// 注意 Y 軸需要取負(fù)值!

 1 0 20
 0 1 -20
 0 0 1

CSS 中的變形動(dòng)畫(huà)為:

transform: matrix(1,0,0,1,20,-20);

然后,第二段就為:

1 0 25
0 1 -14.4
0 0 1

使用矩陣的乘法法,則有:

 1 0 45
 0 1 -34.4
 0 0 1

變形動(dòng)畫(huà)為:

transform: matrix(1,0,0,1,45,-34.4);

剩余幾段也是這樣的做法。最終,整個(gè) keyframe 就應(yīng)該表示為:

@keyframe Parabola{
    20%{
        transform: matrix(1,0,0,1,20,-20);
    }
    45%{
        transform: matrix(1,0,0,1,45,-34.4);
    }
    ...
}

整個(gè)動(dòng)畫(huà)過(guò)程差不多都是這樣。當(dāng)然,矩陣也不僅僅局限于這幾個(gè)動(dòng)畫(huà),憑借著高度定制化和靈活性的特點(diǎn),這它還常常用于進(jìn)行回彈,彈跳等動(dòng)畫(huà)中。如果大家有興趣,后期也可以對(duì)這類動(dòng)畫(huà)進(jìn)行簡(jiǎn)單的講解。

后面,我們最后來(lái)了解一下 SVG 中很重要的線條動(dòng)畫(huà)。

線條動(dòng)畫(huà)

SVG 中的線條動(dòng)畫(huà)常常用作過(guò)渡屏(splash screen)中。例如:

test.gif-35.7kB

或者,一些比較炫酷的 LOGO 中,比如 AllowTeam 的:

AT

看到這些炫酷的效果,大家有沒(méi)有動(dòng)心想學(xué)一學(xué),看看自己到底能否做的這么好呢?

OK,我們現(xiàn)在來(lái)正式介紹一下線條動(dòng)畫(huà)。在 SVG 中,最長(zhǎng)用到的線條標(biāo)簽就是 Path。這里我前面一篇文章已經(jīng)做了介紹,我這里就不贅述了。

而在具體變化當(dāng)中用到的是關(guān)于 stroke 的相關(guān)屬性:(下面的屬性都可以直接用在 CSS 當(dāng)中?。?/p>

  • stroke*:定義筆觸的顏色。例如:stroke="green"

  • stroke-dasharray*:定義 dash 和 gap 的長(zhǎng)度。它主要是通過(guò)使用 , 來(lái)分隔 實(shí)線間隔 的值。例如:stroke-dasharray="5, 5" 表示,按照 實(shí)線為 5,間隔為 5 的排布重復(fù)下去。如下圖:

image.png-0.2kB

放大看有:

image.png-5.3kB

另外,stroke-dasharray 并不局限于只能設(shè)置兩個(gè)值,要知道,它本身的含義是設(shè)置最小重復(fù)單元,即,dash,gap,dash,gap...。比如,我定義 stroke-dasharray="15, 10, 5" 則相當(dāng)于,[15,10,5] 為一段。則有:

image.png-0.3kB

放大看則有:

image.png-7.7kB

  • stroke-dashoffset*: 用來(lái)設(shè)置 dasharray 定義其實(shí) dash 線條開(kāi)始的位置。值可以為 number || percentage。百分?jǐn)?shù)是相對(duì)于 SVG 的 viewport。通常結(jié)合 dasharray 可以實(shí)現(xiàn)線條的運(yùn)動(dòng)。

  • stroke-linecap: 線條的端點(diǎn)樣式。

  • stroke-linejoin: 線條連接的樣式

  • stroke-miterlimit: 一個(gè)比較復(fù)雜的概念,如果我們只是畫(huà)一些一般的線段,使用上面 linejoin 即可。如果涉及對(duì)邊角要求比較高的,則可以使用該屬性進(jìn)行定義。它的值,其實(shí)就是角長(zhǎng)度比上線寬:

image.png-5.4kB

而實(shí)際理解的話,就是假設(shè)當(dāng) width 為 1。此時(shí)比例為 2。那么 miter = 2。那么超過(guò) 2 的 miter 部分則會(huì)被 cut 掉??梢詤⒄眨?/p>

image.png-15.6kB

他主要是配合 linejoin 一起使用。因?yàn)?linejoin 默認(rèn)取值就是 miter。所以,默認(rèn)情況下就可以使用該標(biāo)簽屬性。它默認(rèn)值為 4。其余的大家下去實(shí)踐一下即可。詳細(xì)可以參考: miter

  • stroke-opacity:線段的透明度

  • stroke-width:線的粗細(xì)。

OK,介紹完關(guān)于 path 的所有 stroke 屬性之后,我們就要開(kāi)始動(dòng)手寫(xiě)一下讓線條動(dòng)起來(lái)的代碼。簡(jiǎn)單來(lái)說(shuō),就是通過(guò) stroke-dashoffsetstroke-dasharray 來(lái)做。整個(gè)動(dòng)畫(huà)可以分為兩個(gè)過(guò)程:

  • 通過(guò) dasharray 將實(shí)線部分隱藏,空余為全線段長(zhǎng)。然后,將實(shí)線部分增加至全長(zhǎng)。比如:dasharray: 0,1000 變?yōu)?dasharray: 1000,1000。

  • 同時(shí),通過(guò) dashoffset 來(lái)移動(dòng)新增的實(shí)線部分,造成線段移動(dòng)的效果。有: dashoffset:0,變?yōu)?dashoffset:1000。

不過(guò),這里我們不打算使用 Path 來(lái)做啥復(fù)雜的動(dòng)畫(huà),這主要考慮到手頭沒(méi)有一些 SVG 生成工具。所以,這里我們就以 Text 來(lái)做吧(因?yàn)樽銎饋?lái)真的簡(jiǎn)單)。

這里,先以 IV-WEB 這段文字來(lái)做動(dòng)畫(huà)。

先給大家看一下最終結(jié)果:

test.gif-61.9kB

那么這種動(dòng)畫(huà)是怎么做的呢?

這里,我主要介紹一下關(guān)于 CSS 相關(guān),SVG 就一個(gè) Text 我直接貼代碼了:

<svg viewBox="0 0 1320 300">

  <!-- Symbol -->
  <symbol id="s-text">
    <text text-anchor="middle"
          x="50%" y="50%" dy=".35em">
      IV-WEB
    </text>
  </symbol>  

  <!-- Duplicate symbols -->
  <use xlink:href="#s-text" class="text"
       ></use>
  <use xlink:href="#s-text" class="text"
       ></use>
  <use xlink:href="#s-text" class="text"
       ></use>
 

</svg>

上面是通過(guò)創(chuàng)建一個(gè)居中定位的字體,然后使用 3 個(gè) text 重疊。具體 CSS 我們下面來(lái)說(shuō)一下。首先,我們營(yíng)造的效果是從無(wú)到有,就需要使用 dasharray 將 gap 設(shè)置的足夠大。這里我取 300 即可。

stroke-dasharray: 0 300;

然后,通過(guò) nth-child 選擇器,給每一個(gè)文字使用不同的顏色值:

.text:nth-child(3n + 1) {
  stroke: #F60A0A;
}
.text:nth-child(3n + 2) {
  stroke: #F2FF14;
}

.text:nth-child(3n + 3) {
  stroke: #FB9505;
}

下面才是重點(diǎn)內(nèi)容。此時(shí),這 3 個(gè) text 的起始點(diǎn)重合。我現(xiàn)在既要他們?cè)谶\(yùn)行時(shí)不完全重合,又要他們的線條能進(jìn)行滾動(dòng)。不啰嗦了,直接看代碼吧:

@keyframes stroke {
  100% {
    stroke-dashoffset: 1000;
    stroke-dasharray: 80 160;
  }
}

@keyframes stroke1 {
  100% {
    stroke-dashoffset: 1080;
    stroke-dasharray: 80 160;
  }
}


@keyframes stroke2 {
  100% {
    stroke-dashoffset: 1160;
    stroke-dasharray: 80 160;
  }
}

這就是上面 3 個(gè)不同的 text 運(yùn)用的動(dòng)畫(huà)。dashoffet 由 0 到 1000。這完成了滾動(dòng)的目的。同時(shí),為了讓字體不重合,我還需要在對(duì)應(yīng)字體的 dashoffset 上,加上不同的間隔距離。比如,第一個(gè)字體 offset 為 1000。那么第二個(gè)字體,我需要加上前一個(gè)字體 dash 的長(zhǎng)度,即,80。所以,第二個(gè)字體就變?yōu)?1080。那么第三個(gè)就是加上前兩個(gè)的 dash 長(zhǎng)度,即 1160。

大致過(guò)程就是這樣,詳情可以查看: IVWEB 線條動(dòng)畫(huà)。

這里再給大家布置一個(gè)練習(xí)作業(yè),如何實(shí)現(xiàn)無(wú)線連續(xù)的分段動(dòng)畫(huà)呢?

具體效果如圖:

test.gif-388.2kB

給點(diǎn)提示:

將多個(gè)文字重疊,取不同的 offset 和 array 即可。動(dòng)畫(huà)的終止位置一般取一個(gè) gap + dash 的周期長(zhǎng)即可。

后面看看這篇文章反響如何,到時(shí)候再?zèng)Q定是否再寫(xiě)一篇續(xù)集,介紹該作業(yè)的原理。

SVG 文字

在 SVG 中定義文字直接使用 text 標(biāo)簽即可。關(guān)于文字來(lái)說(shuō),一般而言需要注意的點(diǎn)就那么即可,文字的排列,間距等等。這些都可以直接使用 CSS 進(jìn)行控制。不過(guò),有幾個(gè)屬性比較特殊,這里需要額外提一下。

text-anchor

用來(lái)定義參考點(diǎn)和實(shí)際字符之間的定位關(guān)系。格式為:

  • text-anchor: start | middle | end | inherit

直接看代碼解釋吧:

<!-- Anchors in action -->
    <text text-anchor="start"
          x="60" y="40">A</text>

    <text text-anchor="middle"
          x="60" y="75">A</text>

    <text text-anchor="end"
          x="60" y="110">A</text>

第一個(gè) A,參考的是 (60,40) 的點(diǎn),定義為 start ,那么參考點(diǎn)應(yīng)該在字符的前面。

image.png-1kB

而剩下兩個(gè)也是同樣的道理:

image.png-1.9kB

tspan

現(xiàn)在,假如我們想在 text 里面添加一些特殊的字符效果,比如斜體,加粗等。由于,text 標(biāo)簽不能實(shí)現(xiàn)嵌套,所以,為了解決這個(gè)痛點(diǎn),提出了 tspan 的標(biāo)簽。它其實(shí)就是一個(gè)可以嵌套的 text 標(biāo)簽。

<text x="10" y="30" style="font-size:12pt;">
  Switch among
  <tspan style="font-style:italic">italic</tspan>, normal,
  and <tspan style="font-weight:bold">bold</tspan> text.
</text>

tspan 里面同樣可以自定義相關(guān)的自身屬性。詳細(xì)的可以參考 tspan 我這里就不詳述了。

在 Path 展示 text

Text 一般可以橫放,豎放。那有沒(méi)有啥辦法讓文字可以按照一定的路徑任意排放呢?

有的,這里可以使用 textPath 標(biāo)簽,來(lái)定義具體參考路徑。

<path id="sharp-corner"
    d="M 30 110 100 110 100 160"
    style="stroke: gray; fill: none;"/>

<text>
    <textPath xlink:href="#sharp-corner">
    Making a quick turn
    </textPath>
</text>

如圖:

image.png-11kB

具體細(xì)節(jié)我這里就不多說(shuō)了。

Clip

在 DOM 中如果想展示一個(gè)圖片的部分,或者以某種形狀展示圖片的部分,一般是通過(guò)一個(gè) cover div 來(lái)實(shí)現(xiàn)的。不過(guò),如果涉及到不規(guī)則圖形的話,那么 DOM 就有天生缺陷了(當(dāng)然使用 CSS 里的 clip-path 可以完成,不過(guò)兼容性不太好)。而在 SVG 中,提供了 clipPath 標(biāo)簽,能夠讓我們自定義裁剪圖片的范圍和形狀。

clipPath 里面可以接任何圖形,比如,path,rect 甚至是 text。使用的時(shí)候,直接在 style 中,指定 clip-path 即可,或者直接使用 clip-path 屬性指定。

<defs>
  <clipPath id="textClip">
    <text id="text1" x="20" y="20" transform="rotate(60)"
      style="font-family: 'Liberation Sans';
        font-size: 48pt; stroke: black; fill: none;">
CLIP
    </text>
  </clipPath>
 </defs>
 
 <use transform="translate(100, 0)"
  xlink:href="#shapes" style="clip-path: url(#textClip);"/>
  
   <use transform="translate(100, 0)"
  xlink:href="#shapes" clip-path="url(#textClip);"/>

image.png-66.6kB

或者說(shuō),如果我們想畫(huà)一個(gè)圓的裁剪區(qū)域的話:

<defs>
     <clipPath id="circularPath" clipPathUnits="objectBoundingBox">
     <circle cx="0.5" cy="0.5" r="0.5"/>
    </clipPath>
</defs>

<use xlink:href="#shapes" style="clip-path: url(#circularPath);" />

Appendix 參考標(biāo)簽

g

分組標(biāo)簽應(yīng)該毫無(wú)意外排第一,因?yàn)槠鋵?shí)作為繪制圖形中最常和最基本的標(biāo)簽。前面一篇文章也主要介紹過(guò)了,這里做點(diǎn)補(bǔ)充。

每一個(gè)分組標(biāo)簽都帶有 id 屬性,唯一標(biāo)識(shí)該分組,為什么呢?

因?yàn)?,后面我們可以使用?id 標(biāo)簽添加動(dòng)畫(huà),重用該分組等。

<g id="demo" stroke="green" fill="white" stroke-width="5">
     <circle cx="25" cy="25" r="15"/>
     <circle cx="40" cy="25" r="15"/>
     <circle cx="55" cy="25" r="15"/>
     <circle cx="70" cy="25" r="15"/>
   </g>

每個(gè)分組里面可以含有一些描述標(biāo)簽,比如 desc。 這些描述內(nèi)容是不會(huì)被渲染的。

<g id="demo" stroke="green" fill="white" stroke-width="5">
    <desc>Just Demo</desc>
     <circle cx="25" cy="25" r="15"/>
   </g>

use

該標(biāo)簽就是結(jié)合 g 標(biāo)簽一起使用,作用是可以復(fù)用 g 分組的樣式。

<g id="Port">
      <circle style="fill: inherit;" r="10"/>
</g>
<use x="50" y="30" xlink:href="#Port" class="classA"/>

里面使用 xlink:href 加上指定 group 的 id,然后通過(guò) x,y 屬性指定副本放置的位置。不過(guò),有一個(gè)限制,use 標(biāo)簽的 style 屬性,并不能覆蓋點(diǎn)原始的 group style 樣式。而且,有時(shí)候,我們只是想使用一些模板,即,圖形并未被解析,只有代碼存在。這時(shí)候,就需要使用 defs 來(lái)包裹了。

defs

用來(lái)保存一些代碼,使其不會(huì)被瀏覽器解析。并且里面的分組可以被 use 屬性的 style 樣式所覆蓋。

<defs>
    <g id="Port">
      <circle style="fill: inherit;" r="10"/>
    </g>
  </defs>

 <use x="50" y="50" xlink:href="#Port" style="fill: blue;"/>

symbol

該標(biāo)簽和 g 標(biāo)簽類似,也是用來(lái)進(jìn)行分組。不過(guò),它有個(gè)特點(diǎn),即,不會(huì)被瀏覽器所渲染。那它不和 defs 差不多嗎?

恩,確實(shí)。不過(guò),defs 是官方推薦,用來(lái)包裹一些模板 svg 代碼而創(chuàng)造出來(lái),用來(lái)增加可讀性的標(biāo)簽。而 symbol 是存粹的作為一個(gè)模板。它可以獨(dú)立于 svg 的 viewbox 來(lái)自定義子 viewbox 和 preserveAspectRatio。

<symbol id="sym01" viewBox="0 0 150 110">
  <circle cx="50" cy="50" r="40" stroke-width="8"
      stroke="red" fill="red"/>
  <circle cx="90" cy="60" r="40" stroke-width="8"
      stroke="green" fill="white"/>
</symbol>

<use href="#sym01"
     x="0" y="0" width="100" height="50"/>

同樣使用該模板,也是使用 use 標(biāo)簽來(lái)完成。

image

既然 use 可以重用 SVG 代碼,那么 SVG 里面能不能重用已經(jīng)畫(huà)好的 png/jpg 的圖片呢?

這時(shí)候,就需要用到 image 標(biāo)簽。其可以用來(lái)加載外部的 PNG, JPEG 圖片,注意,官方規(guī)定是前兩種,其它圖片支持不支持官方?jīng)]做答復(fù)。即,如果你使用 GIF 圖片,并不能保證所有的瀏覽器都能正常顯示。

<image xlink:href="kwanghwamun.jpg"
  x="72" y="92"
  width="160" height="120"/>
</svg>

同樣,該 image 標(biāo)簽也具有自定義 preserveAspectRatio 的效果。

  • x: 定義水平位置

  • y: 定義垂直位置

  • width: 圖片渲染的寬度,必須有。

  • height: 圖片渲染的高度,必須有。

  • preserveAspectRatio: 控制圖片的縮放

marker

marker 一般是用來(lái)畫(huà)箭頭或者線段始末的標(biāo)識(shí)圖形。

<defs>
    <marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5"
        markerWidth="6" markerHeight="6" orient="auto">
      <path d="M 0 0 L 10 5 L 0 10 z" />
    </marker>
  </defs>

  <polyline points="10,90 50,80 90,20" fill="none" stroke="black" 
      stroke-width="2" marker-end="url(#Triangle)" />

如圖:

image.png-1.5kB

這里我們只需要里了解即可,因?yàn)樵趯?shí)際畫(huà)的時(shí)候,直接使用相關(guān)工具生成更加方便。

a

這里的 a 標(biāo)簽和我們直接在 HTML 使用的超鏈接 a 標(biāo)簽類似。也是用來(lái)定義一個(gè)外鏈的。

<a xlink:href="https://developer.mozilla.org/en-US/docs/SVG"
      target="_blank">
    <rect height="30" width="120" y="0" x="0" rx="15"/>
    <text fill="white" text-anchor="middle" 
          y="21" x="60">SVG on MDN</text>
  </a>

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多