文章目录
  1. 1. 什么是Web Animations
    1. 1.1. 概述
    2. 1.2. 兼容性
    3. 1.3. 代码示例
    4. 1.4. 与CSS动画相比的优点
      1. 1.4.1. 低耦合
      2. 1.4.2. 兼容性和流畅度
  2. 2. Angular中的Web Animations动画
    1. 2.1. 概述,及引入方法
    2. 2.2. 基本写法
      1. 2.2.1. 状态(state)
      2. 2.2.2. transition()
    3. 2.3. 关键帧
    4. 2.4. 第三方动画库
  3. 3. 参考资料

什么是Web Animations

概述

正如CSS Animations一样,Web Animations是javascript提供的一组动画接口,它允许同步和定时更改网页的呈现, 即DOM元素的动画。因此,使用Web Animations可以方便地用Js操作动画,而不再需要像之前那样写一大堆css和定时器来实现。

兼容性

它目前尚属W3C的标准草案,详情可查阅:https://www.w3.org/TR/web-animations/ ,因此很多浏览器并不能完整支持,兼容性查询

正因如此,W3C官方为开发者提供了web-animations/web-animations-js polyfill插件,是我们能通过插件来解决浏览器的支持问题,插件地址:https://github.com/web-animations/web-animations-js/tree/master

1
<script src="web-animations.min.js"></script>

如上所示插入插件,即可使用Web Animations进行开发。

代码示例

提供了很简洁明了的,我们可以在 dom 元素上直接调用的 animate 函数:

1
2
var element = document.querySelector('.animate-me');
var animation = element.animate(keyframes, 1000);

第一个参数是一个对象数组,每个对象表示动画中的一帧:

1
2
3
4
var keyframes = [
{ opacity: 0 },
{ opacity: 1 }
];

这与 css 中的 keyframe 定义类似:

1
2
3
4
5
6
0% {
opacity: 0;
}
100% {
opacity: 1;
}

第二个参数是 duration,表示动画的时间。同时也支持在第二个参数中传入配置项来指定缓动方式、循环次数等:

1
2
3
4
5
6
7
8
9
10
11
var options = {
iterations: Infinity, // 动画的重复次数,默认是 1
iterationStart: 0, // 用于指定动画开始的节点,默认是 0
delay: 0, // 动画延迟开始的毫秒数,默认 0
endDelay: 0, // 动画结束后延迟的毫秒数,默认 0
direction: 'alternate', // 动画的方向 默认是按照一个方向的动画,alternate 则表示交替
duration: 700, // 动画持续时间,默认 0
fill: 'forwards', // 是否在动画结束时回到元素开始动画前的状态
easing: 'ease-out', // 缓动方式,默认 "linear"
};
var animation = element.animate(keyframes, options);

在 dom 元素上调用 animate 函数之后返回一个 Animation 对象,或者通过 ele.getAnimation 方法获取 dom 上的 Animation 对象。

Animation对象有一些属性和方法,比如:

  • Animation.finished(此动画的当前完成的状态,只读)
  • Animation.effect(与此动画相关联的关键帧)
  • Animation.play()(开始播放动画)
  • Animation.pause()(暂停播放动画)

等等。

Animation对象还有两个事件处理程序:

  • Animation.oncancel:获取并设置取消事件的事件处理程序
  • Animation.onfinish:获取并设置完成事件的事件处理程序

由以上API,开发者可以通过 promiseevent 两种方式对动画进行操作:

1
2
3
4
// 通过监听事件进行处理
myAnimation.onfinish = function() {
element.remove();
}
1
2
3
4
// 通过承诺进行处理
myAnimation.finished.then(
() => element.remove()
)

与CSS动画相比的优点

低耦合

CSS 动画中,如果需要控制动画或者过渡的开始或结束只能通过相应的 dom 事件来监听,并且在回调函数中操作,这也是受 CSS 本身语言特性约束所致。也就是说很多情况下,想要完成一个动画需要结合 CSS 和 JS 来共同完成。使用 WAAPI 则有 promise 和 event 两种方式与监听 dom 事件相对应。从代码可维护性和完整性上看 WAAPI 有自身语言上的优势。

兼容性和流畅度

