深入理解CSS动画animation

前面的话

  transition过渡是通过初始和结束两个状态之间的平滑过渡实现简单动画的;而 animation 则是通过关键帧 @keyframes 来实现更为复杂的动画效果。本文将介绍关于 animation 动画的相关知识

 

定义

  和 transition 类似,animation 也是一个复合属性,包括 animation-name、animation-duration、animation-timing-function、animation-delay、animation-iteration-count、animation-direction、animation-play-state、animation-fill-mode 共 8 个子属性

  [注意]IE9- 不支持;safari4-8、IOS3.2-8.4、android2.1-4.4.4 需要添加 -webkit- 前缀

animation-name: 动画名称 (默认值为 none)
animation-duration: 持续时间 (默认值为 0)
animation-timing-function: 时间函数 (默认值为 ease)
animation-delay: 延迟时间 (默认值为 0)
animation-iteration-count: 循环次数 (默认值为 1)
animation-direction: 动画方向 (默认值为 normal)
animation-play-state: 播放状态 (默认值为 running)
animation-fill-mode: 填充模式 (默认值为 none)
div{
    width: 300px;
    height: 100px;
    background-color: pink;
    animation-name: test;
    animation-duration: 3s;
    animation-timing-function: ease;
    animation-delay: 0s;
    animation-iteration-count: infinite;
    animation-direction: normal;
    animation-play-state: running;
    animation-fill-mode: none;
}
/* 关于 keyframes 关键帧的内容稍后介绍     */
@keyframes test{
    0%{background-color: lightblue;}
    30%{background-color: lightgreen;}
    60%{background-color: lightgray;}
    100%{background-color: black;}
}

 

关键帧

  animation 制作动画效果需要两步,首先用关键帧声明动画,再用 animation 调用动画

  关键帧的语法是以 @keyframes 开头,后面紧跟着动画名称 animation-name。from 等同于 0%,to 等同于 100%。百分比跟随的花括号里面的代码,代表此时对应的样式

@keyframes animation-name{
    from | 0%{}
    n%{}
    to | 100%{}}

【1】百分比顺序不一定非要从 0% 到 100% 排列,最终浏览器会自动按照 0%-100% 的顺序进行解析

  [注意]0% 不可以省略百分号

@keyframes test{
    100%{background-color: black;}
    60%{background-color: lightgray;}
    30%{background-color: lightgreen;}
    0%{background-color: lightblue;}
}
div{
    width: 300px;
    height: 100px;
    background-color: pink;
    animation-name: test;
    animation-duration: 3s;
    animation-iteration-count: infinite;
}

【2】如果存在负百分数或高于 100% 的百分数,则该关键帧将被忽略

/* -20% 和 120% 对应的代码无效*/
@keyframes test{
    -20%{background-color: red;}
    0%{background-color: lightblue;}
    30%{background-color: lightgreen;}
    60%{background-color: lightgray;}
    100%{background-color: black;}
    120%{background-color: yellow;}
}

【3】如果 0% 或 100% 不指定关键帧,将使用该元素默认的属性值

/* 0% 和 100% 对应的颜色是默认值 pink*/
@keyframes test{
    30%{background-color: lightgreen;}
    60%{background-color: lightgray;}
}

【4】若存在多个 @keyframes,浏览器只识别最后一个 @keyframes 里面的值 

/* 后面覆盖前面 */
@keyframes test{
    0%{background-color: lightblue;}
    30%{background-color: lightgreen;}
    60%{background-color: lightgray;}
    100%{background-color: black;}
}
@keyframes test{
    0%{background-color: blue;}
    30%{background-color: green;}
    60%{background-color: gray;}
    100%{background-color: black;}
}

【5】空的 keyframes 规则是有效的,它们会覆盖前面有效的关键帧规则

/* 后面覆盖前面 */
@keyframes test{
    0%{background-color: lightblue;}
    30%{background-color: lightgreen;}
    60%{background-color: lightgray;}
    100%{background-color: black;}
}
@keyframes test{
}

 

动画属性

动画名称

animation-name

  值: none | <single-animation-name> [, <single-animation-name>]*

  初始值: none

  应用于: 所有元素

  继承性: 无

<single-animation-name>: none | 自定义动画名称

【1】如果多个动画试图修改相同的属性,那么动画列表的后面覆盖前面

