Angular中如何跨组件触发动画 实现先关闭顶导再路由过渡
Angular跨组件控制顶导与路由过渡动画时序方案
核心逻辑是把动画触发、完成的状态从单个组件内部抽离,用全局服务做状态中转,所有动画的触发、等待都走服务的通知流,完全避免组件之间找不到实例、时序对不上的问题。
1. 新建全局动画协调服务
这个服务只做组件之间的状态“传声筒”,存储顶导的动画触发指令、动画完成通知,不需要额外复杂逻辑:
// animation-coordinator.service.ts import { Injectable } from '@angular/core'; import { Subject, Observable } from 'rxjs'; type TopNavState = 'open' | 'closing' | 'closed'; @Injectable({ providedIn: 'root' }) export class AnimationCoordinatorService { private readonly topNavStateTrigger = new Subject<TopNavState>(); public topNavState$: Observable<TopNavState> = this.topNavStateTrigger.asObservable(); private readonly topNavAnimationComplete = new Subject<void>(); public topNavAnimationComplete$: Observable<void> = this.topNavAnimationComplete.asObservable(); triggerCloseTopNav() { this.topNavStateTrigger.next('closing'); } triggerOpenTopNav() { this.topNavStateTrigger.next('open'); } notifyTopNavClosed() { this.topNavStateTrigger.next('closed'); this.topNavAnimationComplete.next(); } }
2. 改造TopNavComponent对接服务
把原来组件内部自控的开合逻辑,改成监听服务下发的状态,动画执行完成后主动给服务发完成通知:
// top-nav.component.ts import { Component, OnDestroy } from '@angular/core'; import { trigger, state, style, transition, animate } from '@angular/animations'; import { AnimationCoordinatorService } from './animation-coordinator.service'; import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-top-nav', template: ` <nav class="top-nav" [@slideToggle]="currentNavState" (@slideToggle.done)="handleAnimationEnd()" > <!-- 原有导航内容 --> </nav> `, styles: [` .top-nav { position: fixed; top: 0; left: 0; width: 100%; z-index: 999; } `], animations: [ trigger('slideToggle', [ state('open', style({ transform: 'translateY(0)' })), state('closed', style({ transform: 'translateY(-100%)' })), transition('* <=> *', animate('280ms ease-in-out')) ]) ] }) export class TopNavComponent implements OnDestroy { currentNavState: 'open' | 'closed' = 'open'; private destroy$ = new Subject<void>(); constructor(private coordService: AnimationCoordinatorService) { // 监听全局下发的导航状态变更 this.coordService.topNavState$ .pipe(takeUntil(this.destroy$)) .subscribe(state => { if (state === 'open') this.currentNavState = 'open'; if (state === 'closing' || state === 'closed') this.currentNavState = 'closed'; }) } handleAnimationEnd() { if (this.currentNavState === 'closed') { // 动画实际执行完成,通知全局 this.coordService.notifyTopNavClosed(); } } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }
3. 拦截路由跳转,对齐动画时序
两种实现方式二选一即可,不需要同时写:
方式A:路由守卫拦截(推荐)
写一个路由守卫,跳转前先触发顶导关闭,等收到关闭完成的通知再放行路由:
// nav-close.guard.ts import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; import { AnimationCoordinatorService } from './animation-coordinator.service'; import { take } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class NavCloseGuard implements CanActivate { constructor(private coordService: AnimationCoordinatorService) {} canActivate() { this.coordService.triggerCloseTopNav(); // 等导航完全收起再放行路由 return this.coordService.topNavAnimationComplete$.pipe(take(1)); } }
把这个守卫加到所有需要过渡效果的路由配置上即可。路由加载完成、页面进入动画跑完之后,调用服务的triggerOpenTopNav()方法让顶导航入,整个流程就完全按顺序执行了。
方式B:全局路由事件监听
不想给每个路由单独加守卫的话,直接在AppComponent里监听Router的事件流:
- 监听到
NavigationStart事件时,调用triggerCloseTopNav(),同时加isRouteLoading = true标志位,暂时禁用router-outlet的动画 - 等收到
topNavAnimationComplete$的通知,把isRouteLoading设为false,允许router-outlet渲染新页面、执行页面过渡动画
避坑提醒
- 别图省事写死
setTimeout()等动画结束:后续改动画时长、低端机掉帧、用户快速连续点击跳转的时候,时序会直接乱套,用Angular动画自带的done回调是最稳的,动画实际执行完成才会触发 - 记得处理订阅取消:服务里的Subject是长连接流,组件里订阅的时候一定要加
takeUntil销毁逻辑,避免内存泄漏 - 提前禁用路由动画:如果页面过渡动画是绑定在
<router-outlet>上的,顶导关闭过程中记得给动画加@.disabled绑定,别让页面动画提前触发
内容的提问来源于stack exchange,提问作者Saeed Noruzi
相关产品推荐
相关产品推荐

