CSS绘制序列帧动画

目前实现帧动画的方案大概分为三种

  • GIF图片: UI直接合成GIF图片,很多场景下为了省事,都是这么做的。

  • CSS帧动画

  • JS帧动画

一般选择GIF和CSS帧动画方案,下面介绍下CSS帧动画如何实现。

原理: 使用sprite图和background-position,再加上animationstep函数,可以实现序列帧动画。

但是在移动端不推荐,因为移动端进行适配之后会出现序列帧抖动

序列帧动画 实现

示例代码地址

tips.png是按照每帧的顺序已经合成好的一张雪碧图。一共25帧

.ex1 {
        width: 750px;
        height: 41px;
        background: url("tips.png");
        overflow: hidden;
        animation: tips 2s infinite steps(25);
    }

    @keyframes tips {
        from {
            background-position: 0 0px;
        }
        to {
            background-position: 0 -1025px;
        }
    }

使用step函数进行步进,你有多少帧动画,step步进数量就是多少。

steps() 会根据你指定的步进数量,把整个动画切分为多帧,而且整个动画会在帧与帧之间硬切,不会做任何插值处理。通常,这种硬切效果是我们极力避免的,因此我们很少听到关于steps() 的讨论。在CSS调速函数的世界里,基于贝塞尔曲线的调速函数就像是处处受人追捧的白天鹅,而steps()则是旁人避之唯恐不及的丑小鸭。

不过,在这个案例中,后者却是我们通向成功的关键。一旦把整个动画的代码修改为下面的形式,这个加载提示就瞬间变成我们想要的样子了:

可缩放的序列帧动画

考虑这样的一个场景:提供一个序列帧图片,有什么办法可以实现一张sprite图做成可缩放的序列帧动画,

一开始想到就是background-size,这个思路没有错,但是使用绝对像素就不对了。

当然,这种问题网上肯定有大神解答,腾讯前端团队就开发出一个插件gka进行制作可缩放的雪碧帧动画传送门

原理剖析: 当背景图片设置 background-size:100% 100% 时,百分比是以元素宽高为基准的,应用到雪碧图上会将整张雪碧图拉伸填充整个元素,

例如我们有一个5帧的雪碧图,当我们设置background-size: 100% 500%时,高度就可以只显示一张图片,同理,background-position也可以设置为百分比,动画的过程从0%500%,刚刚好显示完五张帧图片。

代码示例:

.btn2 {
    width: 600px;
    height: 600px;
    background: url("btn.png");
    background-size: 100% 2500%;
    overflow: hidden;
    animation: btn2 1s infinite steps(25);
}

@keyframes btn2 {
    from {
        background-position: 0 0;
    }
    to {
        background-position: 0 -2500%;
    }
}

移动端序列帧动画

移动端动画抖动的原因

在pc端上的时候,计算的就是绝对像素,不会出现抖动的问题,但是在移动端的时候,因为有时候进行移动端适配的时候对图片大小进行了缩放,导致在计算position的时候,因为缩放的原因出现了小数,终端的光点都是以自然数的形式出现的,这里需要做取整处理。取整一般是三种方式:

  • round

  • ceil

  • floor

自然就出现了盈亏,导致我们在看到的时候因为位置有偏差,所以发生抖动。

方案一:单帧图片

就是一帧对应一张图片,不使用雪碧图,这样增加了HTTP请求,不推荐

方案二:transform:scale()

对于不同屏幕的尺寸,计算出原图片需要适配的图片大小比例,根据这个比例设置缩放。

.steps_anim {
    position: absolute;
    width: 360px;
    height: 540px;
    background: url(//misc.aotu.io/leeenx/sprite/m.png) 0 0 no-repeat;
    background-size: 1800px 540px;
    top: 50%;
    left: 50%;
    transform-origin: left top;
    margin: -5.625rem 0 0 -5.625rem;
    transform: scale(.5);
    animation: step 1.2s steps(5) infinite;
}

@keyframes step {
    100% {
        background-position: -1800px;
    }
}

/* 写断点 */
@media screen and (width: 320px) {
    .steps_anim {
        transform: scale(0.4266666667);
    }
}

@media screen and (width: 360px) {
    .steps_anim {
        transform: scale(0.48);
    }
}

@media screen and (width: 414px) {
    .steps_anim {
        transform: scale(0.552);
    }
}

这种方式就是比较麻烦了,每一个尺寸都需要一个断点,聪明人一般都不这么干,改进一下,使用js计算

css:

.steps_anim {
    position: absolute;
    width: 360px;
    height: 540px;
    background: url(//misc.aotu.io/leeenx/sprite/m.png) 0 0 no-repeat;
    background-size: 1800px 540px;
    top: 50%;
    left: 50%;
    transform-origin: left top;
    margin: -5.625rem 0 0 -5.625rem;
    transform: scale(.5);
    animation: step 1.2s steps(5) infinite;
}

@keyframes step {
    100% {
        background-position: -1800px;
    }
}

js:

document.write("<style id='scaleStyleSheet'>.steps_anim {scale(.5); }</style>"); 
function doResize() {  
    scaleStyleSheet.innerHTML = ".steps_anim {-webkit-transform: scale(" + (document.documentElement.clientWidth / 750) + ")}"; 
}
window.onresize = doResize; 
doResize();

你会发现,在一个css代码中使用的js,总归感觉是不太好的,所以,肯定还会有更好的方法

方案三:svg缩放

我们的目的是想让img随着我们的适配进行缩放,这里就可以使用svg和img标签一样的缩放性质,缺点是不利于自动化工具的处理。

<svg viewBox="0, 0, 360, 540" class="steps_anim">
  <image xlink:href="//misc.aotu.io/leeenx/sprite/m.png" width="1800" height="540" />
</svg>
.steps_anim {
    position: absolute;
    width: 9rem;
    height: 13.5rem;
    top: 50%;
    left: 50%;
    margin: -5.625rem 0 0 -5.625rem;
    image {
        animation: step 1.2s steps(5) infinite;
    }
}
@keyframes step {
    100% {
        transform: translate3d(-1800px, 0, 0);
    }
}

参考

  • 《CSS揭秘》

Last updated