/* animation-name 的顺序是 test1,test2,且它们修改的是同样的属性,后面覆盖前面,所以 test2 有效,test1 无效 */
div{
    width: 300px;
    height: 100px;
    background-color: pink;
    animation-name: test1,test2;
    animation-duration: 3s;
    animation-iteration-count: infinite;
}
@keyframes test2{
    0%{background-color: blue;}
    30%{background-color: green;}
    60%{background-color: gray;}
    100%{background-color: black;}
}
@keyframes test1{
    0%{background-color: lightblue;}
    30%{background-color: lightgreen;}
    60%{background-color: lightgray;}
    100%{background-color: black;}
}

【2】如果动画的其他 7 个子属性和动画名称的长度不同,动画名称列表的长度决定最终的长度,多余的值无余,缺少的值按照顺序进行重复

div{
    width: 300px;
    height: 100px;
    position: relative;
    background-color: pink;
    animation-name: test1,test2,test3;
    animation-duration: 3s,1s;
    animation-iteration-count: infinite;
}
@keyframes test1{
    0%{background-color: lightblue;}
    30%{background-color: lightgreen;}
    60%{background-color: lightgray;}
    100%{background-color: black;}
}
@keyframes test2{
    0%{font-size: 20px;}
    30%{font-size: 30px;}
    60%{font-size: 40px;}
    100%{font-size: 50px;}
}
@keyframes test3{
    0%{left: 0px;}
    30%{left: 30px;}
    60%{left: 40px;}
    100%{left: 50px;}
}
<div> 测试文字 </div>    

 

持续时间

  持续时间指完成动画的时间

animation-duration

  值: <time> [, <time>]*

  初始值: 0s

  应用于: 所有元素

  继承性: 无

animation-duration: <time>[,<time>]*

  0s 意味着动画没有时间,持续时间不能为负值

animation-name: test1,test2;
/*test1 的持续时间设置为负值,将使得整个动画持续时间都失效,因此 test2 也将没有动画效果 */
animation-duration: -1s,1s;

 

时间函数

animation-timing-function

  值: <single-timing-function> [, <single-timing-function>]*

  初始值: ease

  应用于: 所有元素

  继承性: 无

  animation 的时间函数类似于transition 的时间函数。时间函数可以应用于整个动画中,也可以应用于关键帧的某两个百分比之间

div{
    width: 300px;
    height: 100px;
    position: relative;
    background-color: pink;
    animation-name: test;
    animation-duration: 5s;
    animation-iteration-count: infinite;
}

@keyframes test{
0%{left: 0px;animation-timing-function: ease;}
20%{left: 50px;animation-timing-function: linear;}
40%{left: 100px;animation-timing-function: ease-in;}
60%{left: 150px;animation-timing-function: ease-out;}
80%{left: 200px;animation-timing-function: step-start;}
100%{left: 250px;animation-timing-function: step-end;}
}

 

循环次数

animation-iteration-count

  值: infinite | <number>[,infinite | <number>]*

  初始值: 1

  应用于: 所有元素

  继承性: 无

  默认为 1,可以是整数也可以小数,但不能是 0 和负数。如果为 infinite 则表示无限次动画

 

动画方向

  动画方向用来定义是否动画需要反向播放

animation-direction

  值: <single-animation-direction>[,<single-animation-direction>]*

  初始值: normal

  应用于: 所有元素

  继承性: 无

<single-animation-direction> = normal | reverse | alternate | alternate-reverse
normal: 正向播放
reverse: 反向播放
alternate: 若动画只播放一次,则和正向播放一样。若播放两次以上,偶数次效果为反向播放
alternate-reverse: 若动画只播放一次,则和反向播放一样。若播放两次以上,偶数次效果为正向播放

  [注意]safari 浏览器不支持 reverse 属性和 alternate-reverse 属性

 

动画状态

animation-play-state

  值:running | paused[,running | paused]*

  初始值: running

  应用于: 所有元素

  继承性: 无

  running 表示播放中,paused 表示动画暂停

 

延迟时间

  定义延迟多少时间后动画开始播放

animation-delay

  值: <single-animation-delay>[,<single-animation-delay>]*

  初始值: 0s

  应用于: 所有元素

  继承性: 无

<single-animation-delay>= <time>[,<time>]*

  [注意] 该延迟时间是指整个动画的延迟时间,而不是每个循环的延迟时间,只在动画开始时进行一次时间延迟

  如果该值是负值,则表示动画的起始时间从 0s 变为延迟时间的绝对值

 

填充模式

  定义动画开始帧之前和结束帧之后的动作

  [注意]android2.1-3 不支持 animation-fill-mode

