使用动画:
import { Component, Input} from '@angular/core';import { trigger, state, style, animate, transition} from '@angular/animations';@Component({ selector: 'app-hero-list-basic', template: `
- { {hero.name}}
状态与转场:
-
状态:
状态是一个由程序代码中定义的字符串值, 上面的例子 'active'
和'inactive'
这两种状态
state
具体定义了每个状态的最终样式。一旦元素转场到那个状态,该样式就会被应用到此元素上,当它留在此状态时,这些样式也会一直保持着。 从这个意义上讲,这里其实并不只是在定义动画,而是在定义该元素在不同状态时应该具有的样式。
-
转场:
定义完状态,就能定义在状态之间的各种转场了。每个转场都会控制一条在一组样式和下一组样式之间切换的时间线
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))
如果多个转场都有同样的时间线配置,就可以把它们合并进同一个transition
定义中:
transition('inactive => active, active => inactive',animate('100ms ease-out'))
如果要对同一个转场的两个方向都使用相同的时间线(就像前面的例子中那样),就可以使用<=>
这种简写语法
transition('inactive <=> active', animate('100ms ease-out'))
希望一些样式只在动画期间生效,但在结束后并不保留它们。这时可以把这些样式内联在transition
中进行定义
例子中,该元素会立刻获得一组样式,然后动态转场到下一个状态。当转场结束时,这些样式并不会被保留,因为它们并没有被定义在state
中
transition('inactive => active', [ style({ backgroundColor: '#cfd8dc', transform: 'scale(1.3)' }), animate('80ms ease-in', style({ backgroundColor: '#eee', transform: 'scale(1)' }))]),
*
(通配符)状态
*
(通配符)状态匹配任何动画状态。当定义那些不需要管当前处于什么状态的样式及转场时,这很有用。比如:
当该元素的状态从active
变成任何其它状态时,active => *
转场都会生效
当在任意两个状态之间切换时,* => *
转场都会生效。
void
状态
有一种叫做void
的特殊状态,它可以应用在任何动画中。它表示元素没有被附加到视图。这种情况可能是由于它尚未被添加进来或者已经被移除了。 void
状态在定义“进场”和“离场”的动画时会非常有用.
比如当一个元素离开视图时,* => void
转场就会生效,而不管它在离场以前是什么状态
*
通配符状态也能匹配 void
使用void
和*
状态,可以定义元素进场与离场时的转场动画:
-
-
-
进场:
void => *
-
离场:
* => void
-
-
例如,在下面的animations
数组中,这两个转场语句使用void => *
和* => void
语法来让该元素以动画形式进入和离开当前视图
animations: [ trigger('flyInOut', [ state('in', style({transform: 'translateX(0)'})), transition('void => *', [ style({transform: 'translateX(-100%)'}), animate(100) ]), transition('* => void', [ animate(100, style({transform: 'translateX(100%)'})) ]) ])]
这个例子中,这些样式在转场定义中被直接应用到了void
状态,但并没有一个单独的state(void)
定义。 这么做是因为希望在进场与离场时使用不一样的转换效果:元素从左侧进场,从右侧离开
这两个常见的动画有自己的别名:
transition(':enter', [ ... ]); // void => *transition(':leave', [ ... ]); // * => void
范例:从不同的状态下进场和离场
非激活英雄进场:void => inactive
激活英雄进场:void => active
非激活英雄离场:inactive => void
激活英雄离场:active => void
animations: [ trigger('heroState', [ state('inactive', style({transform: 'translateX(0) scale(1)'})), state('active', style({transform: 'translateX(0) scale(1.1)'})), transition('inactive => active', animate('100ms ease-in')), transition('active => inactive', animate('100ms ease-out')), transition('void => inactive', [ style({transform: 'translateX(-100%) scale(1)'}), animate(100) ]), transition('inactive => void', [ animate(100, style({transform: 'translateX(100%) scale(1)'})) ]), transition('void => active', [ style({transform: 'translateX(0) scale(0)'}), animate(200) ]), transition('active => void', [ animate(200, style({transform: 'translateX(0) scale(0)'})) ]) ])]
可动的(Animatable)属性与单位
由于Angular的动画支持是基于Web Animations标准的,所以也能支持浏览器认为可以参与动画的任何属性。这些属性包括位置(position)、大小(size)、变换(transform)、颜色(color)、边框(border)等很多属性。W3C维护着 。
尺寸类属性(如位置、大小、边框等)包括一个数字值和一个用来定义长度单位的后缀:
-
'50px'
'3em'
'100%'
对大多数尺寸类属性而言,还能只定义一个数字,那就表示它使用的是像素(px)数:
-
-
50
相当于'50px'
-
自动属性值计算:
有时候,我们想在动画中使用的尺寸类样式,它的值在开始运行之前都是不可知的。比如,元素的宽度和高度往往依赖于它们的内容和屏幕的尺寸。处理这些属性对CSS动画而言通常是相当棘手的。
如果用Angular动画,就可以用一个特殊的*
属性值来处理这种情况。该属性的值将会在运行期被计算出来,然后插入到这个动画中
例子中的“离场”动画会取得该元素在离场前的高度,并且把它从这个高度用动画转场到0高度:
animations: [ trigger('shrinkOut', [ state('in', style({height: '*'})), transition('* => void', [ style({height: '*'}), animate(250, style({height: 0})) ]) ])]
动画时间线
对每一个动画转场效果,有三种时间线属性可以调整:持续时间(duration)、延迟(delay)和缓动(easing)函数。它们被合并到了一个单独的转场时间线字符串
持续时间
持续时间控制动画从开始到结束要花多长时间。可以用三种方式定义持续时间:
作为一个普通数字,以毫秒为单位,如:100
作为一个字符串,以毫秒为单位,如:'100ms'
作为一个字符串,以秒为单位,如:'0.1s'
延迟
延迟控制的是在动画已经触发但尚未真正开始转场之前要等待多久。可以把它添加到字符串中的持续时间后面,它的选项格式也跟持续时间是一样的:
等待100毫秒,然后运行200毫秒:'0.2s 100ms'
。
缓动函数
用于控制动画在运行期间如何加速和减速。比如:使用ease-in
函数意味着动画开始时相对缓慢,然后在进行中逐步加速。可以通过在这个字符串中的持续时间和延迟后面添加第三个值来控制使用哪个缓动函数(如果没有定义延迟就作为第二个值)
等待100毫秒,然后运行200毫秒,并且带缓动:'0.2s 100ms ease-out'
运行200毫秒,并且带缓动:'0.2s ease-in-out'
例子: 这里是两个自定义时间线的动态演示。“进场”和“离场”都持续200毫秒,也就是0.2s
,但它们有不同的缓动函数。“离场”动画会在100毫秒的延迟之后开始,也就是'0.2s 10 ease-out'
:
animations: [ trigger('flyInOut', [ state('in', style({opacity: 1, transform: 'translateX(0)'})), transition('void => *', [ style({ opacity: 0, transform: 'translateX(-100%)' }), animate('0.2s ease-in') ]), transition('* => void', [ animate('0.2s 0.1s ease-out', style({ opacity: 0, transform: 'translateX(100%)' })) ]) ])]
基于关键帧(Keyframes)的多阶段动画
通过定义动画的关键帧,可以把两组样式之间的简单转场,升级成一种更复杂的动画,它会在转场期间经历一个或多个中间样式。
每个关键帧都可以被指定一个偏移量,用来定义该关键帧将被用在动画期间的哪个时间点。偏移量是一个介于0(表示动画起点)和1(表示动画终点)之间的数组。
在这个例子中,我们使用关键帧来为进场和离场动画添加一些“反弹效果”:
animations: [ trigger('flyInOut', [ state('in', style({transform: 'translateX(0)'})), transition('void => *', [ animate(300, keyframes([ style({opacity: 0, transform: 'translateX(-100%)', offset: 0}), style({opacity: 1, transform: 'translateX(15px)', offset: 0.3}), style({opacity: 1, transform: 'translateX(0)', offset: 1.0}) ])) ]), 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}) ])) ]) ])]
注意,这个偏移量并不是用绝对数字定义的时间段,而是在0到1之间的相对值(百分比)。
动画的最终时间线会基于关键帧的偏移量、持续时间、延迟和缓动函数计算出来。
为关键帧定义偏移量是可选的。如果省略它们,偏移量会自动根据帧数平均分布出来。例如,三个未定义过偏移量的关键帧会分别获得偏移量:0
、0.5
和1
。
并行动画组(Group)
我们已经知道该如何在同一时间段进行多个样式的动画了:只要把它们都放进同一个style()
定义中就行了!
但我们也可能会希望为同时发生的几个动画配置不同的时间线。比如,同时对两个CSS属性做动画,但又得为它们定义不同的缓动函数。
这种情况下就可以用动画组来解决了。在这个例子中,我们同时在进场和离场时使用了组,以便能让它们使用两种不同的时间线配置。 它们被同时应用到同一个元素上,但又彼此独立运行:
animations: [ trigger('flyInOut', [ state('in', style({width: 120, transform: 'translateX(0)', opacity: 1})), transition('void => *', [ style({width: 10, transform: 'translateX(50px)', opacity: 0}), group([ animate('0.3s 0.1s ease', style({ transform: 'translateX(0)', width: 120 })), animate('0.3s ease', style({ opacity: 1 })) ]) ]), transition('* => void', [ group([ animate('0.3s ease', style({ transform: 'translateX(50px)', width: 10 })), animate('0.3s 0.2s ease', style({ opacity: 0 })) ]) ]) ])]
其中一个动画组对元素的transform
和width
做动画,另一个组则对opacity
做动画。
sequence
按顺序一个接一个执行, 和 group 刚好打对台, 一个 step by step, 另一个是并发
sequence(animate,animate)
useAnimation
animate是可以封装的. 使用 animation 方法
let fadeAnimation = animation([ style({ opacity: ‘{ { start }}‘ }), animate(‘{ { time }}‘, style({ opacity: ‘{ { end }}‘))], { params: { time: ‘1000ms‘, start: 0, end: 1 }});
然后在任何想使用 animate 的地方改用 useAnimation
useAnimation(fadeAnimation, { params: { time: ‘2s‘, start: 1, end: 0 }})
query
任何你想使用 animate 的地方都可以使用 query
animate 会施法在当前的 element 上, 而通过 query 你可以施法在 element 的 child 上 query 支持 css 选择器的语法,还有一些 angular 特别的定义语法.query(‘css-selector‘,[animate...])
这里有个神技 ; 通过 items.length 配上下面的 transition * => * + query child 就可以实现 items 在插入和移除时的动画了.
html:
ts:
import { trigger, state, style, animate, transition, group, sequence, keyframes, useAnimation, stagger, animateChild, query, animation } from '@angular/animations'; animations: [ trigger('listAnimation', [ transition('* => *', [ query(':enter', style({ height: 0 }), {optional: true}), query(':enter', stagger(100, [ animate('.3s', style({ height: '*' })) ]), {optional: true}), query(':leave', style({ height: '*' }), {optional: true}), query(':leave', stagger(100, [ animate('.3s', style({ height: 0 })) ]), {optional: true}) ]) ]) ]
必须添加 : optional: true 不然会报错,说找不到元素,加上就是没有元素不执行动画的意思
stagger
stagger 是配合 query 来使用的, 它的作用是当 query select 出很多 element 时,你希望它们不要并发, 而是隔着一个间隔时间.
query(‘css-selector‘,[stagger(100,[animate])])
比如 select 出 2 个 element, 一个触发动画先,另一个则会等间隔 100ms 后才触发.
animateChild
animateChild 是一个 manual trigger 概念
one
angular 说, 当 parent trigger 触发后,child trigger by default 是不会被触发的 (不过我试了会 /.\)
而我们可以在 parent trigger 中通过 query(‘@child‘,[animateChild()]) 来手动触发 child trigger. 这个在做 router animate 时会用到哦.
router animation 实现 https://github.com/angular/angular/commit/f1a9e3c
其实也是依据上面这些方法来做的. 主要用了 parent, child, query, enter, leave, animateChild 这些概念.
动画回调
当动画开始和结束时,会触发一个回调。
对于例子中的这个关键帧,我们有一个叫做@flyInOut
的trigger
。在那里我们可以挂钩到那些回调,比如:
template: `
- { {hero.name}}
这些回调接收一个参数,它包含一些有用的属性,例如
fromState
,toState
和totalTime
。