使用CSS3实现60FPS动画

发布于 大漠

使用动画在移动应用程序中很容易的。如果您遵循我们的建议,移动应用程序中使用动画变得很简单。

虽然现在很多人在移动应用程序中使用CSS3 Animation来制作动画,当然很多人也不这么做。很多最佳实践,还是不断的被忽视。出现这种情况主要是因为仍有许多人并不真正了解这些最佳实践存在的真正原因,因此没有大力的支持。

面对这么多的终端设备,如果不考虑优化你的代码,你最终将会交付一个水平一般的效果,从而失去一份最高份额的市场占有率。

这篇文章我们想给你提供一个正确利用和使用CSS3的特性,这样做我们首先需要了解一些事情。

理解时间轴

什么是浏览器的渲染?这个非常简单的时间称为关键渲染路径(Critical Rendering Path)。

时间轴

实现平滑的动画,我们需要关注于改变属性影响合成的步骤,而不是在前一层添加这个压力。

Style

时间轴

浏览器开始计算应用中的元素样式——重新计算样式风格。

Layout

时间轴

接下来的一层,浏览器会为这些元素生成形状和位置——布局。浏览器会给页面设置属性,比如widthheight以及marginlefttoprightbottom等。

Paint

时间轴

浏览器将开始给每个元素填充像素。它们使用属性有:box-shadowborder-radiuscolorbackground-color等。

Composite

浏览器开始在屏幕中绘制这些图层(Layout)。

时间轴

现代浏览器可以利用transformopacity绘制很好的动画,其中共有四个让动画更好的属性:

  • 位置(Position)transform: translateX(n) translateY(n) translateZ(n)
  • 缩放(Scale): transform: scale(n)
  • 旋转(Rotation): transform: rotate(ndeg)
  • 透明度(Opacity): opacity: n

如何实现每秒60FPS

考虑到这一点,是时候撸起袖子开始工作了。

让我们先从HTML开始,我们创建一个非常简单的结构和布局并且将app-menu类放在layout类里。

<div class="layout">
    <div class=”app-menu”></div>
    <div class=”header”></div>
</div>

时间轴

错误之处

.app-menu {
  	left: -300px;
  	transition: left 300ms linear;
}

.app-menu-open .app-menu {
  	left: 0px;
  	transition: left 300ms linear;
}

看到这些属性改变了吗?你应该避免在动画中使用lefttoprightbottom这四个属性。这些属性并不能帮我们创建一个流体动画,因为浏览器每次都会创建布局(重排),而且还会影响到他们的后代元素。

最终效果像这样:

时间轴

动画效果并不光滑。我们通过DevTools Timeline工具来查看发生了什么,检查后的结果如下所示:

时间轴

清楚显示了FPS是不规则的,性能相当的差。

使用Transform

.app-menu {
  	-webkit-transform: translateX(-100%);
  	transform: translateX(-100%);
  	transition: transform 300ms linear;
}

.app-menu-open .app-menu {
  	-webkit-transform: none;
  	transform: none;
  	transition: transform 300ms linear;
}

transform属性效果的合成(Composite)。这里告诉浏览器将会绘制(Paint),尽快准备好动画开始,所以有更少的时间打断动画的渲染。

时间轴

Timeline显示的结果如下:

时间轴

结果开始有所好转,FPS更普通,因此动画是平滑的。

FPS开始更好的结果,更多的常数,动画也顺畅。

在GPU上运行动画

那么我们再提高一层,真正的使动画像黄油一样平滑,这样一来我们就要使用GPU来渲染动画。

.app-menu {
  	-webkit-transform: translateX(-100%);
          transform: translateX(-100%);
  	transition: transform 300ms linear;
  	will-change: transform;
}

尽管transformZ()或者translate3d()仍将需要在一些浏览器中回退,will-change具有这方面的特性。什么中以让它们促进元素提高到另一层,因此浏览器不需要考虑容器布局的渲染或绘制。

时间轴

看看是平滑的吗?Timeline效果如下:

时间轴

动画的FPS越是常数动画呈现的将会越快。但仍有很多片段(Frame)需要运行。

还记得一开始创建的HTML结构吗?让我们使用JavaScript来控制.app-menudiv:

function toggleClassMenu() {
  	var layout = document.querySelector(".layout");
  	if(!layout.classList.contains("app-menu-open")) {
    	layout.classList.add("app-menu-open");
  	} else {
    	layout.classList.remove("app-menu-open");
  	}
}
var oppMenu = document.querySelector(".menu-icon");
oppMenu.addEventListener("click", toggleClassMenu, false);

这里的问题是,通过JavaScript给.layoutdiv添加类名,浏览器将要重新计算样式风格——这将直接影响渲染性能。

像黄油一样平滑的60FPS的解决方案

如果已经创建窗口外区域的菜单,创建一个独立(isolated)区域将确保只影响真正想要动画的元素。

因此,前面的HTML结构需要做一下调整:

<div class="menu">
    <div class="app-menu"></div>
</div>
<div class="layout">
    <div class="header"></div>
</div>

现在控制菜单的状态可以有一个稍微不同的方式。在动画过渡结束时删除类名,实现这样的效果,可以使用JavaScript的transitionend函数:

function toggleClassMenu() {
  	myMenu.classList.add("menu--animatable");
  	myMenu.classList.add("menu--visible");
  	myMenu.addEventListener("transitionend", OnTransitionEnd, false);
}
function OnTransitionEnd() {
  	myMenu.classList.remove("menu--animatable");
}
var myMenu = document.querySelector(".menu");
var oppMenu = document.querySelector(".menu-icon");
oppMenu.addEventListener("click", toggleClassMenu, false);

把这一切放在一起,检查结果。这是一个CSS3完全支持的例子:

.menu {
  	position: fixed;
  	left: 0;
  	top: 0;
  	width: 100%;
  	height: 100%;
  	overflow: hidden;
  	pointer-events: none;
  	z-index: 150;
}

.menu—visible {
  	pointer-events: auto;
}

.app-menu {
  	background-color: #fff;
  	color: #fff;
  	position: relative;
 	max-width: 400px;
  	width: 90%;
  	height: 100%;
  	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
  	-webkit-transform: translateX(-103%);
  	transform: translateX(-103%);
  	display: flex;
  	flex-direction: column;
  	will-change: transform;
  	z-index: 160;
  	pointer-events: auto;
}

.menu—-visible.app-menu {
  	-webkit-transform: none;
  	transform: none;
}

.menu-—animatable.app-menu {
  	transition: all 130ms ease-in;
}

.menu--visible.menu—-animatable.app-menu {
  	transition: all 330ms ease-out;
}

.menu:after {
  	content: ‘’;
  	display: block;
  	position: absolute;
  	left: 0;
  	top: 0;
  	width: 100%;
  	height: 100%;
  	background: rgba(0,0,0,0.4);
  	opacity: 0;
  	will-change: opacity;
  	pointer-events: none;
  	transition: opacity 0.3s cubic-bezier(0,0,0.3,1);
}

.menu.menu--visible:after{
  	opacity: 1;
  	pointer-events: auto;
}

时间轴

时间轴的结果告诉我们:

时间轴

像黄油一样平滑,看到了?

本文根据@José Rosário的《Smooth as Butter: Achieving 60 FPS Animations with CSS3》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://medium.com/outsystems-experts/how-to-achieve-60-fps-animations-with-css3-db7b98610108#.midp8pp5r