Angular 21 中 animate.leave 未执行就移除 DOM,如何排查?
结论
先检查 animate.leave 是否绑定在条件渲染直接移除的元素上。如果被移除的是外层父元素,而动画绑定在内部子元素上,需要考虑 Angular 对嵌套离场动画的支持差异,不能仅凭这一现象认定 CSS 写错或浏览器不支持。
Angular 官方记录确认,嵌套动画支持曾因路由导航时意外触发内部离场动画等兼容性问题被撤回。因此,next.angular.dev 中描述的行为不能直接视为 Angular v21.2.5 的行为保证。参见 Angular 官方问题记录 #67552。
目前未能读取所给 StackBlitz 的代码,无法确认该项目是否采用了这种结构。可以通过下面的步骤排查原因。
为什么进入动画正常,离场动画却没有执行?
先比较两种模板。
动画绑定在条件块直接移除的元素上:
@if (isShown()) {
<div animate.leave="leaving">内容</div>
}
动画绑定在被移除父元素的内部:
@if (isShown()) {
<section>
<div animate.leave="leaving">内容</div>
</section>
}
第二种结构涉及嵌套离场动画。如果运行版本没有相应支持,外层 section 被移除时,内部 div 的离场动画可能不会触发。进入动画正常,也不代表移除父元素时子元素的离场动画一定会执行。
如何调整模板?
如果需要让整个区域淡出,可以把 animate.leave 放到条件块直接移除的外层元素上。下面是用于对照排查的独立组件示例:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-leave-demo',
standalone: true,
template: `
<button type="button" (click)="isShown.update(toggle)">
切换显示
</button>
@if (isShown()) {
<section class="panel" animate.leave="leaving">
<p>这块内容在离场时淡出。</p>
</section>
}
`,
styles: `
.panel {
padding: 16px;
border: 1px solid #ccc;
}
.leaving {
animation: panel-leave 2s ease-out forwards;
}
@keyframes panel-leave {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(20px);
}
}
`,
})
export class LeaveDemoComponent {
readonly isShown = signal(true);
readonly toggle = (value: boolean) => !value;
}
示例中使用 2s 是为了方便观察。按类名使用 animate.leave 时,Angular 会应用对应的 CSS 类,并等待动画完成后移除元素;有多个动画或 transition 时,会等待其中耗时最长的一个。参见 Angular 动画指南。
如果只有内部某个元素需要动画,也可以保留外层容器,把 @if 移到动画元素所在的位置。如果外层组件仍然同时被销毁,只移动内部条件块不能保证动画有机会执行。
如何确认具体卡在哪一步?
- 在浏览器开发者工具中观察目标元素的
class。关闭显示后,如果完全没有出现leaving,优先检查移除层级,以及是否跨越了组件边界。 - 如果出现了
leaving,检查计算样式中的animation-name、animation-duration或对应的 transition 属性,确认规则匹配且持续时间不为0s。 - 先在同一个组件模板中运行上面的简单结构,再逐步加回外层容器、子组件和路由。如果简单结构正常、嵌套后失败,排查方向就更明确了。
- 核对项目实际安装的 Angular 版本,使用对应版本的文档。需要升级时,应根据目标版本的变更记录验证当前复现,不能仅凭早期讨论就认定某个后续版本已经解决全部相关情况。
延长 CSS 动画时长只能帮助观察已经触发的动画。如果子元素的离场动画根本没有触发,调整时长不会改变结果。
备注:内容仅供参考。
