跟随重叠动画的3种实现方法:Sass,GreenSock 和 Web Animations API

1,657 阅读7分钟
原文链接: svgtrick.com

开始之前,先来了解下跟随重叠动画是咋回事,说到跟随重叠动画动画就不得不提到著名的迪士尼十二动画原则中的跟随和重叠这个原则。即表示事情并不总在同一时间发生。当一辆车从急刹到停下,车子会向前倾、有烟从轮胎冒出来、车里的司机继续向前冲。

要创造一个重叠动作的感觉,我们可以让元件以稍微不同的速度移动到每处。一些按钮和元件以不同速率运动,整体效果会比全部东西以相同速率运动要更逼真,

通俗点说就是多个元素的动画是一样的,只是它们需要有规律地安排在时间轴的不同位置,即延迟执行每一个元素的一种动画的表现形式,一图胜千言:

这种动画形式在网页或者是APP交互中用的也比较多,可以去这个地址看看。Google在它们动画设计原则中也提到了这个原则在交互设计中的应用,可以去这里查看,也具体给出了引导:

通过上面相信你已经了解了跟随重叠动画动画是怎么一回事了。

下面来看看在web开中,实现这种动画的三种方法。

首先,来分析下我们要实现这种动画的表现形式

这里有四个圆圈,在可视区域内只显示3个圆圈,第一个圆圈在可视区域外。接下来要做的动画效果是,第四个圆圈往右边移到可视区域外,而第一个圆圈从左边移入道可视区域内,然后不停的重复这个过程。

先来准备基本的html结构:

<div id="container">
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</div>

CSS:

#container {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 160px;
  height: 40px;
  display: block;
  overflow: hidden;
}
span {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #4df5c4;
  display: inline-block;
  position: absolute; 
  transform: translateX(0px);
}

先来使用CSS实现一个简单的动画效果,简单的使用transform移动整个圆圈:

代码地址

现在动画看起来有点生硬奇怪,对吧?因为缺少了一个上面提到的:跟随重叠的动画原则。具体到这个动画效果就是,每个圆圈在移动的时候要有先后顺序,而不是像上面一样一起移动。要实现这种效果,你可能首先想到的方法是使用CSS中的animation-delay这个属性来完成。OK,那就来使用它是圆圈每一个在运动的时候都给一个延迟的效果:

明显是有问题的,原因是animation-delay只在动画刚开始的起到了延迟效果。它不会在每次动画迭代完的时候自动的增加延迟的时间,所以才导致如上图所示的动画不同步的结果。每个圆圈动画延迟的时间会随着动画循环的次数而增加,如下图所示:

SASS解决方案

为了解决这个问题,可以把动画的延迟放到动画之中去。CSS关键帧动画是用百分比来指定的,所以我们可以把动画延迟的时间也计算到关键帧中去,即用关键帧来包含动画的延迟。举个例子,如果设定了动画的执行时间是1秒,那么在关键帧中表示在0%的开始动画,在20%的时候执行和0%同样的值,在80%的时候结束动画,100%也执行结束动画的值,就表示你的动画首先延迟0.2秒,然后执行动画0.6秒,最后再延迟0.2秒。

具体到这个圆圈动画,我们想要达到的效果是每个圆圈在执行动画之间有0.15秒的延迟,然后实际执行的动画时间是0.5秒,整个过程需要的时间是1秒。这意味着第四个圆圈的动画延迟是0秒,执行动画0.5秒,然后再等待0.5秒。第二个圆圈等待0.15秒,然后执行动画0.5秒,再等待0.35秒。其它圆圈一次类推。

要达到这个目的,需要有四个关键着动画(keyframes):1和2关键帧表示交错延迟,2和3关键帧用来实际执行动画,3和4则用来做最后的延迟。关键的技巧是要了解如何将所需要的时间转换为关键帧的百分比,计算比较简单。比如:第二个圆圈需要延迟0.15*2=0.3秒,然后执行动画0.5秒。我们知道整个动画的时间是1秒,所以关键帧的百分比可以这样来计算:

0s = 0%
0.3s = 0.3 / 1s * 100 =  30%
0.8s = (0.3 + 0.5) / 1s * 100 = 80%
1s = 100%

最后计算结果如下图所示:

