为什么使用 Promise.then 设置 CSS 属性实际上不会发生在 then 块中?

Ric*_*ard 12 javascript css settimeout promise

请尝试运行以下代码段,然后单击该框。

const box = document.querySelector('.box')
box.addEventListener('click', e => {
  if (!box.style.transform) {
    box.style.transform = 'translateX(100px)'
    new Promise(resolve => {
      setTimeout(() => {
        box.style.transition = 'none'
        box.style.transform = ''
        resolve('Transition complete')
      }, 2000)
    }).then(() => {
      box.style.transition = ''
    })
  }
})
Run Code Online (Sandbox Code Playgroud)
.box {
  width: 100px;
  height: 100px;
  border-radius: 5px;
  background-color: #121212;
  transition: all 2s ease;
}
Run Code Online (Sandbox Code Playgroud)
<div class = "box"></div>
Run Code Online (Sandbox Code Playgroud)

我期望发生的事情:

  • 点击发生
  • Box 开始水平平移 100px(此操作需要两秒钟)
  • 单击时,Promise还会创建一个新的。里面说Promise,一个setTimeout函数设置为2秒
  • 操作完成后(两秒钟过去了),setTimeout运行其回调函数并设置transition为无。这样做之后,setTimeout也恢复transform到它的原始值,从而使框出现在原始位置。
  • 盒子出现在原来的位置,这里没有过渡效果问题
  • 所有这些完成后,将transition框的值设置回其原始值

但是,可以看出,运行时transition似乎没有该值none。我知道还有其他方法可以实现上述目标,例如使用关键帧和transitionend,但为什么会发生这种情况?transition只有setTimeout完成回调,我才显式地将返回值设置为原始值,从而解决了 Promise。

编辑

根据请求,这是显示有问题行为的代码的 gif: 问题

Cer*_*nce 5

事件循环批处理样式更改。如果您在一行上更改元素的样式,浏览器不会立即显示该更改;它会等到下一个动画帧。这就是为什么,例如

elm.style.width = '10px';
elm.style.width = '100px';
Run Code Online (Sandbox Code Playgroud)

不会导致闪烁;浏览器只关心所有 Javascript 完成后设置的样式值。

渲染发生所有 Javascript 完成后,包括微任务。将.then在microtask诺言的发生(如所有其他JavaScript已经完成,这将有效地立即运行,但之前别的-比如渲染-有机会来运行)。

你在做什么是你的设置transition属性''在microtask,浏览器已经开始呈现变化引起的之前style.transform = ''

如果您在 a 之后requestAnimationFrame(将在下一次重绘之前运行)重置转换为空字符串,然后在 a 之后setTimeout(将在下一次重绘之后运行),它将按预期工作:

elm.style.width = '10px';
elm.style.width = '100px';
Run Code Online (Sandbox Code Playgroud)
const box = document.querySelector('.box')
box.addEventListener('click', e => {
  if (!box.style.transform) {
    box.style.transform = 'translateX(100px)'
    setTimeout(() => {
      box.style.transition = 'none'
      box.style.transform = ''
      // resolve('Transition complete')
      requestAnimationFrame(() => {
        setTimeout(() => {
          box.style.transition = ''
        });
      });
    }, 2000)
  }
})
Run Code Online (Sandbox Code Playgroud)
.box {
  width: 100px;
  height: 100px;
  border-radius: 5px;
  background-color: #121212;
  transition: all 2s ease;
}
Run Code Online (Sandbox Code Playgroud)

  • 这不完全正确。事件循环没有提及任何有关样式更改的信息。大多数浏览器都会尽可能地尝试等待下一个绘画帧,但仅此而已。[更多信息](/sf/answers/4213993311/);-) (2认同)