kekobin/blog

大前端之动画(一)

Opened this issue · 0 comments

写在前面

我们都直到前端的动画分为css3动画和js动画,有些动画可以纯css3实现,有些可以纯js实现,还有一些可以结合两者一起实现。
接下来会分别说明css3动画和js动画的不同用法,和具体的使用案例。

一 css3动画

css3动画分为补间动画和帧动画。
其中,补间动画只有开始和结束两种状态,补齐中间的动画(即中间是一个渐变的过程);帧动画不仅有开始和结束状态,还可以用关键帧来定义中间的状态,做出比较复杂的动画。

transition(过渡)

为补间动画,先来看看它的用法:

transition:transition-property  transition-duration  transition-timing-function  transition-delay

其它属性值没什么好讲的,主要看看transition-property。它可以是三种值:all(任何属性改变都会应用该过渡效果)、none、某个具体属性(该属性在触发过渡的前后有变化才会应用过渡效果)。
很明显可以看到,这种动画是必须要有触发的,其触发方式如下:

  • 伪类触发::hover : focus :checked :active
  • js触发:toggleClass

具体示例,可查看transition.html

注意:transition-property只有在过渡的前后样式中都有设置,才能实现双向的过渡效果,即怎么过渡过来的怎么过渡回去。如果transition中有一个transition-property没有过渡前后都设置,则只能实现单向的过渡效果,返回去的会是生硬的效果。

animation(动画)

animation为帧动画,一般结合@Keyframes使用,先来看看它的用法:

@keyframes anime {
  from {}
  to {}
}

可以是用from to,也可以使用百分比的形式。
而animation常见的使用方式如下:

animation: animation-name  animation-duration  animatino-timing-function  animation-delay animation-iteration-count animation-direction animtion-play-state  animation-fill-mode

其中,animation-name就是上面@Keyframes的anime。
animation-iteration-count: 定义动画的播放次数,其通常为整数,默认是1,;取值为infinite,动画将无限次的播放。
animation-direction:主要用来设置动画播放方向,normal每次循环都是顺序播放,alternate则是正反正反播放。
animtion-play-state:属性是用来控制元素动画的播放状态。runninig(重新播放)、paused(暂停).
animation-fill-mode: 控制动画结束后元素的样式.none(回到最初)、forwards(停留在结束时的状态)、backwords(动画第一帧)、both(轮流应用forwards和backwards).

具体示例,可查看animation.html

注意:如果@Keyframes中使用的是百分比,则每一个百分比后面的属性动画,都是从0s开始的,如 %5、20%、50%、100%,动画总共10s,那么每个阶段的执行状况是: 0s-0.5s、0s-2s、0s-5s、0s-10s。

由上面可以看出transition(用A代替)和animation(用B代替)的差异:

  • A需要借助交互,B既可以自动播放,也可以借助交互;
  • A只有开始和结束状态,B即有开始和结束状态,中间还能定义多个帧动态;
  • A不可以控制暂停和播放,B可以控制暂停和播放;
  • A只能通过transition-timing-function定义固定的几种效果,B的效果通过帧变得多样;
  • A是一次性的,不能重复发生,除非一再触发,B可以无限动画。

不过,B消耗的资源会比较多,所以能使用A的情况下尽量使用A。

硬件加速

我们都知道,css3动画相对于js动画有几个优势:

  • 不占用JS主线程
  • 可以开启硬件加速

其中的硬件加速又叫做 GPU 加速,是利用 GPU 进行渲染,减少 CPU 操作的一种优化方案。由于 GPU 中的 transform 等 CSS 属性不会触发 repaint,所以能大大提高网页的性能。
下面的示例用来显示没开启硬件加速和开启硬件加速的效果对比,从中可以看到两者差别明显:
transform-3d.html

所以使用css3实现复杂动画时,可以适当开启硬件加速,能够开启这个特性的属性包含下面几个:

  • transform
  • opacity
  • filter
  • will-change

而现实很多动画场景并不会使用到这些属性,那怎么办呢?其实还是有办法去诱导浏览器开启加速的:

transform: translateZ(0); 
// or 
transform: translate3d(0, 0, 0);
// or 
transform: rotateZ(360deg);

注意:

  • 过多地开启硬件加速可能会耗费较多的内存,因此什么时候开启硬件加速,给多少元素开启硬件加速,需要用测试结果说话。
  • GPU 渲染会影响字体的抗锯齿效果。这是因为 GPU 和 CPU 具有不同的渲染机制,即使最终硬件加速停止了,文本还是会在动画期间显示得很模糊。

