《CSS Secrets》是@Lea Verou最新著作,这本书讲解了有关于CSS中一些小秘密。是一本CSSer值得一读的一本书,经过一段时间的阅读,我、@南北和@彦子一起将在W3cplus发布一系列相关的读后感,与大家一起分享。
问题
通常,我们需要一个动画效果是比较难的或者说使用CSS的transition是不可能实现的。例如,一个卡通移动或一个复杂的进度条。在这种情况下,基于图像的逐帧动画是完美的,但是实现这种效果是具有挑战性的。
在这一点上,你可能会说:难道我们不能使用gif动画?很多情况下,答案是肯定的,gif动画是完美的。然而,他们也有一些缺点,可能在一些地方无法使用:
所有帧中被限制使用256色调色板
他们不能具有透明度,这是一个很大的问题。例如,下图所示的效果是很常见的。
在CSS中有些方面无法做修改,比如时间、重复和停顿等等。一旦生成gif文件,一切都存在文件中,只能通过一个图像编辑器来做修改或生成另一个文件。
上图是一个半透明的进度指示器(dabblet.com),这个是Gif图无法实现的效果。
早在2004年,Mozilla团队就解决了前两个问题,他们通过逐帧动画PNG文件,实现类似静态的方式或者动画的GIF文件。他们把这种设计称为APNG。但是,直到今天,支持APNG的浏览器和制图软件都非常有限。
开发人员甚至通过使用一个Sprite图和使用JavaScript脚本来动态更改background-position值,在浏览器中实现灵活的逐帧动画。你甚至可以找到一个小的库来完成,那么有没有一个简单的方法,使用CSS来实现这样的效果呢?
解决方案
假设我们所有帧动画用到的Sprites的PNG图如下所示:
我们有一个元素,用来制作加载动画(别忘了,在元素里面放些文本,提搞可访性!),元素大小是单帧动画的尺寸:
.loader {
width: 100px;
height: 100px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
text-indent: 999px;
overflow: hidden;
background: url(http://www.w3cplus.com/sites/default/files/blogs/2015/1507/loader.png) 0 0;
}
目前结果如下图所示:
将显示的是第一帧,但是没有动画效果。如果我们对background-position进行不同值的设置,比如-100px,0是第二帧,-200px 0是第三帧等等。这样一来,首先想到的他可能是一个动画:
@keyframes loader {
to { background-position: -800px 0; }
}
.loader {
animation: loader 1s infinite linear;
/*省略其他样式*/
}
然而,正如下图所看到的图片(每167ms),这并不是真正需要的效果:
当我们不需要关键帧之间的插值时,我们最初尝试的逐帧动画效果失败了。
虽然我们还没有实现逐帧动画效果,但实际上我们已经非常接近解决方案。这里有一个秘密,将使用steps()时间函数来替代前面介绍的“Bézier-based”时间函数。
你可能会问,这是什么时间函数?正如上一节介绍的,我们看到的都是有关于“Bézier-based”时间函数来控制关键帧之间的平滑过渡。通常情况之下,在transition和animation都可以实现平滑过渡。然而,在这种情况这下,这种平滑过渡时间函数直接摧毁了我们使用Sprite制作的动画。
steps()时间函数和“Bézier-based”很不一样,他可以根据整个动画的关键帧步骤指定步骤的数量,并且可以对插值进行开关的设置。在CSS中,有关于steps()讨论不太多。至于CSS中有关于时间函数功能,“Bézier-based”才是亲妈的生,很受欢迎,但steps()是后妈的,一直不受人待。然而,在这种情况下,steps()才正是我们需要的。一旦把这个用到加载的动画中,动画才变得正常,也是我们需要的效果:
animation: loader 1s infinite steps(8);
请记住,steps()还可以接受第二个参数,而且这个参数是可选的。start或end(默认的),用来设定每个时间间隔开关,如下图所示:
如果我们只需要一个步骤,也可以通过step-start和step-end快捷键,其相当于steps(1,start)和steps(1,end)。
提示:Simurai给Sprites动画提供了一个使用steps()技术。