事实上Web Animations常用方法现在已经兼容了大部分现代的浏览器。如果想现在就玩玩,可以使用官方提供的polyfill。而CSS动画我们也用了很久,基本上对于老旧的浏览器只能用一些优雅的降级方案。至于流畅度的问题两者性能差距不大,而且Web Animations提供了性能优化的方案。

Angular中的Web Animations动画

概述,及引入方法

Angular动画是基于标准的Web动画API(Web Animations API)构建的,它们在支持此API的浏览器中会用原生方式工作。

上述是官方文档对于Angular动画的解释。由于基于Web Animations,因此很多写法也是非常相像的。

和http、form一样,Angular中的动画也是一个独立模块,首先需在根模块进行导入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
// ...(其它模块)
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserAnimationsModule,
// ...(其它模块)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

就在组件里去定义动画了,如果需要定义大量的动画片段,建议单独建立一个项目的动画模块。当然,定义动画之前需要引入一些常用的API:

1
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';

基本写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import {
Component,
Input
} from '@angular/core';
import {
trigger,
state,
style,
animate,
transition
} from '@angular/animations';

@Component({
selector: 'app-hero-list-basic',
template: `
<h1 [@clickAnim]="state"
(click)="toggleState()">
点击触发
</h1>
`,
styleUrls: [],
animations: [
trigger('clickAnim', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))
])
]
})
export class HeroListBasicComponent {

constructor(public state = 'inactive') { }

toggleState() {
this.state = this.state === 'active' ? 'inactive' : 'active';
}
}

上述代码在@Component元数据中构建了一个名为clickAnim的简单动画,它会让<h1>元素在两个状态activeinactive之间转场, 当处于active激活状态时,它会把该元素显示得稍微大一点、亮一点。模板中用[@triggerName]语法即可调用。

Angular动画图解

状态(state)

在此动画中,我们可以为每个动画状态定义了一组样式:

1
2
3
4
5
6
7
8
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),

这些state具体定义了每个状态的最终样式,也就是说,这里其实并不只是在定义动画,而是在定义该元素在不同状态时应该具有的样式。

除了自定义状态,还有两种特殊状态:

  1. void:匹配尚未被添加进来或者已经被移除了的状态,用于进场或离场动画
  2. *:匹配任何动画的状态

transition()

而负责这定义两组样式转换的则是transition()函数,它的第一个参数负责定义需被转换的状态名称(可定义多个):

1
2
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))

其中,=> 也可以写成 <=> 即双向的。

第二个参数是一个animate()函数或一个数组,负责定义动画执行的总时间以及样式,当有些样式只希望在动画过程中生效,结束后并不保留,可以在数组中定义一个style()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
transition('inactive => active', [
style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.3)'
}),
animate(
'80ms ease-in',
style({
backgroundColor: '#eee',
transform: 'scale(1)'
})
)
]),

关键帧

上述代码中,关于动画运行的方式,用了ease-inease-out两种方法,除此之外还有几种,例如linear等,同CSS动画一样,甚至可以使用一些赛贝尔曲线,例如cubic-bezier(0.4, 0, 0.2, 1)。我们可以在这两个网站自定义挑选赛贝尔曲线的表达式:

同CSS一样,并不是所有的赛贝尔曲线都能被使用的,所以关键帧的设置必不可少,它在Angular中是这样写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
transition('* => void', [
animate(300, keyframes([
style({
opacity: 1,
transform: 'translateX(0)',
offset: 0
}),
style({
opacity: 1,
transform: 'translateX(-15px)',
offset: 0.7
}),
style({
opacity: 0,
transform: 'translateX(100%)',
offset: 1.0
})
]))
])

上述代码是一个典型的“离场动画”,它在第二帧定义了一些“反弹”效果。

offset属性定义了此帧在整个动画中处于哪个时间点,它并不一定用绝对数字定义,而是在0到1之间的相对值(百分比),而且它是可选的,如果省略它,偏移量会自动根据帧数平均分布出来。

第三方动画库

上述的动画我们直接写在了组件中,更多时候往往需要把它们抽象出来,形成一个自己的动画库,可以在各个组件调用。下面这个简单的库来自于segmentfault社区:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// animate.ts
import {
trigger,
state,
style,
transition,
animate,
keyframes
} from '@angular/animations';

