我用SVG+CSS3做了一个舞动的机械人——致AI时代的到来

3,386 阅读8分钟

想法来源于codepen大神的一个神作,一群机械人以神一样的步伐在进行群魔乱舞,可是,可是,打开一看,纯js实现,js!也罢,我偏要试一下用SVG+CSS3能不能搞出这个效果来,o( ̄ヘ ̄o#)。此为前提。

1.基础图形与基础动画

先来个简易版的机械人吧,因为重点是动效,所以就简易到不能再简易,全部胶囊样式的圆角矩形来搞,但为了方便以后替换个胳膊腿什么的,我还是乖乖的把每个准备运动起来的零件都拆分到了不同的图层,并且给关节部位打了一个螺丝钉。好吧好吧,简易螺丝钉,打了个黑点行了吧,在AI里绘制完成后,这货是下面这样滴。(为了方便叠加,我给加了透明度,后来发现有透明度居然自带设计感,那就这样吧)

虽然没有科技感,且遍布全身的诡异黑点导致它看上去像是十八铜人或者中医针灸啊穴位啊的什么东西,但是这不妨碍我们的机械人运动,来吧,给大家打个招呼,动动头先,走起来。

CSS部分略通一二的小伙伴都知道,再简单不过,transform:rotate属性,这里唯一要注意的是在定义animation时,一定要定义transform-origin,旋转的原点,这里就是我在关节处打了一堆黑点的意义了。因为我AI导出的SVG中,每个关节,也就是<circle>的圆心cxcy的值,就是各个部位旋转的原点。

很好,很听话。既然动起来有了生命,管它硅基还是碳基,给“他”起个帅气的名字,叫起来也方便。大白(●—●)不能叫,那就叫小冰吧。“Hi~,我是小冰”

小冰,现在命令你动一下左腿,摆幅大一些。

好的,接下来,其他部位一并动起来。


好了检测完毕,一切正常。等等,似乎哪里不太对,聪明如你一定发现了诡异之处,那就是——动效里都是肢体的末节在动,小腿动大腿不动,胳膊动胳膊肘不动,咳咳,是的,这是一个被钉住只能挣扎的机械人。钉住小冰的就是那一堆“螺丝”。为什么这么搞呢?理由很简单,我让大腿动一下,看看发生了什么

这是?各甩各的,谁都不理谁?拜托,你们可是一个整体哎。现在的问题来了,我们CSS动画属性定义的时候,每个可活动的形状的旋转原点都是给了固定值,那既然如此,怎么才能让末肢的原点随着上肢的摆动而动起来呢?

2.SIML路径动画解决方案

凡事皆要慢慢来,先进行第一步拆解,只让上半部分肢节动起来。

为了方便查看(现在眼花缭乱的不能直视),小冰听令,先暂停摆动,右大腿带着右小腿动一个。(大腿和小腿用相同的旋转动画定义,包括原点,统一定义为大腿旋转的原点)

有没有毛病?有!不符合物理规律的样子。我们先用图来分析一下,然后来对症下药。


大腿从位置A甩到位置B,我希望得到小腿的效果是始终保持下垂。相当于小腿部分没有摆幅度的运动,也就是沿着图中的虚线轨迹运动。等等,想起来了点什么没?轨迹?我们在路径动画中提到过是可用控制摆动的,<animateMotion rotate="auto">这是定义随着路径摆动,不定义的话默认不摆动,那既然圆形也可以是路径,我们这里用路径动画实现一下看看如何。关于路径动画的详解我以前写过详解了,这里只看效果:

似乎解决了问题的样子。但是,新的问题已然出现,就是我要甩大腿的过程中小腿自然摆动,怎么搞?

3.拆解SVG,利用独立坐标系解决方案

去他的路径动画,正解在这里,因为一个SVG是一个坐标系,也就是说我们定义旋转原点的时候都要遵循同一个规则,在立方体旋转动画里,我们曾经搞过,六个面,六个SVG,里面各自动画进行,互不干扰,这个思路放到我们这个动画中,依旧可行。


简单来说,我把需要摆动的四个末肢放到四个SVG里,还是拿右腿为例。看图,看图。小腿所在的独立的SVG先与大腿保持相同的旋转运动,当然,用的也是大腿的旋转原点O2,里面组成小腿的形状按自己坐标系的旋转原点O1运动,因为SVG运动的过程中,坐标系也在运动,所以虽然相对于原始坐标系小腿的运动的原点一直在变化,但在它自己的坐标系里,却始终不变。有点拗口,但理解了动画思路也就打通了。
贴上部分代码顺便解释一下
首先,最重要的是,放到html中,首先要把SVG的位置属性定义成绝对,这里不卖弄了,小伙伴都懂得,因为我们的SVG是互相重叠的,所以

SVG{position:absolute}

运动的CSS代码如下(只放了右腿的):

 @keyframes rightLeg{              /*定义右腿大腿的摆动(旋转动画)*/
 0%{transform:rotate(0deg); }
 100%{transform:rotate(-90deg); }
 }
 .rightLeg{
animation:rightLeg 1s ease alternate infinite; 
transform-origin:           /*旋转原点O1*/
}

@keyframes rightLeg2{         /*定义右腿小腿的摆动(旋转动画)*/
0%{transform:rotate(-50deg); }
100%{transform:rotate(50deg); }
}
.rightLeg path{
animation:rightLeg2 0.4s ease alternate infinite ;
transform-origin:          /*旋转原点O2*/
} 

DOM部分,大腿、胳膊肘儿、头、身体是同一个SVG,组成小腿部分是单独的SVG

<svg class="rightLeg">
<path d=""/> <!--这里可能是path,也可能是rect,总之AI导出什么就是什么啦。-->
</svg>

好了,小冰,听口令,现在让大家看一下正常的机械腿。


perfect!现在小腿和大腿可以无缝连接在一起,想怎么动就怎么动,运动速度啊,摆动幅度啊,任意定义。

剩下的又是体力活了,现在把其余的部分按照上面的思路拆解就可以咯。我随随便便定义了一下(说是随便,但每个都要定义一堆,也是头昏脑涨啊),然后就得到了下面这段动画。


机械舞,够不够魔性。
当然了,如果你有足够的创意的话,可以随便玩儿嘛,反正小冰非我族类,怎么运动都可以。比如齐刷刷的左右摆动(左臂右臂定义成相同的动画,左腿和右腿定义成相同的动画)


总之,怎么定义都行,一切随意。

还记不记得我们的跳帧动画,见这篇文章juejin.cn/post/684490…
里面做了一些效果,比如“咔哒咔哒”的时钟,我们的小冰也可以“咔吧咔吧”的动起来。在定义动画animation时,去掉ease啊,linear啊,或者cubic-brazier这些定义速率的值,换成steps(),就会得到缺少润滑油的小冰,关于steps(n)里n值的定义,可以不要太过随意,根据旋转动画的时间,使咔吧的频率保持一致,会更严谨一些。我做了效果可以看一下:

再比如我们来个炫酷的组装过程。(动画第一部分为零件组装,也就是以transform:translate位移动画为主)


就这样吧,拆得再散实在招架不住了。

如果你觉得身体部分一动不动,太过死板的话,no problem,先理清楚思路,首先,所有的零件一起移动,同时,大腿在自己的坐标系做旋转运动,小腿在自己的坐标系做旋转运动。这样的话,要对原DOM结构再加入一个容器<div>。把所有的SVG放到这个<div>中。
CSS部分原来的不变,增加一个身体移动部分的定义如下:

 @keyframes base{        
 0%{transform:translateX(-100px); }
 100%{transform:translateX(100px); }
 }
.base{
animation:base 1s ease alternate infinite;
}

或者再增加点摆动,就得到了下面这个效果:

在基础的动效基础上,可以对机械人小冰进行各种美化,去替换SVG的组成部分就可以,比如下面这种:


记得重新定义旋转原点!!

关于动效联动重点是相对坐标系的使用,也就是说,如果一个元素需要“自动”和“公动”(源自自转和公转,自己造词)方法就是放到单独的SVG中,每个SVG拥有自己独立的坐标系,组成元素的“自动”遵循自己的坐标系。

这个方法一旦掌握,再做动效时简直为所欲为,做机械臂很简单吧?做摩天轮很简单吧?感兴趣的小伙伴可以自行尝试。
另外,另外,其实我知道最好的方法是用js,比如我们这种动画,可以js去获取关节部分的坐标值,再赋值给旋转原点,可是,这不是不会嘛~~手动(* ̄︶ ̄)