SVG形变动画的两种实现方式

原文: SVG Morphing (the easy way and the hard way)
作者:Ahmed Mahmoud

本文是对SVG图形变换的两种实现方法的探索和对比。
我们将使用下面两个图形,并实现它们之间的平滑转换。

图形的代码:

<svg id="cups" width="100" height="130" viewBox="0 0 75 47" xmlns="http://www.w3.org/2000/svg">
    <path id="simple-cup" d="M0.385262674,5.94224634 C0.385262674,6.34224634 -0.28516339,17.7539308 4.32901017,31.1532061 C5.32901017,32.3532061 6.09230991,37.8915223 7.49230991,42.9915223 C10.3923099,53.5915223 9.99336758,58.192 9.99336758,58.192 C9.99336758,58.192 9.99336758,59.1351801 9.99336758,62.192 L9.99336758,63.3762725 L14.9773381,65.692 C20.0773381,67.792 34,67.592 38.6,65.692 L44.0054662,63.992 L44.0054662,62.192 C44.0054662,60.3070603 44.0054662,61.2485521 44.0054662,59.5616903 C44.0054662,59.6227531 44.0054662,58.192 44.0054662,58.192 C44.0054662,58.192 44.0054662,56.0357788 44.0054662,56.0368563 C44.0054662,56.1443976 44.1584889,52.1913962 44.1513903,52.2148862 C44.18043,52.2064969 44.4505642,49.3217527 44.4611476,49.3039416 C44.0054662,46.9651447 46.7678097,39.091534 47.5678097,38.791534 C48.3678097,38.591534 52.8,37.6017328 55.4,37.0017328 C62.3288017,36.7364795 68.4691682,30.4573789 68.4691682,26.851942 C68.4691682,25.0527911 68.4691682,20.2992257 66.1171925,17.7539308 C63.4665746,14.8148385 60.3572838,14.0704885 56.6937477,14.2033611 C54.2179046,14.2931572 54.2497766,12.7733674 54.0497766,12.5733674 C53.8497766,12.4733674 54.0497766,12.5733674 54.0497766,10.8407045 L53.3480457,6.66187369 L51.3120843,4.64051206 C49.8120843,3.64051206 47.4776665,2.98869271 44.6776665,2.18869271 C38.2776665,0.488692712 20.1046423,0.0299756608 12.7046423,1.42997566 C7.00464234,2.42997566 0.385262674,4.74224634 0.385262674,5.94224634 Z M64.7915417,22.8695184 C64.7915417,25.8695184 65.5001114,27.9159065 60.6340924,31.7563748 C59.5900982,32.5803392 64.003575,30.2804794 62.903575,30.7804794 C60.903575,31.8804794 53.6,32.676872 52.3920534,31.7563748 C51.5537443,31.1057778 51.0837569,29.8430422 50.9820915,27.9681678 C51.2625331,24.5690682 52.1351692,22.0569251 53.6,20.4317385 C57.4,16.0317385 64.7915417,19.1695184 64.7915417,22.8695184 Z" fill-rule="nonzero" fill="#070707" />
    <path id="fancy-cup" d="M0,6.792 C0,7.192 0.9,8.492 1.9,9.592 C2.9,10.792 4.9,15.892 6.3,20.992 C9.2,31.592 12.4,36.492 17.5,38.192 C21.6,39.592 21.7,39.792 18.8,42.192 L16.5,43.992 L19.5,45.292 C24.6,47.392 37,47.592 41.6,45.692 L45.5,44.092 L43.1,42.092 C41,40.392 40.9,39.892 42.1,39.092 C42.8,38.592 44.2,38.192 45.1,38.192 C47,38.192 52.4,33.492 53.2,31.192 C53.6,29.892 53.9,29.892 55.3,30.992 C57.5,32.892 59.1,31.592 58.4,28.592 C58,26.992 58.3,26.092 59.1,25.792 C59.9,25.592 62.6,24.792 65.2,24.192 C70.7,22.692 75,18.992 75,15.492 C75,13.692 74.1,12.692 71.3,11.592 C68,10.192 67.3,10.192 63.7,11.592 C61.4,12.492 59.5,12.992 59.3,12.792 C59.1,12.692 59.9,11.292 61,9.892 L63.1,7.292 L60.5,5.592 C59,4.592 55.5,3.192 52.7,2.392 C46.3,0.692 21.5,-0.108 14.1,1.292 C8.4,2.292 0,5.592 0,6.792 Z M70,15.592 C70,18.592 65.7,22.192 62.3,22.192 C61,22.192 59,22.692 57.9,23.192 C55.9,24.292 55.5,23.592 56.6,20.792 C56.9,19.892 57.6,19.492 58.1,19.792 C58.6,20.092 60.5,18.692 62.2,16.792 C66,12.392 70,11.892 70,15.592 Z" fill-rule="nonzero" fill="#070707" />
