为什么CSS transition关闭弹窗时会瞬间消失?

作者:袖梨 2026-09-02

transition必须写在永不被移除的基础类中,否则关闭时因样式丢失导致动画失效;禁用display: none,改用opacity与visibility协同;避免JS快速切换class,需加过渡锁和属性过滤;height: auto无法过渡,应改用max-height或预设具体高度。

transition写在状态类里,移除后就没了

关闭弹窗时瞬间消失,最常见原因是把transition声明写进了会被classList.remove()干掉的类中。比如你写了.modal--open { opacity: 1; transition: opacity 0.3s; },关的时候删掉这个类,transition就跟着没了——浏览器连“从哪开始过渡”都不知道,只能直接设opacity: 0

必须把transition抽到一个永不被移除的基础类里:

  1. .modal { transition: opacity 0.3s ease, transform 0.3s ease; }
  2. .modal--closed { opacity: 0; transform: translateY(10px); }
  3. .modal--open { opacity: 1; transform: translateY(0); }

用 DevTools 的 Computed 面板盯住transition-property,确保它在开/关状态下都稳定存在,而不是只在--open里闪一下。

用了display: none,动画根本不会启动

display是离散属性,没有中间态,浏览器压根不支持对它做transition。你写transition: all 0.3s + display: nonedisplay部分会被静默丢弃,而opacity等其他属性也会被强制中断——因为display: none会让元素立刻脱离渲染树。

正确做法是用opacityvisibility协同控制:

  1. 初始态:opacity: 0; visibility: hidden;
  2. 过渡声明:transition: opacity 0.25s ease, visibility 0s 0.25s;visibility延迟生效,卡在opacity结束那一刻)
  3. 关闭后,在transitionend事件里检查event.propertyName === 'opacity',再执行el.style.display = 'none'el.setAttribute('aria-hidden', 'true')

JS快速切换class导致过渡被覆盖

用户连点关闭按钮,或动画未结束就再次触发打开,会造成新样式覆盖旧样式。浏览器不会自动取消正在进行的transition,结果就是卡在中间帧、跳变、甚至只动了10%就停住。

关键不是阻止点击,而是让动画链可控:

  1. 加个开关变量,比如isTransitioning,在transitionend里重置
  2. 监听时过滤属性:if (e.propertyName !== 'opacity' && e.propertyName !== 'transform') return;
  3. 避免在同一个任务中连续增删 class:必要时用requestAnimationFramesetTimeout(() => ..., 0)让样式重排落地

height: auto 或未设初始值,起点不明确

想用height做展开/收起?height: auto无法插值——浏览器不知道“auto”到底等于多少像素,自然没法算中间帧。哪怕你写了transition: height 0.3s,它也只会从当前计算高度直接跳到0auto,毫无过渡。

如果真要过渡高度,必须提供明确数值起点:

  1. 打开前,先用getComputedStyle(el).height读出现有高度
  2. 设为内联style.height(比如el.style.height = '240px';
  3. 再加height: 0类触发收起
  4. 收起完成后,再清空style.height,回归height: auto

更稳的方案是放弃height,改用max-height + overflow: hidden,但得预估一个足够大的值(比如max-height: 500px),否则收不全。

真正容易被忽略的,是把transition当成一次性配置——它得像呼吸一样持续存在,而不是“开的时候有、关的时候就撤”。一旦断掉,所有动画契约就失效了。

相关文章

精彩推荐