SVG+JS path等值变化实现CSS3兴叹的图形动画

这篇文章发布于 2014年06月24日,星期二,17:41,归类于 SVG相关。 阅读 147011 次, 今日 4 次 24 条评论

 

一、CSS3动画交互的局限

CSS3也能实现很多精彩的动画效果,配合transform新增的多个变换,以及animation的延时、重复次数、前后端状态控制等,可以实现很多精彩的效果。

然而,也是有局限的。很明显,CSS3对图形所做的变化效果,往往都是基于图形整体的动画。什么意思呢?比方说,一个四边矩形,可以让他捏成足球(border-radius,圆角矩形)、然后再旋转、拉伸或者缩放,无论这些动画效果如何,我们依然可以窥见其四条边的存在,虽然可能位置、角度或弧线不一样了。

但是,当我们想把四边形变换成五边形的时候,就会捉襟见肘,虚弱乏力。图形本身的基础变化了,这是CSS3无法驾驭的。

换个文艺的概括,CSS3就是「形可得而神不备」。

因此,对于动其筋骨的图形变换,在web上,我们可能需要借助其他势力的力量,比方说Canvas绘图或者SVG标记。

二、SVG图形动画

SVG本质上就是HTML,能和CSS卿卿我我,打得火热。因此,CSS2/CSS3的一些特性,SVG也是可以受用的。例如,尺寸变化,位置变化,各种颜色变化,transform变换(虽然SVG本身有自己的transform变换属性 – 见下图)等~
SVG的transform属性与矩阵变换

同时SVG一些特有的属性,例如fill, stroke-*等也能直接在CSS中设置,相当强大和方便。//zxx: 具体应用可参见:“纯CSS实现帅气的SVG路径描边动画效果”一文。

不过上面的动画本质上依然是“CSS3图形动画”。差别仅仅是作用在SVG元素上。

而标题这里的“SVG图形动画”指的是:需要借助JS对SVG特定元素属性值做连续变化实现的图形动画效果。 简单来说,就是“只能使用JS驱动实现的SVG图形动画”。

举个大力神杯:
SVG中的圆使用<circle>元素以及中心点属性cx, cy和半径属性r表示。现在,我们想水平位移200像素,怎么办?

指望CSS3动画?关键,CSS看到cx就腿软,指望CSS怕是还不如指望自己家的阿黄。此时,要想对cx属性操刀,势必需要JS大人出马。设置cx +=200, 就可以水平右移200像素了。

我把这种CSS搞不定,JS才能驱动的SVG图形变化称之为「SVG图形动画」。

三、SVG图形动画理论基础

CSS3驱动的元素动画,专业术语称为“补间动画”,指定动画的关键点,对比关键点前后差异,对变化数值做连续化处理。

实际上,任何有规律可循的web动画,包括SVG图形动画,其本质实现与上面的类似。找出前后差异,根据动画引擎做连续化处理。

还是上面那个大力神杯的例子。

其实要位移,何需动cx的干戈,translateX(200px)就可以满足需求。浏览器内置的动画引擎可以自动检测0~200的位移,配合缓动函数,就有我们的动画效果了。现在问题来了,如何我们非要使用cx属性(SVG circle元素横坐标属性)实现呢?

1. 自己写一个简单的动画处理方法; 2.使用优秀的开源的。

出于学习而非政治,加上本身涉猎不深,因此,自己采用了策略2 – 使用之前墙裂推荐的Snap.svg.js.

其中,对于元素,扩展了个animate()方法,可以直接补间形成动画效果。套用上面大力神杯的例子,就有代码:

circle.animate({ cx: 200 });

位移动画直接呈现,超简单。

但是,并不是所有的变化都是可以轻松补间的,尤其当属性值是有多个数值组成的时候,甚至还有特定指令的时候。

比方说下面这两个萌脸:
萌脸 萌脸

假设左图是起始态,右图是终止态,来,给爷来个补间!

呵呵,傻眼了吧。

打脸

显然,目前技术来看,是不可能一行代码就搞定的。要真是这样,呵呵,动画公司的那些制作人员要担心自己的饭碗了,人人都是动画师的节奏啊!

但是,理论上,上面如此复杂的图形补间也是可以实现的。

这就需要深入图形的本源了。

SVG中,规则图形,例如,足球、球门都是有特定的元素的,这些是常用图形,为的是快速方便构建。不规则图形,例如,萌脸的曲面脸蛋,C罗的嘴型,一般都是使用万能的path元素实现。path元素可以实现任意规则或不规则的线、面。