</svg>

比较简单的方式

可以使用js库 Snap.svg来实现:

  1. <script src=”https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js"></script> 或者 npm install snapsvg添加 Snap.svg 到项目中,如果使用ES6可以直接引入(import),也可以借助gulp或grunt进行合并。
  2. 添加“#simple-cup”路径
  3. 添加“#fancy-cup”路径
  4. 把“#fancy-cup”改为不透明(opacity="0"),因为它主要是用来提供d属性中的值
  5. 添加下面的代码
var svg = document.getElementById("cups");
var s = Snap(svg);
var simpleCup = Snap.select('#simple-cup');
var fancyCup = Snap.select('#fancy-cup');
var simpleCupPoints = simpleCup.node.getAttribute('d');
var fancyCupPoints = fancyCup.node.getAttribute('d');
var toFancy = function(){
  simpleCup.animate({ d: fancyCupPoints }, 1000, mina.backout, toSimple);  
}
var toSimple = function(){
  simpleCup.animate({ d: simpleCupPoints }, 1000, mina.backout, toFancy); 
}
toSimple();

完整的例子在这里

这种实现方式很简单:

  1. 选择svg
  2. 选择里面的路径
  3. 获取路径的 d (description)
  4. 添加动画让d从一个值(第一个杯子)变为另一个(第二个杯子)
  5. 利用动画结束后的回调函数,让图形循环变换

优点

  1. 可以在任意的图形间实现变化,不受图形节点的限制
  2. 可以在动画结束时添加回调函数
  3. 有很多预置的运动曲线 (mina.linear, mina.backin, mina.bounce, mina.easein, mina.easeinout, mina.easeout, mina.elastic)

缺点

  1. 需要在网页中添加80.44kb(压缩后28.59kb)大小的js文件
  2. 在JS中定义动画,而不是遵循SOG(separation of concerns关注点分离)
  3. 动画依赖于JS,就有可能因为JS文件加载失败或者某些错误阻碍代码执行而导致动画失败

另一种实现方式

使用<animate>,然后定义动画开始和结束的路径节点。这种方式一个有些麻烦的局限性是,变换的两个图形需要有相同数量的节点。

具体的方法:

  • 在SVG编辑器(Sketch/Illustrator)打开这两个SVG图形

  • 双击图形显示节点

  1. 单击那些需要改变曲线弧度的点,把图形变成另一个形状

  • 当图形A变换成B之后,导出SVG
  • 用编辑器打开SVG文件
  • 复制path
  • 假设已经有图形A的path,将代码<animate dur=”5s” repeatCount=”indefinite” attributeName=”d” values=”shapeAPoints;shapeBPoints;shapeAPoints”>添加到图形A的path标签中
  • 添加了动画的dur(时长),运动的属性值attributeName,以及重复次数repeatCount之后,通过添加fill="freeze" calcMode="spline"来设置使用运动函数(贝塞尔曲线),用keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"自定义运动曲线算法,具体可以参考这里

这里是完整的栗子

这种实现方式的优点

  1. 不依赖任何js插件
  2. 实现了SOG
  3. 性能更好


(使用<animate>

(使用Snap.svg)

缺点

  1. 需要修改SVG
  2. 图形变换并不总能保证节点对应(数量一致)
  3. 动画结束时不能添加回调函数

注: 还有就是IE不支持...

结论

SVG图形变换的方法:或者引入80kb的插件,使用Snap.svg在任意图形间切换,或者手动编辑图形然后使用<animate>

Comments
Write a Comment