本文提到的例子的在线演示:
https://kylebing.cn/test/scss/
最近在看线上服务器的时候,无意间发现一个好玩的东西,看下图:
一个是阿里云的动画

一个是腾讯云的动画

还有一个是 vultr 的动画

因为我一直想实现这样的功能用于我日记应用的天气图标中,也一直没有思路,真是得来全不费工夫。
所以我好好研究了一下它的实现方式,就只有两种,
前两种实现方式都采用了逐帧动画的原理,底图是一张完整动画帧的拼接


而 Vultr 的动画采用的是拆分部件的方式,将一个 SVG 图标拆分成了三块:阴影、中间层、上层。使用 SVG 的好处是随意缩放不会失真。

动画的实现就是变换呈现给用户的图片位置,来实现动画的展示,这也是所有视频和动画的原理。
阿里云用的是通过 js 来变换画面位置

而腾讯云则用的 css 动画 animation 来实现的变换,我更喜欢 css 来实现的这种方式。更合理。效果更好。

Vultr 使用的也是 CSS 动画,不过只是简单的变换 transition
.deploy__box 设置 :hover 响应样式,
当 :hover 的时候,变换 .deploy__box 下面的 svg 下面的 .svg-illustration-shadow .svg-illustration-bottom .svg-illustration-top 相应的变换


我最终实现的动画效果是这样的,接下来会详细解说是如何实现的:


线上例子:
关于动画的设置就不多说了,可以去百度 css animation 的使用。
这里只说比较特殊的地方。
一张逐帧动画的拼接图,比如腾讯云的 :

我们都已经知道 animation 动画是从一个状态到另一个状态的过渡,比如从 a 点到 b 点,默认情况下,这个过渡的过程是线性的,也就是均速的。
以现在这个例子来说,就是从背景图的最上端变换到最下端,而变化的行程,就是
图标高度 = 60px
图标高度 x 图中的图标数量 = 60px * 16 = 960px
也就是说,实现这个图标动画,需要图标背景从 0 0 变换到 0 -960px ,第一个数是横坐标值,第二个数是纵坐标值。
既然这个变换过程是线性的,那它变化就是匀速的,也就会是这样:

那么如何改变上面这种样子呢,这就需要更深入的了解一下 css 动画中的动画曲线了。
什么是动画曲线呢,就是在 1x1 的坐标系中,横坐标代表时间,纵坐标代表距离,表达距离与时间关系的这么一种线段,就是动画曲线。
动画曲线有一些预置的,比如 linear easy-in-out 都代表着一种

你可以从这个网站中编辑自己需要的动画曲线:
上图中的动画效果就是图二的曲线。
如果我们需要它逐帧的变换,那么它的曲线就应该是阶梯状的,像这样。

那么如何生成这种曲线呢, css 有它的方法,就是 steps(步数)

animation: icon-animation-enter 0.3s steps(16) forwards
此时的动画就不再是匀速的变换了,而是跟幻灯片似的,变换 16 次,这样也实现了动画图标背景的播放。
比如腾讯云的背景是由 16 张图标合成的,也就是需要变换16次才算跟底片吻合。
先做图标的基础样式,比如做一个 60x60 的图标。这里我用 scss 写,为了方便定义一些变化的值,不了解的可以查看我之前写的 scss 教程,或者直接可以自己计算。
完整的代码 html + css 代码是这样的
<div class="container">
<div class="header">Animation Icons - 腾讯云div>
<div class="content">
<div class="animation-icon lock">div>
<div class="animation-icon money">div>
<div class="animation-icon setup">div>
<div class="animation-icon weixin">div>
<div class="animation-icon cloud">div>
<div class="animation-icon game">div>
div>
div>
// 动画图标 ANIMATION ICONS
$animation-duration: 0.3s; // 动画的间隔
$icon-width: 60px; // 图标的大小
$frame-count: 16; // 动画的帧率
.animation-icon {
width: $icon-width;
height: $icon-width;
background-position: top;
background-size: 100% auto;
background-repeat: no-repeat;
@extend .animation-icon-leave; // 默认图标动画
border: 1px solid transparent; // 这个只是为了标识鼠标悬停的位置,实际使用中删除即可
&:hover {
border: 1px solid #eee; // 这个只是为了标识鼠标悬停的位置,实际使用中删除即可
@extend .animation-icon-enter // hover 时的图标动画
}
}
// 背景图
.lock{background-image: url(animation-icons/tencent.png);}
.money{background-image: url(animation-icons/money.png);}
.setup{background-image: url(animation-icons/setup.png);}
.game{background-image: url(animation-icons/game.png);}
.cloud{background-image: url(animation-icons/cloud.png);}
.weixin{background-image: url(animation-icons/weixin.png);}
// 进入动画
.animation-icon-enter {
-webkit-animation:icon-animation-enter $animation-duration steps($frame-count) forwards;
animation: icon-animation-enter $animation-duration steps($frame-count) forwards
}
@-webkit-keyframes icon-animation-enter {
0% {background-position: 0 0}
to {background-position: 0 $icon-width * $frame-count * -1}
}
@keyframes icon-animation-enter {
0% {background-position: 0 0}
to {background-position: 0 $icon-width * $frame-count * -1}
}
// 离开动画
.animation-icon-leave {
-webkit-animation: icon-animation-leave $animation-duration steps($frame-count) forwards;
animation: icon-animation-leave $animation-duration steps($frame-count) forwards
}
@-webkit-keyframes icon-animation-leave {
0% {background-position: 0 $icon-width * $frame-count * -1}
to {background-position: 0 0}
}
@keyframes icon-animation-leave {
0% {background-position: 0 $icon-width * $frame-count * -1}
to {background-position: 0 0}
}