在整个动画中,包括动画的延迟和动画的执行,只需1秒钟,这样动画就不会出现不同步的情形了。

幸好,诸如SASS等预编译CSS语言提供简单的循环计算功能,使用这个功能可以很简单的实现我们需要的自动计算关键帧百分比的需求。还可以定义一些变量来方便修改测试动画效果:

@mixin createCircleAnimation($i, $animTime, $totalTime, $delay) {      
  @include keyframes(circle#{$i}) {
    0% {              
      @include transform(translateX(0));            
    }
    #{($i * $delay)/$totalTime * 100}% {     
      @include transform(translateX(0));            
    }          
    #{($i * $delay + $animTime)/$totalTime * 100}% {     
      @include transform(translateX(60px));            
    }          
    100% {
      @include transform(translateX(60px));             
    }
  }      
}

$animTime: 0.5s;
$totalTime: 1s;
$staggerTime: 0.15s;

@for $i from 0 through 3 {
  @include createCircleAnimation($i, $animTime, $totalTime, $staggerTime); 
  span:nth-child(#{($i + 1)}) {
    animation: circle#{(3 - $i)} $totalTime infinite;
    left: #{$i * 60 - 60 }px;
  }
}

最终效果如下:

代码地址

SASS方法注意事项

首先,你需要确保定义的交错延迟时间和动画时间的比值不会太长,与总的动画时间重叠,否则计算就会出错。

其次,这个方法会编译出大量的CSS代码,特别是当你选择兼容不同浏览器的时候,产生的代码更多。在这个简单动画中还可以接受,如果你有大量的动画,使用这种方法可就要考虑下了。这种情况下,使用诸如GreenSock等动画库可能是一个更好的选择。不过,使用CSS来编写动画仍然是一件非常酷的事情。

使用更高效的方法来制作动画

上面使用SASS的方法来实现的动画效果,想必你也看到了这种方法的冗长不便,下面就来展示下使用GreenSock中的TweenMax的强大的动画时间轴管理和staggerTo方法来实现这个动画是多么的简单高效:

代码地址

简单的几行代码就可以实现:

var tl = new TimelineMax({ 
  repeat: -1,
  repeatDelay: 0.05 //delay the repeat by 0.05 seconds so that it's a total of 1 second long (last stagger is delayed 0.45 seconds, and the tween is 0.5 seconds long = 0.95 seconds total)
}); 

tl.staggerFromTo(
  'span',   //target (all span tags)
   0.5,     //duration (0.5 seconds)
   {x:0},   //"from" values
   {x:60},  //"to" values
   -0.15    //stagger amount (seconds between each start time)
);

首先是声明来一个TimelineMax的实例,使用repeat: -1设置动画为无限循环方式,repeatDelay: 0.05设置动画的延迟时间为1秒。

然后使用staggerFromTo方法来执行圆圈的动画,从0移动到60的位置,执行时间为0.5秒,而且在每一个圆圈开始动画前0.15秒就执行动画。

使用Web Animations API来实现

Web Animations API是W3C新推出的一种动画接口,不过现在还不是很成熟,浏览器支持的也不是很好。下面就使用它来实现下,用到的计算方法和SASS中的一样:

let totalTime = 1;
let circleTime = 0.5;
let staggerTime = 0.15;


let circles = document.querySelectorAll('span');
for(let x=0; x<circles.length;x++){  
  let frames = [
    { transform: 'translateX(0px)', offset: 0},
    { transform: 'translateX(0px)', offset: x * staggerTime / totalTime},
    { transform: 'translateX(60px)', offset: (x * staggerTime + circleTime) / totalTime },
    { transform: 'translateX(60px)', offset: 1 }
   ];

  let circle = circles[circles.length - x - 1];
  circle.animate(frames,{
    duration: totalTime * 1000,
    iterations: Infinity,
    easing: "cubic-bezier(0.250, 0.460, 0.450, 0.940)"
  })
}

代码地址

通过上面使用三种方法来实现一个简单动画,可以看出使用GreenSock来实现动画是多么的方便高效,用起来吧!

本文主要是从Repeatable, Staggered Animation Three Ways: Sass, GSAP and Web Animations API这篇文章整理而来,有删减,有疏漏或者理解不到位的地方,还请多多指教!