如何结合@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() }
方案说明
- 精准监听过渡:过滤掉
::backdrop的过渡事件,只响应Dialog自身的opacity属性过渡完成,确保触发时机准确。 - 兜底机制:定时器时长与CSS过渡时长完全匹配,避免因浏览器行为导致
transitionend未触发时元素残留。 - 资源清理:移除元素时清理定时器和事件监听,避免内存泄漏。
CSS部分无需修改,现有配置可正确触发进入/退出过渡:
- 进入:
showModal()使Dialog进入:modal状态,@starting-style触发从opacity:0到opacity:1的过渡。 - 退出:
close()使Dialog离开:modal状态,触发从opacity:1到opacity:0的过渡,display和overlay属性会在过渡周期结束后切换(因allow-discrete)。
内容的提问来源于stack exchange,提问作者user24891761
相关产品推荐
相关产品推荐