// 动画时间线
var time = '300ms'
var styles = {
ease: time + ' ease ',
linear: time + ' linear ',
easeIn: time + ' ease-in',
easeOut: time + ' ease-out',
stepStart: time + ' step-start',
stepEnd: time + ' step-end',
easeInOut: time + ' ease-in-out',
faseOutSlowIn: time + ' cubic-bezier(0.4, 0, 0.2, 1)',
inOutBack: time + ' cubic-bezier(0.68, -0.55, 0.27, 1.55)',
inOutCubic: time + ' cubic-bezier(0.65, 0.05, 0.36, 1)',
inOutQuadratic: time + ' cubic-bezier(0.46, 0.03, 0.52, 0.96)',
inOutSine: time + ' cubic-bezier(0.45, 0.05, 0.55, 0.95)'
}

// 动画配置

var opts = {
fadeIn: [
style({ opacity: 0 }),
animate(styles.inOutBack, style({ opacity: 1 })),
],
fadeOut: [
style({ opacity: 1 }),
animate(styles.inOutBack, style({ opacity: 0 }))
],
shrink: [
style({ height: '*' }),
animate(styles.inOutBack, style({ height: 0 }))
],
stretch: [
style({ height: '0' }),
animate(styles.inOutBack, style({ height: '*' }))
],
flyIn: [
style({ transform: 'translateX(-100%)' }),
animate(styles.inOutBack, style({ transform: '*' }))
],
flyOut: [
style({ transform: '*' }),
animate(styles.inOutBack, style({ transform: 'translateX(-100%)' }))
],
zoomIn: [
style({ transform: 'scale(.5)' }),
animate(styles.inOutBack, style({ transform: '*' }))
],
zoomOut: [
style({ transform: '*' }),
animate(styles.inOutBack, style({ transform: 'scale(.5)' }))
]
}

// 导出动画时间线定义,供自定义动画的时候使用
export const animStyle = styles

// 导出动画
export const fadeIn = [trigger('fadeIn', [transition('void => *', opts.fadeIn)])]
export const fadeOut = [trigger('fadeOut', [transition('* => void', opts.fadeOut)])]
export const stretch = [trigger('stretch', [transition('void => *', opts.stretch)])]
export const shrink = [trigger('shrink', [transition('* => void', opts.shrink)])]
export const flyIn = [trigger('flyIn', [transition('void => *', opts.flyIn)])]
export const flyOut = [trigger('flyOut', [transition('* => void', opts.flyOut)])]
export const zoomIn = [trigger('zoomIn', [transition('void => *', opts.zoomIn)])]
export const zoomOut = [trigger('zoomOut', [transition('* => void', opts.zoomOut)])]

export const simAnim = [
trigger('simAnim', [
transition('* => fadeIn', opts.fadeIn),
transition('* => fadeIn', opts.fadeOut),
transition('* => shrink', opts.shrink),
transition('* => stretch', opts.stretch),
transition('* => flyIn', opts.flyIn),
transition('* => flyOut', opts.flyOut),
transition('* => zoomIn', opts.zoomIn),
transition('* => zoomOut', opts.zoomOut)
])
]

上述代码定义了8种常用动画效果,还可以自定义time变量修改动画速度。在使用时,可以在组件直接全部引入:

1
import { simAnim } from './animate.ts';

亦或是单独引入某个动画:

1
import { fadeIn } from './animate.ts';

然后在组件元数据中加入动画数组:

1
2
3
4
@Component({
// ...
animations: [simAnim]
})

然后在模板中加入:<div @flyIn @flyOut>...</div>,也可以写成<div [@simAnim]="'flyIn'">...</div>

如果上述代码仍不满足需求,可以引入其它开源项目,比如:https://github.com/jiayihu/ng-animate
它是一个更强大的开源项目,动画演示及说明文档非常详细,这里就不再赘述。

参考资料

文章目录
  1. 1. 什么是Web Animations
    1. 1.1. 概述
    2. 1.2. 兼容性
    3. 1.3. 代码示例
    4. 1.4. 与CSS动画相比的优点
      1. 1.4.1. 低耦合
      2. 1.4.2. 兼容性和流畅度
  2. 2. Angular中的Web Animations动画
    1. 2.1. 概述,及引入方法
    2. 2.2. 基本写法
      1. 2.2.1. 状态(state)
      2. 2.2.2. transition()
    3. 2.3. 关键帧
    4. 2.4. 第三方动画库
  3. 3. 参考资料