【第1433期】CSS3动画实战之多关键帧实现无限循环动效的时间间隔
前言
有趣的噢~ 今日早读文章由交互设计师@泱泱分享。
正文从这开始~~
题目有点绕,源起最近一个项目中所需的一枚loading图标。SVG+CSS3动画做了那么多,真正应用在项目中的机会少之又少,所以,抓住一切机会,即使loading也不能放过,用系统自带菊花有辱我这一年的修炼。在最后完美做成的过程中,解决了两个问题,第一,是非等粗交叉路径的描边动画实现,第二,是多个拼接动画的无限循环问题,后者困扰了许久,所以,当这个问题解决时,急于分享出来,便于其他的设计师小伙伴遇坑时一笑而过,也就是这篇文章的起因了。
1.如果只是简单的描边动画
是的,我是说如果只是如果。先看下要实现的动效。
左边是真身,很简单的一个企业的logo,因为是这种连笔式的,所以本能的反应适合描边动画,动态展示logo绘制过程。右边就是初步想法,绘制过程。看上去很简单,啊哈,来,透过现象看本质。如果这个图标是下面这种,对,就是我曾经的心头好,网易云音乐,因为随手用钢笔画的,没有用布尔运算,所以略显粗糙。这种来个描边动画,那简直是分分钟搞定的事情。
这种描边动效通过定义stroke-dashoffset属性来实现(from 0; to length),非常简单,以前的文章中也写过戳这里戳这里,此处略过。好了,为什么说这种好实现呢,因为描边只要定义三个样式的属性值stroke-linecap、stroke-linejoin和stroke-width就好。
好了,不说别人的logo描边动效多好实现了,来分析一下现在的案例,难度在哪里?非线性啊亲们。如果这个图标是下面这种的:
简单不简单,你就说简单不简单!当然,让甲方爸爸改图标是不现实,如果妥协做个神似形不似版的那还不如菊花了(转行做美工久了,没点强迫症还真是不行)。现在开始,找解决方法,突破,分析问题的能力还是有的。我想到的方法是万能的蒙版。蒙版是个好东西啊,能遮住所有你不想看到的,那直接给路径描边动画加个蒙版就好了呗。
白色蒙版是logo部分,其余黑色的部分遮住。之所以描边给了很粗,就是因为这个logo本身起点处较粗,需要加粗的描边路径经过所有的logo部分。那么,还以为这样就完了?
2.如果只是路径不重合的描边动画
是的,我是说如果只是如果。继续举个栗子,如果是下面这种logo,这事就又简单多了。这是个什么,鬼知道,我就随手搞了一个不等粗的描边而已。
看,上面分析的使用蒙版的思路也是对的吧?轻松实现了不等粗的logo描边效果。
那来看看真实案例,准备好,打脸( ̄ε(# ̄)☆╰╮( ̄▽ ̄///)。效果是这样的!!!
其实 也蛮好理解的,主要是交叉部分出了问题。当第一遍描边动画路过交叉点的时候,已经透过蒙版显示了与描边等宽的部分。
3.来吧,解决问题吧
当然,这点区区的小困难是不会让我放弃的。既然在交叉点那里纠缠不清,那就快刀斩乱麻,把路径剁开就好(暴露了暴力的本性)。
每段各司其职,定义好时间延迟,ok了。简单贴上点代码凑凑字数,CSS部分
/* 定义一个统一的改变stroke-dashoffset值的动画属性*/
@keyframes dash{
to {stroke-dashoffset: 0;}
}
@keyframes dash{
#MH_Path1{
stroke-dasharray:705; /* 705为第一段分段路径的长度*/
stroke-dashoffset:705;
animation: dash 0.7s linear forwards; /* 0s开始,持续0.7s*/
}
#MH_Path2{
stroke-dasharray:645; /* 645为第二段分段路径的长度*/
stroke-dashoffset:645;
animation: dash 0.6s linear 0.7s forwards; /* 延迟0.7s开始,持续0.6s*/
}
#MH_Path3{
stroke-dasharray:108; /* 108为第三段分段路径的长度*/
stroke-dashoffset:108;
animation: dash 0.1s linear 1.3s forwards; /* 延迟1.3s开始,持续0.1s*/
}
DOM部分,因为三部分蒙版图形要被复用一次作为浅灰色logo底图(或者也可以单独导出路径,但毕竟不是最优化的方法),所以我用<symbol>来定义三部分的图形。
<symbol id="logo1">
<path d="" /> <!-d值对应第一部分蒙版的路径-->
</symbol>
<symbol id="logo2">
<path d="" /> <!-d值对应第二部分蒙版的路径-->
</symbol>
<symbol id="logo3">
<path d="" /> <!-d值对应第三部分蒙版的路径-->
</symbol>
<!--定义三部分蒙版,用use标签去引用 -->
<mask id="MH1"><use xlink:href="#logo1" /></mask>
<mask id="MH2"><use xlink:href="#logo2" /></mask>
<mask id="MH3"><use xlink:href="#logo3" /></mask>
<!--底层浅灰色logo-->
<g fill="#ede8e6">
<use xlink:href="#logo1" />
<use xlink:href="#logo2" />
<use xlink:href="#logo3" />
</g>
<path mask="url(#MH1)" id="MH_Path1" d="" /> <!-d值对应第一段分段路径-->
<path mask="url(#MH2)" id="MH_Path2" d="" /><!-d值对应第二段分段路径-->
<path mask="url(#MH3)" id="MH_Path3" d="" /><!-d值对应第三段分段路径-->
经过路径和蒙版的剪切,得到了下面这枚半成品的loading logo。
4.或许,这里才是真正的干货
看起来似乎没有问题了,蒙版拼接+路径拼接,交叉点的问题已然解决。但这不过是SVG+CSS3动效的活学活用,这样的案例随随便便拿一个来都可以分析,不足以成文。
loading图标算是完成了,but just done ,not perfect。我们都知道,加载的时间是不可控的,那么,完成一次描边动画后,理论上应该开始下一轮,animation有个属性是animation-iteration-count,也就是动画播放次数,像我们转圈圈的菊花图标,一般都会定义值为infinite,也就是无限循环,那在这个案例中,问题出在什么地方呢?
再回过头看我们的描边动画属性的定义,我以最有代表性的第二段为例:animation: dash 0.6s linear 0.7s forwards,后面的0.7s是动画延迟开始的时间,在进行无缝拼接的时候,第二段描边动画开始的延迟时间就是第一段动画的时间,同理,第三段动画开始的延迟时间为动画一加动画二,当没有定义执行次数时,默认执行一次。那么,当我们加上这个无限循环的属性值之后,来看看动画变成了什么样子。
看上去乱七八糟,那是因为被切割的每段都在单独执行自己的循环,是的,动画执行的次数可以无限循环,但延迟只能被执行一次,并没有什么特殊的属性可以控制在每个循环开始之前都执行延迟。至少现在没有,但CSS3是不是可以考虑加上新的规范(又在浮想翩翩中,醒醒吧)。通过最常用的infinite属性来控制无限循环的泡沫已然破碎,但这个问题会是无解的么?(又在废话,无解的话这篇文章意义何在?)
现在来想一下,控制延迟时间,除了直接在animation属性中直接写时间值来定义,还有什么方法。对,就是关键帧@keyframes(嗯,俨然又开启了愉快的自问自答模式)。从现在开始,为了infinite属性可以发挥作用,我的三部分动画不再设任何延迟,共用相同的全程动画时间,取而代之通过@keyframes来控制开始和结束的时间。看一下下面这张图或许有助于理解:
下面,我要通过控制@keyframes的时间节点来控制每段动画的时间区间,至于为什么选择45%和90%作为节点,无他,只是估摸了一下每段动画的时间的比例,为了好计算而已。我把整个动画时间周期设计成了2s,DOM部分没有变化,但CSS部分需要完全重新定义。首先,通用的改变stroke-dashoffset值的动画属性的设置已经无用了,因为不是从0%到100%执行,而是打断了,具体的打断方法各个部分又有所不同。
@keyframes MH_Path1{
0%{stroke-dashoffset:705;}
45%, 100%{stroke-dashoffset: 0;}
}
/* 0s开始,持续0.9s,1.1s延迟*/
#MH_Path1{
stroke-dasharray:705;
animation: MH_Path1 2s linear both infinite;
}
@keyframes MH_Path2{
0%, 45% {stroke-dashoffset: 645;}
90%, 100%{stroke-dashoffset: 0;}
}
/* 0.9s开始,持续0.9s,0.2s延迟*/
#MH_Path2{
stroke-dasharray:645;
animation: MH_Path2 2s linear both infinite;
}
@keyframes MH_Path3{
0%, 90% {stroke-dashoffset: 108;}
100%{stroke-dashoffset: 0;}
}
/* 1.8s开始,持续0.2s*/
#MH_Path3{
stroke-dasharray:108;
animation: MH_Path3 2s linear both infinite;
}
在第一段路径中,我在45%处即执行完成了整个描边动画过程,而剩下的从45%到100%部分,因为没有任何变化,所以自然而然的生成了延迟时间,对应定义45%, 100%{stroke-dashoffset:0;};第二段则需要同时控制开始和结束,同理,第三段则只需要控制开始时间。
是时候检验一下效果了:
如果是整个动画需要延迟开始,那就简单的多,只需要在每个animation属性中写入需要延迟的时间就可以了,三个不分彼此,相同的定义,完美共享。
在做这次案例的过程中,最大的收获就是通过定义关键帧实现了多个拼接的动画(或者称之为有延迟效果的动画)的无限循环问题,所以,你压轴!
Demo预览地址:
https://codepen.io/yangyangbeiqiu/pen/mxQYbz
关于本文
作者:@泱泱
原文:
https://juejin.im/post/5abc2d25f265da237b222797
最后,为你推荐
【第1216期】最全最好用的动效落地方法、都帮你总结好了(上)