Angular动画备忘录
在网上找不到相关信息,所以进行公开。
(只是根据实际行为推测,并没有追踪来源,所以不要轻信,自己进行验证吧)
-
- 查询时,{ optional: true }是实际必需的。
-
- 查询时,对于animateChild()的进入和离开指定将不起作用。
-
- 如果在状态转换时没有与之匹配的任何过渡效果,则会立即转换到新的状态。
-
- void状态将成为原始样式。
-
- 作为子元素设置的动画在父元素进入时,如果没有明确调用animateChild(),则会立即转换到新的状态。
-
- 作为子元素设置的动画在父元素离开时,除非以组并行执行,否则不会生效。
-
- 作为子元素设置的动画在父元素离开时,如果以组并行进行并且没有相应的过渡效果,则会立即转换为void状态即原始样式。
-
- “动画中的再次过渡”通常会合并,但在animateChild()中发生时,会导致显示与状态不一致。在动画时间较长时经常发生。
-
- 如果从存在显示和状态不一致的状态转换进行动画播放,会出现不适当的播放。
-
- 即使在存在显示和状态不一致的状态中,进行(不当播放中的)“动画中的再次过渡”,显示和状态的不一致仍然会持续。
-
- 即使两个状态之间没有显示差异,实际上仍执行了动画。在此期间发生动画将被视为“动画中的再次过渡”处理。
- 典型示例是在“:enter”中进行长时间的animateChild()动画,而显示差异不存在。通过缩短:enter动画的持续时间可以减少其发生。
请给我一个例子。
動畫定義
import { trigger, transition, style, query, group, animate, animateChild, AnimationMetadata, state } from '@angular/animations';
function optionalQuery(selector: string, animation: AnimationMetadata | AnimationMetadata[]) {
return query(selector, animation, { optional: true });
}
export class AppAnimations {
/**
* 典型的なFloatIn
* @param triggerName
* @param enterStart 新しい状態の横初期位置
* @param leaveDuration 古い状態が消えるまでの動作時間
* @param enterDelay 新しい状態の動作開始までのディレイ
* @param enterDuration 新しい状態のディレイ後の動作時間
* @returns
*/
static floatIn(triggerName: string, enterStart: string = '-1%', leaveDuration: string = '100ms', enterDelay: string = '100ms', enterDuration: string = '300ms') {
return trigger(triggerName, [
transition('* <=> *', [
style({ position: 'relative' }),
optionalQuery(':enter, :leave', [style({ position: 'absolute', top: 0, left: 0, width: '100%' })]),
optionalQuery(':enter', [style({ left: enterStart, opacity: 0 })]),
group([
optionalQuery(':leave', [animate(`${leaveDuration} ease-out`, style({ opacity: 0 }))]),
optionalQuery(':enter', [animate(`${enterDuration} ${enterDelay} ease-out`, style({ left: '0%', opacity: 1 }))]),
]),
optionalQuery('@*', animateChild()),
])
]);
}
/**
* 典型的なOpenClose。状態:open, closed
*
* Angular14以降、このアニメーションを実施するとConsoleに警告が出ます。
* ('pointer-events'やoverflowの指定は一般的だが、警告が出てしまう)
* 詳細は以下のissueを確認してください。
* https://github.com/angular/angular/issues/46928
* @param triggerName
* @param duration 動作時間
* @param ease '', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'cubic-bezier(x1, y1, x2, y2)'
* @param enterDuration animateChild()で:enterする際の挙動対策。短い時間を設定することで問題を発生させづらくする
* @returns
*/
static openClose(triggerName: string = 'openClose', duration: string = '300ms', ease: string = 'ease', enterDuration: string = '100ms') {
return trigger(triggerName, [
state('open', style({ height: '*', opacity: 1 })),
state('closed', style({ height: '0px', opacity: 0, 'pointer-events': 'none', padding: 0, overflow: 'hidden' })),
transition(':enter', [animate(`${enterDuration} ${ease}`)]),
transition('* <=> *', [animate(`${duration} ${ease}`)]),
]);
}
}
请在使用端(app.component.ts / html)上设置每个页面的路由,例如{ animation: ‘特定的状态名称’ }。
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
animations: [AppAnimations.floatIn('routeAnimations')]
})
export class AppComponent {
...
getRouteAnimationData() {
return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation'];
}
...
...
<div [@routeAnimations]="getRouteAnimationData()">
<router-outlet></router-outlet>
</div>
使用侧(网页 ts / html)
@Component({
selector: 'app-sample-a2',
templateUrl: './sample-a2.component.html',
styleUrls: ['./sample-a2.component.scss'],
animations: [AppAnimations.openClose('slowOpenClose', '2000ms', 'cubic-bezier(0.5, 0, 0.1, 1)'), AppAnimations.openClose('fastOpenClose', '100ms')]
})
export class SampleA2Component {
isOpen1: boolean = true;
isOpen2: boolean = false;
...
changeIsOpen1_click(): void {
this.isOpen1 = !this.isOpen1;
}
changeIsOpen2_click(): void {
this.isOpen2 = !this.isOpen2;
}
<div [@slowOpenClose]="isOpen1 ? 'open' : 'closed'">
<!-- etc. -->
</div>
<div [@fastOpenClose]="isOpen2 ? 'open' : 'closed'">
<!-- etc. -->
</div>
如果将enterDuration设置为2000ms等较长时间,则似乎会出现8~11的行为。