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

HTML dialog元素关闭时.getAnimations()返回的Promise为何会被拒绝?

HTML dialog元素关闭时.getAnimations()返回的Promise为何会被拒绝?

嘿,这个问题我之前做dialog过渡动画的时候也踩过类似的坑,咱们来唠唠这背后的原因和解决办法~

首先,你遇到的Promise被拒绝的情况,大概率是因为动画被浏览器强制中止了,而不是正常播放完成。

为什么会被中止?

当dialog的close事件触发时,浏览器其实已经开始改变它的状态了:open属性被移除,元素的堆叠上下文(尤其是你用了Chrome独有的overlay过渡属性)也在快速调整。这时候浏览器可能会直接终止和dialog(包括它的backdrop)关联的部分动画,导致animation.finished这个Promise抛出AbortError而拒绝。

加上你用了Promise.any——这个方法的特性是只要有一个Promise被拒绝,它就会立即跟着拒绝,所以哪怕只有一个backdrop动画被中止,整个逻辑就会“翻车”。

该怎么解决?

给你几个实用的调整方案:

1. 换用更稳妥的Promise处理方式

别再用Promise.any了,换成Promise.allSettled或者给每个动画的finishedPromise加错误捕获:

$dialog.addEventListener('close', async function() {
  // 别忘了同时获取dialog本身和backdrop的动画!
  const allAnimations = [
    ...$dialog.getAnimations(),
    ...$dialog.backdropElement.getAnimations()
  ];

  if (allAnimations.length) {
    // 方案一:用allSettled,等所有动画要么完成要么中止
    await Promise.allSettled(allAnimations.map(anim => anim.finished));
    
    // 方案二:给每个Promise加catch,忽略中止错误
    // await Promise.all(allAnimations.map(anim => anim.finished.catch(() => {})));
    
    console.log('After close');
  }
});

这样不管个别动画有没有被中止,我们都能等所有动画相关的操作结束后再执行后续逻辑。

2. 换个时机触发关闭逻辑(更推荐)

与其在close事件里被动等动画,不如主动控制退出流程:先触发过渡动画,等动画真的完成后再调用dialog.close()。

调整JS代码:

const $button = document.querySelector('button');
const $dialog = document.querySelector('dialog');

$button.addEventListener('click', function() {
  if ($dialog.open) {
    // 先给dialog加一个关闭状态的类
    $dialog.classList.add('closing');
    
    // 监听动画结束事件,完成后再真正关闭dialog
    const handleAnimEnd = () => {
      $dialog.close();
      $dialog.classList.remove('closing');
      $dialog.removeEventListener('transitionend', handleAnimEnd);
    };
    // 用once: true确保只触发一次
    $dialog.addEventListener('transitionend', handleAnimEnd, { once: true });
  } else {
    $dialog.showModal();
  }
});

对应的CSS也要调整,用类来控制退出过渡,避开overlay的兼容性问题:

.dialog {
  --_transition-duration: 200ms;
  border: 0;
  border-radius: .25rem;
  box-shadow: 0 0.0625rem .5rem 0 rgb(0, 0, 0, .1);
  opacity: 0;
  scale: .9;
  overscroll-behavior: contain;
  padding: 0;
  transition: opacity var(--_transition-duration) linear, scale var(--_transition-duration) linear;
  width: min(calc(100% - 2rem), 30rem);
}

.dialog[open] {
  opacity: 1;
  scale: 1;
}

/* 关闭时的过渡状态 */
.dialog.closing {
  opacity: 0;
  scale: .9;
}

.dialog::backdrop {
  background-color: rgb(0, 0, 0, .375);
  opacity: 0;
  transition: opacity var(--_transition-duration) linear;
}

.dialog[open]::backdrop {
  opacity: 1;
}

.dialog.closing::backdrop {
  opacity: 0;
}

这种方式完全由我们控制动画和关闭的时机,浏览器不会中途强制中止动画,行为更稳定,还能兼容Safari和Firefox。

是正常行为还是浏览器bug?

更偏向于Chrome在处理overlay过渡和dialog关闭时机的边缘场景问题。毕竟overlay是比较新的属性,目前只有Chrome支持,浏览器在结合dialog的状态变化处理这类过渡时,逻辑还不够完善,导致动画被意外中止。

如果只在Chrome环境下开发,用第一种Promise的调整方式就能解决;如果要兼容多浏览器,第二种主动控制的方案会更可靠。

备注:内容来源于stack exchange,提问作者R. Groot Jebbink

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 17:05:28