SVG初步实践
一、SVG是什么
SVG 意為可縮放矢量圖形(Scalable Vector Graphics),是基于 XML 的矢量圖形描述語言,可以近似理解成 HTML,所以能和 JS 以及 CSS 進行交互。
不要把 SVG 和 CSS,Canvas,HTML 搞混,他們之間并沒有你中有我,我中有你的關系。
SVG 就是一個 XML,例如:
<svg x="0px" y="0px" width="450px" height="100px" viewBox="0 0 450 100"><rect x="10" y="5" fill="white" stroke="black" width="90" height="90"/><circle fill="white" stroke="black" cx="170" cy="50" r="45"/><polygon fill="white" stroke="black" points="279,5 294,35 328,40 303,62 309,94 279,79 258,94 254,62 230,39 263,35"/><line fill="none" stroke="black" x1="410" y1="95" x2="440" y2="6"/><line fill="none" stroke="black" x1="360" y1="6" x2="360" y2="95"/> </svg>二、SVG繪制路徑
SVG提供了 rect、circle、ellipse、line、polyline、polygon 六種基本形狀用于圖形繪制,這些形狀可以直接用來繪制一些基本的形狀,如矩形、橢圓等。
而復雜圖形的繪制需要使用 path:
<path>通過屬性 d 來定義路徑,具體值是由專門的“指令字母+坐標值”實現,每一個命令對應一個字母,并且區分大小寫
- 大寫:? 參照的是絕對坐標,即 SVG 的右上角
- 小寫:? 參照的相對坐標,即 前一個點 的坐標
<path>屬性 d 主要有以下幾個命令:
其中,有5個指定屬于基本指令,你也可以理解為“好理解好上手好記憶”的指令,見下表:
?
除了這5個參數少、直來直往的指令,剩下的,除了弧形命令A(Arcs),就都是與貝塞爾曲線相關的命令了。數學比較差,暫不介紹。
三、SVG從哪里來
雖然利用SVG的標簽可以畫出任意的圖形,但是對于復雜圖形而言,靠手寫坐標既費力又費時,這時候就需要借助第三方工具,例如 Adobe Illustrator CC
先在 AI 工具中畫好圖像,然后導出成 svg 文件即可(這塊的工作可以交給 UI 的同事)
四、動畫方式
svg 文件拿到了,怎樣才能讓它動起來呢,這里就要講一下動畫相關的姿勢了~~~ ?
動畫變換其實就是圖畫從 A 狀態 過渡到 B 狀態的過程,可以用SMIL、Javascript、CSS3 方式實現。
SMIL
SMIL 全稱 Synchronized Multimedia Integration Language,該語言被 SVG 原生支持,主要使用標簽來描述動畫。
據傳,由于性能的問題以及 CSS Animation 越來越強大,Chrome 會在未來的版本中廢棄對 SMIL 的支持。
不過 MDN 上的聲明卻說 Chrome 推遲了廢棄時間,也不知道是什么情況。
總而言之,被 Chrome 這么一搞還是不推薦大家入門 SMIL 了。
Although Chrome 45 deprecated SMIL in favor of CSS animations and Web animations, the Chrome developers have since suspended that deprecation.
via: SVG animation with SMIL
JavaScript
使用 JS 來操作 SVG 動畫自不必多說,網上已經有很多現成的類庫,例如老牌的 Snap.svg 以及 anime.js,都能讓我們快速制作 SVG 動畫。
當然,除了這些類庫,HTML 本身也有原生的 Web Animation 實現,使用 Web Animation 也能讓我們方便快捷地制作動畫。
CSS3
這里主要是使用 CSS3 動畫三劍客 ( animation, transform, transition ) 來實現動畫。
css 優點不言自明,它比 JS 更加簡單方便,關鍵幀的配合可以讓其實現很多復雜的動畫。
?
下文主要就從 CSS3 的角度來描述 SVG 動畫的方法。
?
五、CSS3 動畫方法
動畫可以拆成兩部分,起始狀態和終止狀態是一部分,兩態中間的過渡是另一部分。
而兩態的變換,簡單的來說可以分為 變換 和 變形 兩種,即線性變換和非線性變換。
變換動畫
平移 translate
平移變換主要是利用 translate 等方法讓元素移動位置達到的動畫,只要善于使用,簡單的移動也能做出不錯的動效,例如:
這里有一個小技巧,就是在元素移動的起點和終點分別設置標記元素。使用 getClientRects() 方法獲取標記元素的絕對位置,通過計算標記元素的位置差,換算成平移的距離。這樣利用純 CSS 就實現了位置移動的有趣動畫。
旋轉 rotate
旋轉變換是使用 rotate 方法讓元素進行旋轉。使用時請注意:旋轉圓心默認在 SVG 的左上角。可以通過設置 transform-origin 來修改圓心。
.rotate {
transform: rotate(45deg);
transform-origin: 100px 100px
}
由于旋轉圓心如此重要,所以正如下面的這個例子,我們首要確認的是旋轉圓心,剩下的就是簡單的旋轉角度的控制問題了。
縮放 scale
縮放變換則是使用 scale 方法讓元素進行變大變小, 它的本質實際上是元素點 (x, y) 被乘以縮放系數了,即 (x * sacle, y * scale)。所以當設置了 transform: scale(2) 時,它相當于元素所有的坐標點變成 (2x, 2y) 了。
講的這么詳細主要是想讓大家注意一個問題,當放大系數為負數的時候,例如 -1 那元素坐標點就會變成 (-x, -y),這就相當于元素針對橫縱坐標分別做了一次鏡像。對于一些需要實現鏡像效果的元素可以使用這招。
斜歪 skew
斜歪變換是使用 skew 方法使元素進行傾斜變形的方法。
矩陣變換 matrix
矩陣變換是所有變換方法的底層實現,上述變換方法都是矩陣變換某個方向的特例,完全可以使用矩陣變換實現。矩陣變換的使用方法就不多介紹了,不清楚的可以查下文檔。一個 2D 的矩陣變換如下所示:
關于為何使用矩陣來進行矩陣變換計算,感興趣的小伙伴可以參考《從矩陣與空間操作的關系理解CSS3的transform》,這篇文章淺顯易懂的解釋了使用矩陣表示的精妙之處。
變形動畫
變形動畫即非線性變換動畫,這里主要講述描邊,路徑軌跡和路徑變形三種變形動畫。
描邊動畫
描邊動畫算是經典的 SVG 動畫了,給人一種繪畫的視覺效果,所以描邊動畫的名字因此得來。
它主要是利用 Path 的描邊長度 stroke-dasharray 配合描邊起始位置偏移量 stroke-dashoffset 來實現的動畫。
首先設置 stroke-dasharray 等于路徑長度,然后將 stroke-dashoffset 從路徑長度減少到 0 ,就完成了一次描邊。
可以看到,描邊動畫其實主要在于設置描邊的長度。
除了在 AI/Sketch 等軟件中獲取路徑長度外,我們還可以使用 JS 的 getTotalLength() 方法獲取。
除了使用 CSS3 Animation 來實現動畫外,我們也可以使用 JS 來繪制動畫,網上有很多現成的類庫。
其中比較推薦大家使用 drawsvg 這款描邊庫,能簡單快速地實現很多復雜的描邊特效。
路徑軌跡動畫
路徑軌跡動畫簡單來說就是讓元素沿著某條路徑進行運動的動畫。如果用其它方法實現這個效果的話可能會略復雜,但是用 SVG 就非常得心應手了,這里主要說一下 CSS3 方法。
首先設置 offset-path 屬性為某一條 Path 路徑,然后通過改變 offset-distance 的距離值,就可以實現元素沿著 offset-path 運動的動畫。
路徑變形動畫
嚴格意義上,路徑變形動畫才真正算的上“變形”動畫,它表示的是路徑之間不規則變化的動畫。
從 A 路徑變形成 B 路徑,我們現在可以直接使用 CSS3 Animation 來實現,瀏覽器會自動幫我們做補間動畫。當然我們也可以使用一些 JS 庫例如 Snap.SVG 來制作。
不管是使用 CSS3 還是使用 Snap.SVG,都需要注意一個問題,那就是兩條變形路徑內的描點數需要保證一致,而且需要都是簡單的描點,不能存在弧線等高級描點。這么實現的原因很好理解,能夠極大減少補間動畫的運算量,本質上可以歸類為每個描點的平移動畫。
但是這種限制對于開發者來說還是頗為麻煩的,所以很多類庫就想要解決這個問題。其中比較著名的有 GreenSock,它們編寫的 GSAP 插件據說能夠對路徑進行任意變形,從官網首頁的動畫也可窺見一二。另外一款插件是 SVG Morpheus,也能實現同樣的效果,不過可能因為算法的問題,補間動畫似乎并沒有 GSAP 那么流暢。另外最近騰訊 AlloyTeam 發布了一款插件 Pasition ,用來實現路徑過渡效果,使用起來也很方便,感興趣的同學可以試一試!
六、實踐
實踐檢驗真理,本例子中使用Javascript方式實現,引用騰訊公司的開源插件 Pasition 。
先上效果圖:
?
代碼如下:
1 <html> 2 3 <head> 4 <title>svg demo use pasition.js</title> 5 </head> 6 <body> 7 <div class="mainContener"> 8 <svg id="line" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 798.8 876.8"> 9 <defs> 10 <path id="cls1" d="M790.13,238.63A398.06,398.06,0,0,1,392.06,636.7c-61.66,0-129.31-30.75-181.41-55.78C77,516.72-6,396.81-6,238.63-6,128.71,48.19,40,120.23-32S285.08-172.94,395-172.94,570.73-101.71,642.76-29.68,790.13,128.71,790.13,238.63Z"/> 11 <path id="cls2" d="M790.13,238.63c0,115.28-34.42,250.22-112.73,322.92-71,65.94-162,56.36-266.55,56.36-61.66,0-133.42-.8-185.53-25.83-38-18.26-80.24-75.71-110.37-104.51C39.09,415.09-4.83,351.84-4.83,238.63c0-109.92,63-178.07,135-250.11S284.49-206.4,394.41-206.4,567.46-75.54,639.5-3.5,790.13,128.71,790.13,238.63Z"/> 12 <path id="cls3" d="M790.5,238.5c0,117.28-46,191.9-126,265.51-71.73,66-157.87,164.39-263,164.39-48.38,0-101.22-34.95-144.19-50.59A403.66,403.66,0,0,1,77.44,487C24.05,418.65-1.3,320.32-1.3,226.89-1.3,99.71,51.19-1.37,143.25-75.18c69-55.29,157.08-51.37,252.34-51.37,101.69,0,203,10.88,273.91,73.05C753.66,20.32,790.5,117.76,790.5,238.5Z"/> 13 <path id="cls4" d="M790,239c0,128.23-63.58,241.76-158.53,313.58C565.55,602.43,484,609.1,395,609.1c-47.5,0-109.32-2.18-151.48-17.61C165.17,562.82,114.19,526.6,68.23,458.57,25.81,395.79-4.24,320.69-4.24,239.22-4.24,124.67,50,21,128.16-50.93c70.05-64.45,158.27-90.3,261-90.3,96.91,0,189.77,37,258.33,95.11C732.59,26,790,118.66,790,239Z"/> 14 <path id="cls5" d="M790.5,238.5c0,70.8-15.7,142.42-48.51,199.75-67.59,118.08-203,191.4-348.75,191.4A390.63,390.63,0,0,1,148,543.55C58.41,471.67-2.48,361.26-2.48,237.46c0-107.11,46.46-204.19,116.06-275A391,391,0,0,1,393.24-154.73C505.44-154.73,612-88,683.5-12.5,750,57.78,790.5,134.1,790.5,238.5Z"/> 15 </defs> 16 <path class="cls-1" transform="translate(7.17 207.9)"/> 17 <path class="cls-2" transform="translate(7.17 207.9)"/> 18 <path class="cls-3" transform="translate(7.17 207.9)"/> 19 <path class="cls-4" transform="translate(7.17 207.9)"/> 20 <path class="cls-5" transform="translate(7.17 207.9)"/> 21 </svg> 22 </div> 23 24 </body> 25 <style> 26 body{background-color: black} 27 .mainContener{width: 500px;height: 500px;margin: 100px auto} 28 .mainContener svg{ 29 margin: 20px auto; 30 } 31 .cls-1,.cls-2,.cls-3,.cls-4,.cls-5{fill:none;stroke-miterlimit:10;} 32 .cls-1{stroke:#f455a4;}.cls-1,.cls-4{stroke-width:2.35px;}.cls-2{stroke:#8faffc;stroke-width:3px;}.cls-3{stroke:#f64629;}.cls-4{stroke:#997aff;}.cls-5{stroke:#ffd500;} 33 34 </style> 35 36 <script src="http://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.js"></script> 37 <script src="https://unpkg.com/pasition@1.0.1/dist/pasition.js"></script> 38 <script type="text/javascript"> 39 40 $(() => { 41 var path1 = document.querySelector('#cls1').getAttribute('d'); 42 var path2 = document.querySelector('#cls2').getAttribute('d'); 43 var path3 = document.querySelector('#cls3').getAttribute('d'); 44 var path4 = document.querySelector('#cls4').getAttribute('d'); 45 var path5 = document.querySelector('#cls5').getAttribute('d'); 46 47 animate(".cls-1", [path1, path2, path3, path4, path5], 0, 1); 48 animate(".cls-2", [path1, path2, path3, path4, path5], 1, 2); 49 animate(".cls-3", [path1, path2, path3, path4, path5], 2, 3); 50 animate(".cls-4", [path1, path2, path3, path4, path5], 3, 4); 51 animate(".cls-5", [path1, path2, path3, path4, path5], 4, 0); 52 53 }); 54 55 pasition.toSVGString = function (shapes) { 56 /*克隆一下實時數據*/ 57 //var shapes = JSON.parse(JSON.stringify(shapes||[])); 58 /*對數據中的每個點數組做處理 59 * */ 60 return shapes.map(function(shape){ 61 shape.forEach(function (point, idx) { 62 if (!idx) { 63 /* 64 * 若是第一個點數組,那么對該點數組的處理是前面加M,然后前兩個點后面加C 65 * */ 66 point.splice(2, 0, "C"); 67 point.unshift("M"); 68 } else { 69 /* 70 * 除了第一個點數據外,所有的點數組的前兩個點刪除掉 71 * */ 72 point.splice(0, 2, "C"); 73 } 74 }); 75 return shape.map(function (point) { 76 return point.join(" "); 77 }).join(""); 78 }).join("") 79 80 }; 81 82 function animate(classname, arr, index1, index2) { 83 var dom = document.querySelector(classname); 84 pasition.animate({ 85 from: arr[index1], 86 to: arr[index2], 87 time: 5000, //5s完成動畫過渡 88 progress: function (shapes) { 89 dom.setAttribute("d", pasition.toSVGString(shapes)); 90 }, 91 end: function () { 92 index1++ 93 index2++ 94 if (index1 === arr.length) index1 = 0 95 if (index2 === arr.length) index2 = 0 96 animate(classname, arr, index1, index2) 97 } 98 }) 99 } 100 101 </script> 102 </html>七、后記
本文只是涉獵了一些常用的變換方法以及一些基本的變形動畫,SVG 還有很多例如遮罩、濾鏡、填充動畫方法,感興趣的同學可以去了解下,都能實現非常有趣的動畫特效。不過萬變不離其宗,只要你富有想象力,能夠設計出一些別出心裁的動效,即使只使用上文提及的一些簡單的動畫方法,我相信最終的動效也一定是非常有意思的!
?
參考資料:
SVG動畫實踐
SVG基本形狀path路徑置換
深度掌握SVG路徑path的貝塞爾曲線指令
?
轉載于:https://www.cnblogs.com/waxdl/p/9103580.html
總結
- 上一篇: python虚拟环境 virtualen
- 下一篇: cjson库