You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合@starting-style与allow-discrete在Dialog关闭过渡后移除元素

使用@starting-style和allow-discrete实现Dialog过渡并在关闭后移除DOM元素

问题背景

我通过@starting-style和allow-discrete实现了Dialog的进入/退出过渡效果,但需要在Dialog关闭且过渡完全结束后,将其从DOM中移除以避免冗余元素。尝试三种方法均存在缺陷:

  • 直接在onclose事件中调用remove():立即移除元素,导致退出过渡完全丢失。
  • 通过transitionend事件移除元素:过渡未完成时可能因display属性被设置为none等原因中断,事件无法触发,元素残留。
  • 监听transitionstart后设置定时器移除:硬编码过渡时长,不够优雅且易出现时间偏差。

原代码

CSS

dialog {
  &,
  &::backdrop {
    opacity: 0;
    transition: opacity 0.15s, display 0.15s allow-discrete, overlay 0.15s allow-discrete;
    transition-timing-function: ease-out;
  }
  &::backdrop {
    background-color: black;
  }
  &:modal {
    opacity: 1;
    &::backdrop {
      opacity: 0.5;
    }
    @starting-style {
      &,
      &::backdrop {
        opacity: 0;
      }
    }
  }
}

HTML

<button type="button">open dialog</button>

JavaScript

document.querySelector('button').onclick = event => {
  const dialog = document.createElement('dialog')
  const close_button = event.currentTarget.cloneNode()
  close_button.textContent = 'close'
  close_button.onclick = () => dialog.close()
  dialog.append(close_button)
  
  // 方法1:直接移除丢失过渡
  // dialog.onclose = Element.prototype.remove

  // 方法2:transitionend可能不触发
  // dialog.onclose = () => {
  //   dialog.ontransitionend = Element.prototype.remove
  // }
  
  // 方法3:定时器不够优雅
  // dialog.addEventListener(
  //   'transitionend',
  //   () => {
  //     dialog.ontransitionstart = () => setTimeout(() => dialog.remove(), 150)
  //   },
  //   {once: true}
  // )
  document.body.append(dialog)
  dialog.showModal()
}

解决方案

核心思路是监听Dialog自身的opacity过渡结束事件,同时设置与过渡时长一致的兜底定时器,确保元素无论如何都会被移除,且避免过渡丢失。修改后的JavaScript代码如下:

document.querySelector('button').onclick = event => {
  const dialog = document.createElement('dialog')
  const close_button = event.currentTarget.cloneNode()
  const TRANSITION_DURATION = 150; // 与CSS中opacity过渡时长保持一致

  close_button.textContent = 'close'
  close_button.onclick = () => dialog.close()
  dialog.append(close_button)

  dialog.addEventListener('close', () => {
    const removeDialog = () => {
      dialog.remove();
      clearTimeout(removeTimer);
      dialog.removeEventListener('transitionend', handleTransitionEnd);
    };

    const handleTransitionEnd = (e) => {
      // 只处理dialog元素的opacity过渡结束事件
      if (e.target === dialog && e.propertyName === 'opacity') {
        removeDialog();
      }
    };

    dialog.addEventListener('transitionend', handleTransitionEnd);
    // 兜底定时器,防止transitionend事件未触发
    const removeTimer = setTimeout(removeDialog, TRANSITION_DURATION);
  });

  document.body.append(dialog)
  dialog.showModal()
}

方案说明

  1. 精准监听过渡:过滤掉::backdrop的过渡事件,只响应Dialog自身的opacity属性过渡完成,确保触发时机准确。
  2. 兜底机制:定时器时长与CSS过渡时长完全匹配,避免因浏览器行为导致transitionend未触发时元素残留。
  3. 资源清理:移除元素时清理定时器和事件监听,避免内存泄漏。

CSS部分无需修改,现有配置可正确触发进入/退出过渡:

  • 进入:showModal()使Dialog进入:modal状态,@starting-style触发从opacity:0到opacity:1的过渡。
  • 退出:close()使Dialog离开:modal状态,触发从opacity:1到opacity:0的过渡,display和overlay属性会在过渡周期结束后切换(因allow-discrete)。

内容的提问来源于stack exchange,提问作者user24891761

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 09:35:24