用途:这是一套审查视角,用于排查 dialog、popover 及其他切换 display 的 UI 在进入/退出动效上的边缘失败,例如退出动画被跳过、首次打开不动画、依赖 transitionend 的 JS 从不执行。它不讲解如何制作动画,而是覆盖易踩的坑与审查要点。
主要检查项:
- 退出动画被截断:切换 display:none 的元素需在过渡列表中包含 display 并配合 transition-behavior: allow-discrete;顶层元素还需过渡 overlay,否则元素会提前跳出顶层。仅做进入设置的写法,往往表现为进入有动画、关闭时直接消失。
- allow-discrete 位置:优先单独写 transition-behavior 行,并放在 transition 简写之后。写在简写之前会被重置;把 allow-discrete 塞进简写值会让不认识该关键字的浏览器整条声明失效。
- @starting-style 必须指向打开态选择器(如 :popover-open 或 dialog 的 [open]),且位于该规则之后,否则首次样式更新或 display 翻转时不触发,进入动画静默失效。
- ::backdrop 需要独立选择器与独立的 @starting-style,不能继承或遗漏其状态;嵌套写法本身不是缺陷。
- 不要仅以 transitionend 作为清理/焦点/卸载的闸门:元素被移除、设为 display:none 或过渡被取消时不会触发 transitionend,取消路径触发的是 transitioncancel,部分中断/移除路径甚至不产生可用事件。建议用 Promise.all(el.getAnimations({subtree:true}).map(a => a.finished)),注意取消时 finished 会以 AbortError 拒绝,需用 .then(done, done) 或 try/finally 捕获。若必须用事件,可同时监听 transitionrun + transitionend + transitioncancel 并加时长超时兜底。
- 尊重 prefers-reduced-motion:将装饰性动效包在 @media (prefers-reduced-motion: reduce) 中并弱化为淡入淡出,避免使用 * { animation: none !important; },因为部分流程依赖完成事件,且 JS 驱动的 Web Animations 不受该 CSS 技巧覆盖。
快速排查命令: rg -n 'allow-discrete|transition-behavior|@starting-style|\boverlay\b' src/ app/ packages/ 2>/dev/null rg -n 'transitionend|transitioncancel|transitionrun|animationend|onTransitionEnd' src/ app/ packages/ 2>/dev/null rg -n 'getAnimations|.finished|::backdrop|:popover-open|[open]|runAfterTransition' src/ app/ packages/ 2>/dev/null rg -n 'prefers-reduced-motion|matchMedia(.*reduced-motion' src/ app/ packages/ 2>/dev/null
边界:overlay 焦点/滚动契约、View Transitions API、以及 reduced-motion 行为验证分别属于其他更合适的技能范围,本技能只负责过渡/动画生命周期这一层。限制:不提供具体动画教程;是否值得提交问题需满足真实用户路径、用户可见或状态损坏、以实际 CSS/事件代码为证据、修复范围狭窄等条件,纯 @keyframes、非离散属性的简写、库已自行处理卸载、setTimeout 兜底等不视为缺陷。