而这些线、面本质上就是path元素的d属性值的·一系列坐标参数+特定指令·构建的。因此,左萌脸的本质可能是坐标a b c,右萌脸的本质可能是坐标d e f,我们只要找到两个坐标的某些关联规律,就能实现补间效果,也就是SVG图形动画效果了。

文字要开花的节奏,赶快插播了个例子:
简单的SVG图形动画一例

上图Gif效果,就是对路径的某一点做有规律的运动形成的动画效果。

您可以狠狠地点击这里:一个简单的SVG路径点变化动画效果demo

这是一个五边形,我们可以使用pathML指令进行绘制(路径指令可参见这篇文章)。

但是,刚开始,温水炖青蛙,蚯蚓钓蛤蟆,一上来就path指令会吓着花花草草以及路过的小朋友的,因此,这里使用polyline多边形元素,使用points属性标记点。例如,上面的demo默认五边形的点位置是(显示器尺寸不一样,值也不一样,因此,仅示意):

<polyline points='798,432,1022,360,1160,549,1021,740,798,667' fill="rgba(255,255,255,0.85)"></polyline>

上面颜色高亮的5个坐标就是五边形的5个顶点坐标。

要实现Gif所示的效果,原理很简单,只要让第3个点水平位移就好了。

本质上就与大力神杯的那个圆圈位移是一样的。只是,大力神杯独立参数,这里是众多参数中的一个。

这里,我们无法直接对整个属性坐标做补间,我们需要使用原生的坐标动画方法。

Snap.svg.js中有个全局的animate()方法,该方法中文文档戳这里

语法如下:

Snap.animate(from, to, setter, duration, [easing], [callback]);

上面介绍的Element.animate()方法就是根据这个底层方法演变而来。其关系,类似于jQuery中$.get()/$.post()$.ajax()的关系。

如果套用大力神杯偏移200像素的例子,则有:

Snap.animate(0, 200, function (val) {
   circle.attr({
        cx: val
   });
}, 500);

Snap.animate()方法强大之处在于这个setter这个函数,动画引擎的核心就是计算特定时间点的特定坐标值(颜色值、尺寸值)等,而这个函数,可以实时返回当前需要的坐标值,于是,我们就可以按照特定的位置关系、运动函数算法,求得整体图形的运动坐标,从而形成完整的动画效果。

对于这里的图形变换,是非常简单的,纯粹第三个点水平位移到中心点。

5点坐标变换

套用Snap.animate()方法就是(假设半径是200)(下面写法纯示意,不要太认真):

Snap.animate(200, 0, funciton(val) {
    polygon.attr({
        points: "点1, 点2, (中心点x坐标 + val, 点3 y坐标), 点4, 点5"
    });
}, 300, mina.backout);

具体代码,您可以“右键页面→查看源代码”,不要再邮件我要源代码了,诸位!

OK, 以上就是SVG图形动画理论基础:使用动画引擎方法(如Snap.svg的Snap.animate()方法),改变特点时间点的特定参数值,并实时刷新。

四、更实际的SVG图形动画实例

先瞅瞅Gif截图效果:
SVG图形动画截屏效果

上图效果就是某实际项目中实现的效果示意。

您可以狠狠地点击这里:SVG path d路径参数变化与图形动画demo

这是一个伪翻页效果,设计师做了此效果视频。显然,设计师如此给力,身为前端,必须高保真实现。此处,CSS3显然是无能为力的,因为折角有内凹状态(见下捕捉截图),但是,对于SVG图形动画,这就是个小儿科。

内凹效果截图演示

画个示意图,其实实现原理相当简单:
hover效果变换示意图

其实跟上面的五边形效果类似,都是某一个点沿着特定的轨迹运动,图形不断渲染形成动画效果。

只是这里是4个点,点的运动轨迹是个斜线而已。

斜线的函数方程式应该都记得吧:

y = a*x + b;

我们知道SVG右上角和左下角的坐标,上面的ab速速就计算出来了。不展开。

还是Snap.animate()方法,展开的相关JS代码如下:

// 图形展开
Snap.animate(svg_width, 0, function(x) {
    var p4 = [x, a * x + b].join(" ");
    // 路径变化走起
    // M p1 L p2 L p3 L p4 L p1Z
    path.attr({
        d: "M" + [p1, p2, p3, p4, p1].join("L") + "Z"
    });
}, 200, mina.easein);