二 JS动画

css动画一般用来实现比较简单的“一次性转换”,为UI元素转换比较小的独立状态。例如从模态框从底部弹出,logo无限循环转动等。 要实现高级效果时,例如弹跳,加速,减速等比较复杂的动画,则使用JS动画。
而JS动画分为匀速动画和缓动动画,下面就详述两种动画的区别和使用场景!

先来了解一些基础知识:
动画的实现原理,是利用了人眼的“视觉暂留”现象,在短时间内连续播放数幅静止的画面,使肉眼因视觉残象产生错觉,而误以为画面在“动”。
动画相关的几个概念:

  • 帧:在动画过程中,每一幅静止画面即为一“帧”。
  • 帧率:即每秒钟播放的静止画面的数量,单位是fps(Frame per second)。
  • 帧时长:即每一幅静止画面的停留时间,单位一般是ms(毫秒)。
  • 跳帧(掉帧/丢帧):在帧率固定的动画中,某一帧的时长远高于平均帧时长,导致其后续数帧被挤压而丢失的现象。
    由于浏览器渲染刷新频率为60fps,所以帧率稳定在60fps左右的流畅动画最让人舒适。

匀速动画

匀速动画就是所谓的线性运动,符合类似 s = vt 公式的计算方式。虽然没有缓动带给人的效果舒适,但在很多场景中还是会用到。
按照前面"基础知识"的概念,现在我们来模拟一下动画的形成过程:
首先,设置一个正方形的方块作为目标,按钮用来触发方块移动:

<button id="btn">start</button>
<div id="box" style="width:100px;height:100px;background:green;position:absolute;left: 30px;top:50px;"></div>

我们的目标是:让这个方块以动画的方式向右移动到300。
当然,最简单的方式肯定是直接设置它的left属性为300px了,但是这样子是没有任何动画效果的,很僵硬的体验。
前面说了,"动画"是由一帧一帧的静止画面组成的,所以我们来产生这个一帧帧的画面:

// 引入"步长"的概念,即每一步走的距离,相当于隔多久出来一张静态画面。这里我们设置为10(当然也可以是别的标准,不过一般都是10),那么我们需要走的步数就是 (300-30)/10=27   
const step = 10;

每一帧的画面出来了,那么所有帧的画面行程就可以通过定时器来完成了。我们先假设每一帧画面花费的时间为 400:

const btn = document.getElementById('btn')
const el = document.getElementById('box')
const step = 10;

btn.addEventListener('click', function() {
       // 每隔400ms向右移动10px,相当于每隔400ms出来一张静止画面,也即"一帧"
	setInterval(function() {
		el.style.left = el.offsetLeft + step + 'px';
	}, 400)
})

可以很明显的看到,方块在一帧帧的向右移动,但我们的肉眼能看出每一帧中间有明显的卡顿。现在我们连续的画面有了,之所以出现肉眼能见的卡顿,很明显是因为我们设置的时间还不够短,当画面出来的频率(也即“刷新频率”)快到我们肉眼看不出来画面与画面之间的切换的时候,即“动画”。让我们把每一帧画面花费的时间设置为40看看:

setInterval(function() {
	el.style.left = el.offsetLeft + step + 'px';
}, 40)

哇,瞬间舒适多了!整个的动画效果就出来了。
不过还有个问题:我们是希望的是向右动画到300位置,而现在是没有限制的,所以要加上这个条件,也很简单,如下:

setInterval(function() {
	// 如果el距离最左边的距离跟300比都小于步长了,说明已经基本接近300那个位置了,即设定el位置为300
	if (Math.abs(300 - el.offsetLeft) < step) {
		el.style.left = 300 + 'px';
	} else {
		el.style.left = el.offsetLeft + step + 'px';
	}
}, 40)

这样,一个匀速动画的实现过程就完成了。
总结来看:“动画”的核心,其实是每一帧画面如何生成,以及"刷新频率"的设定。
综合实例,可以查看slider轮播的实现

由于篇幅有点长,“缓动动画”部分放到下一篇讲解:
大前端之动画(二)
参考:
Web 性能优化-CSS3 硬件加速(GPU 加速)
使用css3实现动画来开启GPU加速
Web动画性能指南