淡入淡出需协同DOM更新与CSS过渡,在inserted钩子中设opacity=0后触发过渡实现淡入,在removed钩子中设opacity=0并监听transitionend后调用done()实现淡出,配合唯一key和样式清理确保动画稳定。

利用 Patch 过程实现节点的“淡入淡出”,核心不是靠 Patch 本身做动画,而是把 DOM 更新(如插入、移除)和 CSS 过渡(transition)或动画(animation)协同起来,在 Patch 的生命周期钩子中手动控制样式状态与 DOM 存在时机,从而让视觉效果与结构更新解耦。
理解 Patch 的关键时机:inserted 与 removed 钩子
Patch(尤其在 Vue 3 的渲染器、Snabbdom 或自研轻量级 diff 库中)会在真实 DOM 节点被挂载或卸载时触发钩子。这些钩子是插入 CSS 类、触发动画、延迟移除节点的黄金窗口:
-
inserted(el):节点已插入 DOM,但尚未渲染完成(多数情况下可安全加 class);适合添加
fade-in类启动淡入 -
removed(el, done):节点即将被移除,但你可调用
done()显式控制移除时机;适合先加fade-out类,等过渡结束再调用done()
淡入:插入后立即触发过渡
给新节点绑定 inserted 钩子,添加过渡类,并确保对应 CSS 支持 opacity + transition:
const fadeInHook = {
inserted(el) {
// 确保初始为透明且不可见(避免闪现)
el.style.opacity = '0';
el.style.transition = 'opacity 0.3s ease';
// 强制重排,让浏览器读取初始状态
void el.offsetWidth;
// 再设为目标状态,触发过渡
el.style.opacity = '1';
}
};
注意:不要依赖 class 切换的“CSS class toggle”方式(如 el.classList.add('fade-in')),除非你已预设好 .fade-in { opacity: 1; } 且初始 class 是 fade-in fade-out 这类组合——否则可能因 class 同步应用而跳过过渡。
淡出:延迟 DOM 移除,等待过渡完成
这是最关键的控制点。Patch 提供 removed 钩子并传入 done 回调,意味着你可以自主决定何时真正删掉节点:
const fadeOutHook = {
removed(el, done) {
el.style.opacity = '0';
el.style.transition = 'opacity 0.3s ease';
// 监听 transitionend,结束后再调用 done()
const onTransitionEnd = () => {
el.removeEventListener('transitionend', onTransitionEnd);
done(); // 此刻才真正从 DOM 移除
};
el.addEventListener('transitionend', onTransitionEnd);
}
};
若需兼容更广(如无 transitionend 事件),可用 setTimeout(done, 300),但精度略低;推荐监听事件,更可靠。
配合响应式更新:避免 Patch “跳过”动画节点
当节点因响应式数据变化被复用(例如 v-if 切换或列表 key 变化不充分),Patch 可能直接复用旧 DOM 元素,跳过 inserted 或 removed。要确保动画稳定,需:
- 为动态节点设置唯一、稳定的
key,防止复用干扰生命周期 - 在数据变化前主动清理状态(如清空 opacity、transition),避免残留样式阻断下一次过渡
- 对同一元素反复切换显隐,建议统一用
v-show+ class 控制 opacity,而非频繁 patch 插入/移除