哈,这里为了承上启下,使用了path路径元素。如果你想更简单实现,可以使用polyline元素+points属性。

简单吧~

目前为止,我们展示的两个效果都是只有一个点变化,是不是觉得没有什么挑战性啊?哈,那下面我们再看一个多点联动变化的复杂例子。

五、多点联动的复杂SVG图形动画实例

外甥点灯笼-照旧(舅),先上一个Gif,下图为缩小版:
潘多拉效果Gif示意

上图这种从盒子展开以及收入盒子的效果,我称之为“潘多拉效果”——打开潘多拉的盒子。

您可以狠狠地点击这里:SVG潘多拉图形动画效果demo

第一次载入位置是固定的,如果有点击行为,则后面刷新的时候,工具栏以及Chrome图标的位置就会随机,你会发现,无论在何方,潘多拉都会以正确的方式打开。当偏移位置较大的时候,可以看到明显的边缘弧线,这是因为使用了二次贝塞尔曲线的缘故。

实现原理
原理与上面的两个例子类似,不同之处在于,这里,SVG图形的8个点要同时运动,而不是只有1个点。例如,我们盒子要收起的时候,8个点依次奔向盒子,填充路径实时渲染,就有了我们看到的效果了。

如果要具体展开,我勒个去,估计要上百行开外了。篇幅已经很长,这里简单提几个要点:

  1. 同样,还是依赖于Snap.svg.js的Snap.animate()方法,demo中有使用其回调方法。
  2. 只能是path元素,除了起始点和闭合点(同一点),其实均是二次贝塞尔曲线指令(Q)坐标。//zxx: 贝塞尔曲线指令可参考前一篇文章“深度掌握SVG路径path的贝塞尔曲线指令”。二次贝塞尔曲线指令绘制直线只要对应方位坐标值一致就可以。

    潘多拉效果与路径各个点指令

  3. 每一个点对应一个运动函数,本demo使用的是线性运动函数,也就是上面提到的:
    y = a*x + b;

    当然,如果你数学足够好,你也可以使用曲线函数,效果会更funny!

  4. 垂直运动,所有坐标都是跟对纵坐标也就是y坐标变化而变化,水平运动则是x坐标~
  5. 中间的Q点并不是完全居中的,demo中是1/4远端偏移的,为的是更自然的小曲面效果。
  6. 运动的终点,也就是盒子的坐标实际上只是一条边缘线。我曾试过中心点或整体矩形,但效果都不好。
  7. 每个水平面(垂直运动)或垂直面(水平运动)的点是同一对应方位坐标,其运动启动时间是有先后之分的,启动时间的先后与展示的面板/弹框的尺寸相关。一言难尽,不多说了。

以上~~如果有其他问题,欢迎评论形式交流。

如果你想要源代码,直接[右键→查看页面源代码]就可以了,有超详细的注释(含实现思路),比看文章里的唠叨有用。

此潘多拉效果的核心方法已经完全封装,可以直接使用,不依赖任何JS框架库,需要IE9+浏览器支持。注意,这里只是核心方法封装(也就是动画部分),如果你想插件化,需要把一些参数提出来,主要是两个元素,偏移比例以及运动方位,相信不是难事。

六、结束语

我清楚的记得有篇文章的结束语提到了世界杯比赛,可见,我这空间至少活蹦乱跳了4年。时间嗖得一声,发现已经是2018年世界杯了。那个时候我在做什么呢?who knows! 朝着自己的目标与方向,走好脚下的路。

世界杯的每一场比赛我都看了,不要崇拜哥,因为哥看的都是进球集锦。话说我以前的同事在赌球,由于之前冷门太多,所以昨儿个全压巴西输,荷兰输,然后,今天就在办公楼顶楼吹了半晌的风。

计划半年写20篇文章的,还差两篇,只有一周的时间,周末还要回丈母娘家,怎么办呢……

(本篇完)

分享到:


发表评论(目前24 条评论)

  1. 起个昵称真麻烦说道:

    666

  2. 钟海生说道:

    大神很厉害,在这里看到了很多常用知识点~

  3. railsbug说道:

    我居然又复习了一遍一次函数

    已知某个一次函数的图象与x轴、y轴的交点坐标分别是(-2, 0)、(0, 4),则这个函数的解析式为_____。
    解:设一次函数解析式为y=kx+b
    由题意得 ,
    故这个一次函数的解析式为y=2x+4。

  4. 景jing说道:

    今天连看N章,文章精彩,技术硬,但是让我印象最深刻的是你是一个被程序耽误的段子手…

  5. 小李子说道:

    list.addEventListener(“mouseover”, function() {
    // 鼠标移入,图形展开
    Snap.animate(svg_width, 0, function(x) {
    var p4 = [x, a * x + b];
    // 路径变化走起
    // M p1 L p2 L p3 L p4 L p1Z
    path.attr({
    // d: “M” + [p1.space(), p2.space(), p3.space(), p4.space(), p1.space()].join(“L”) + “Z”
    d:”M50 50 L100 100, 200 250,50 50Z”
    });
    }, 5000, mina.easein);
    });
    list.addEventListener(“mouseout”, function() {
    // 鼠标移出,图形收起
    Snap.animate(0, svg_width, function(x) {
    var p4 = [x, a * x + b];
    // 路径变化走起
    // M p1 L p2 L p3 L p4 L p1Z
    path.attr({
    // d: “M” + [p1.space(), p2.space(), p3.space(), p4.space(), p1.space()].join(“L”) + “Z”
    d: “M0 0 100 100 200 250L z”
    });
    }, 5500, mina.easeout);
    });
    这样动画直接一闪而过,没有缓动效果,这是为什么呢?

  6. 小李子说道:

    怎么留言不成功?

  7. 前端小妹说道:

    大神讲的真好,就是看起来还真的挺费劲的,大神在14年就已经把svg弄得很明白了,而我才刚刚开始前端的学习路程,任重而道远呀,希望有天可以和大神一样博学

  8. 前端菜鸟说道:

    刚接触前端不久,老师让做一个监控功能,大概意思是:想实现,svg以时间点为元素的波形图,时间变化时图形会变化,思路是什么?谢谢啊!

    • 名称说道:

      挺好的学习资源,刚接触,不知不觉,学了快一个月了。用心学,还要用心学数学。

  9. wu—小斌说道:

    数学不好,还真不行!

  10. levo说道:

    有丈母娘了~伤心 ~继续多发文哦,应届毕业生表示学习资源有限

  11. 真心不错。说道:

    自从做前端以来,一搜一些前端疑难问题,百度后,总是在这里找到了答案。。。大榭,楼主,提供这个平台,让我们受益!

  12. BlwooSky说道:

    请问你那个gif是用什么软件录的

  13. simplejoy说道:

    容许我在这里冒昧的提个意见,博主前端技术很是不错,但是这个博客是否因为搭建得比较早的缘故,是否该改个版了,一方面可以将自己的前端技术适当应用上,另一方面可以在排版和布局上更加简洁合理些

  14. zzllrr说道:

    张前辈的SVG科普很好,小乐图客的矢量截图功能,就使用了SVG的技术,可否分享一下,SVG色相滤镜,或CSS3色相滤镜 -webkit-filter: hue-rotate(数字deg),如何通过Canvas操作像素(矩阵变换)来等价模拟?w3官网上的矩阵参数有误(在Chrome中,CSS3滤镜与Canvas实现的结果不一致,其它滤镜基本符合)。谢谢了!

  15. 飞露说道:

    哈哈 最近我也在研究svg的东西, 楼下的svg虽然是很早的技术, 可是h5让内联svg重新点燃了活力, 一直感觉用svg写页面要比 html酷, 可是svg做动画还是不如canvas性能好的 ,svg的好处在于矢量图和与js的交互方便.

  16. 袁源说道:

    SVG 真的是很早很早的技术了

  17. StarBrilliant说道:

    “上图这种从盒子展开以及收入盒子的效果,我称之为‘潘多拉效果’——打开潘多拉的盒子。”

    嘛,你知道乔布斯怎么命名这种效果的么?(类似这个但比这个更华丽。)OS X 控制中心中称其为“神奇效果”……

    汗……

    P.S. 吐嘈时间:神奇的动画,动了又动。大快人心的动画,大快所有人心的华丽动画。全新动画方案已发布,全新动画方案也已发布。真的动,动起来。先进的动画效果,更加先进。

  18. lizhi说道:

    svg,我觉得以后会大量运用动画中的,新技能get!

  19. zakkye说道:

    牛逼

  20. 河冀说道:

    好文章。请前辈喝杯饮料。