animation-fill-mode

  值: <single-animation-fill-mode>[,<single-animation-fill-mode>]*

  初始值: none

  应用于: 所有元素

  继承性: 无

<single-animation-fill-mode> = none | forwards | backwards | both
none: 动画结束后,元素移动到初始状态
    [注意] 初始状态并不是指 0%的元素状态,而是元素本身属性值
forwards: 元素停在动画结束时的位置
    [注意] 动画结束时的位置并不一定是 100%定义的位置,因为动画有可能反向运动,也有可能动画的次数是小数
backwards: 在 animation-delay 的时间内,元素立刻移动到动画开始时的位置。若元素无 animation-delay 时,与 none 的效果相同
    [注意] 动画开始时的位置也不一定是 0%定义的位置,因为动画有可能反向运动。
both: 同时具有 forwards 和 backwards 的效果

  [注意] 当持续时间 animation-duration 为 0s 时,animation-fill-mode 依然适用,当 animation-fill-mode 的值为 backwards 时,动画填充在任何 animation-delay 的阶段。当 animation-fill-mode 的值为 forwards 时,动画将保留在 100% 的关键帧上

 

多值

animation

  值: <single-animation>[,<single-animation>]*

  初始值: 无

  应用于: 所有元素

  继承性: 无

<single-animation> = <single-animation-name> || <single-animation-duration> || <single-animation-timing-function> || <single-animation-delay> || <single-animation-iteration-count> || <single-animation-direction> || <single-animation-fill-mode> || <single-animation-play-state>

  [注意] 持续时间在前,延迟时间在后,若只存在一个时间,则是持续时间

div{
    width: 300px;
    height: 100px;
    background-color: pink;
    animation: 1s test1,infinite test2 2s 1s;
}
@keyframes test1{
    30%{background-color: red;}
    60%{background-color: blue;}
    100%{background-color: green;}
}
@keyframes test2{
    100%{color: white;}
}

 

API

  animation 涉及到的事件有 animationstart、animationend、animationiteration 三个。这三个事件的 bubbles 都是 yes,cancelable 都是 no

  [注意] 对于 safari 浏览器,animation 的事件为 webkitAnimationStart、webkitAnimationEnd、webkitAnimationIteration

  [注意] 动画事件只支持 DOM2 级事件处理程序的写法

animationstart

  发生在动画开始时

  【1】如果存在 delay,且 delay 为正值,则元素等待延迟完毕后,再触发该事件

  【2】如果 delay 为负值,则元素先将初始值变为 delay 的绝对值时,再触发该事件

    oSb.addEventListener('animationstart',function(){
        this.innerHTML = '动画开始';
        this.style.background = 'lightgreen';
    },false);

 

animationend

  发生在动画结束时

test.addEventListener('animationend',function(){
    this.style.background="lightgreen";
    this.innerHTML = '动画结束';
},false);

 

animationiteration

  发生在动画的一次循环结束时,只有当 iteration-count 循环次数大于 1 时,触发该事件

var i = 0;
oSb.addEventListener('animationiteration',function(){
    i++;
    this.innerHTML = i;
},false);

【补充】

  只有改变 animation-name 时,才会使 animation 动画效果重新触发

oSb.style.animationName = 'none';
setTimeout(function(){
    oSb.style.animationName = 'test';
},100);

属性

  这三个事件的事件对象,都有 animationName 和 elapsedTime 属性这两个私有属性

animationName 属性: 返回产生过渡效果的 CSS 属性名
elapsedTime 属性: 动画已经运行的秒数

  [注意] 对于 animationstart 事件,elapsedTime 属性等于 0,除非 animation-delay 属性等于负值

<style>
#test{height:100px;width:300px;background-color:lightblue;animation:anim 2s 3;}
@keyframes anim{
    0%{height: 100px;}
    50%{height: 50px;}
    100%{height: 0;}
}
</style>

<button id='reset'>还原</button>
<div id="test"></div>
<script>
reset.onclick
= function(){
history.go();
}
test.addEventListener(
"animationstart", listener, false);
test.addEventListener(
"animationend", listener, false);
test.addEventListener(
"animationiteration", listener, false);
function listener(e){
e
= e || event;
var li = document.createElement("li");
switch(e.type) {
case "animationstart":
li.innerHTML
= "Started: elapsed time is " + e.elapsedTime;
break;
case "animationend":
li.innerHTML
= "Ended: elapsed time is " + e.elapsedTime;
break;
case "animationiteration":
li.innerHTML
= "New loop started at time " + e.elapsedTime;
break;
}
test.appendChild(li);
}
</script>