查看原文
其他

【第1433期】CSS3动画实战之多关键帧实现无限循环动效的时间间隔

泱泱 前端早读课 2019-06-24

前言

有趣的噢~ 今日早读文章由交互设计师@泱泱分享。

正文从这开始~~

题目有点绕,源起最近一个项目中所需的一枚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

最后,为你推荐


【工具】一款简单的高效的帧动画生成工具-GKA


【第1216期】最全最好用的动效落地方法、都帮你总结好了(上)


【第1223期】最全最好用的动效落地方法、都帮你总结好了(下)

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存