简单说下逐帧动画
逐帧动画是个啥呢??
先让我们看一下下面两个网站,上面那动画,唰唰唰,哗哗哗,啦啦啦的,看着心痒的我就忍不住 F12 了一下,然后发现都用到了逐帧动画。
简单来说就是利用一张动画分解的 sprite 图,通过 js 脚本改变图片的 css 属性或使用 css3 的 animation 属性来实现动画的效果。
1. 利用 css3-animation 来制作逐帧动画
首先看一下对 animation 属性各个浏览器的支持情况,IE 10+、Firefox 以及 Opera 支持 animation 属性。
Safari 和 Chrome 支持替代的 -webkit-animation 属性。(IE9 以下不支持)
animation 其他几个动画属性就不多说了,这次要用到的是 animation-timing-function 里的 steps()阶段函数。
animation-timing-function:ease | linear | ease-in | ease-out | ease-in-out | step-start | step-end | steps(
<integer>
[, [ start | end ] ]?) | cubic-bezier(<number>
,<number>
,<number>
,<number>
)
取值:
linear:线性过渡。等同于贝塞尔曲线(0.0, 0.0, 1.0, 1.0)
ease:平滑过渡。等同于贝塞尔曲线(0.25, 0.1, 0.25, 1.0)
ease-in:由慢到快。等同于贝塞尔曲线(0.42, 0, 1.0, 1.0)
ease-out:由快到慢。等同于贝塞尔曲线(0, 0, 0.58, 1.0)
ease-in-out:由慢到快再到慢。等同于贝塞尔曲线(0.42, 0, 0.58, 1.0)
step-start:等同于 steps(1, start)
step-end:等同于 steps(1, end)
steps(<integer>
[, [ start | end ] ]?):接受两个参数的步进函数。第一个参数必须为正整数,指定函数的步数。第二个参数取值可以是 start 或 end,指定每一步的值发生变化的时间点。第二个参数是可选的,默认值为 end。
cubic-bezier(<number>
, <number>
, <number>
, <number>
):特定的贝塞尔曲线类型,4 个数值需在[0, 1]区间内
所以这个 steps()函数是个啥呢,下图是 W3C 对这个函数的图解

大概意思呢就是这个函数可以将动画分成 n 等分,start 和 end 决定动画执行是从前还是从后。
例如我们有上面这么一张逐帧动画图,因为动画是从左往右,所以先写一个关键帧 keyframes 名字叫 sprite-animate-go,背景起始位置从 0%到 100%,具体代码如下:
.go {
background: url(https://file.gaoquanquan.com/blog/16925150876645.png) no-repeat;
width: 50px;
height: 65px;
margin: 0 auto;
animation: sprite-animate-go 1.5s steps(50) infinite;
-webkit-animation: sprite-animate-go 1.5s steps(50) infinite;
}
@keyframes sprite-animate-go {
from {
background-position: 0%;
}
to {
background-position: 100%;
}
}
@-webkit-keyframes sprite-animate-go {
from {
background-position: 0%;
}
to {
background-position: 100%;
}
}
效果如下:

2. 使用 js 脚本操作 css 属性
上面两个网站都是用的脚本实现的,因为使用 js 可以兼容大部分浏览器,是最保险的方法。思路就很简单了,帧图片外面加个固定大小的 div,overflow: hidden, 然后用 js 不断改变帧图片的 translate 值。
代码如下:
<div class="intro-container">
<img src="media/16925150876645.png" />
</div>
.intro-container {
width: 50px;
height: 65px;
overflow: hidden;
}
window.onload = function () {
var introSprite = $('.intro-container')
spriteAnim(introSprite, 25, 59, 33, 2, false)
}
function spriteAnim(container, fps, frames, col, row, loop) {
var intro = container.find('.intro')
var width = container.width()
var height = container.height()
var frame = 1
var x = (y = 0)
intro.css({ transform: 'translate3d(0, 0, 0)', '-webkit-transform': '-webkit-translate3d(0, 0, 0)' })
setTimeout(function () {
playSpriteAnim(intro, fps, width, height, frames, col, row, frame, x, y, loop)
}, 1000 / fps)
}
function playSpriteAnim(intro, fps, width, height, frames, col, row, frame, x, y, loop) {
x++
if (x >= col) {
x = 0
y++
}
var bgX = '-' + x * width + 'px'
var bgY = '-' + y * height + 'px'
intro.css({ transform: 'translate3d(' + bgX + ', ' + bgY + ', 0)', '-webkit-transform': '-webkit-translate3d(' + bgX + ', ' + bgY + ', 0)' })
frame++
if (frame >= frames) {
if (loop) {
frame = 1
x = y = 0
} else {
intro.closest('.intro-container').trigger('end')
return
}
}
setTimeout(function () {
playSpriteAnim(intro, fps, width, height, frames, col, row, frame, x, y, loop)
}, 1000 / fps)
}
PS,那么问题来了,
1.有了 gif 干嘛还要费劲用逐帧动画?
区别大概在于可以控制逐帧动画的快慢和暂停,而且 gif 动画不够清晰,有些时候达不到我们的要求。
2.怎么得到一张逐帧图片嘞?
确切的说应该是从 AE 导出的序列帧图组怎么合成一张图嘞?这个问题好像不是应该我考虑的,但你们不想知道吗?不想吗?想吗?吗?我可是想好久呢,就是
美图秀秀啊
虽然一次最多拼 30 张,但真的挺好用的…
PREV
用 snap.svg.js 做一个笑脸头像变化效果
NEXT