为何Promise在CSS顺序过渡动画中未按预期生效?
我想在页面任意位置放置元素,再通过多段顺序过渡动画处理它:元素初始在左上角(0,0),先通过async/await将它移动到起始位置(比如100,100),再执行后续过渡。但发现除非在初始位置设置函数里用setTimeout(),否则后续过渡会直接基于(0,0)的初始位置执行,而非新设置的(100,100)位置。
我知道有多种解决办法,但想搞懂为什么await没生效? 注意,加了setTimeout之后await就能正常工作了。
代码示例
HTML
<div class="actor" id="actor"></div>
CSS
.actor { border-radius: 25px; width: 50px; height: 50px; background: #f80; position: absolute; top: 0; left: 0; transition: 3000ms ease-out; }
JavaScript
addTransition = (el, styleProps, durationMs = 0) => { return new Promise((resolve, reject) => { function handleTransitionEnd() { resolve(el);} el.addEventListener("transitionend", handleTransitionEnd, { once: true }); if (durationMs > 0) el.style.setProperty("transition", `${durationMs}ms`); Object.entries(styleProps).forEach(([prop, value]) => { el.style.setProperty(prop, value); }); }); } setCSSProperty = (el, prop, val) => { return new Promise((resolve, reject) => { document.styleSheets[0].rules[0].style.removeProperty("transition"); el.style.setProperty(prop, val); // 去掉这个setTimeout或者设为0的话,animate函数里的代码会直接继续执行,不会等这个promise resolve setTimeout(() => { resolve(el); }, 1); // 在CodePen里设1ms就行,我i86台式机上至少要设15ms }); } async function animate() { const actor = document.getElementById("actor"); let x = ((window.innerWidth - actor.getBoundingClientRect().width) * .5); await this.setCSSProperty(actor, "left", x + "px"); await this.addTransition(actor, { left: "40px", }, 2000); } animate()
核心原因是浏览器的DOM更新和JavaScript执行是异步的,且DOM更新会被批量延迟处理。
当你在setCSSProperty里修改元素的left属性后,浏览器并不会立即更新DOM的布局状态——它会把这类DOM修改操作攒到当前JavaScript任务执行完,再统一进行重排(reflow)和重绘(repaint)。
而如果没有setTimeout,你的Promise会在修改样式后立即resolve,await结束后直接执行addTransition。这时候浏览器还没来得及更新元素的实际位置,addTransition里设置新的left值时,浏览器会认为元素的起始位置还是原来的(0,0),自然就从那里开始过渡了。
加了setTimeout之后,哪怕只设1ms,也会把Promise的resolve逻辑放到下一个宏任务里执行。当前JavaScript任务执行完毕后,浏览器会先完成DOM的重排更新,等下一个宏任务触发时,元素的位置已经是你设置的新位置了,这时候再执行addTransition,过渡就会基于正确的起始位置生效。
简单说:await只等Promise resolve,但Promise resolve的时机如果早于浏览器DOM更新,后续代码拿到的还是旧的DOM状态。setTimeout给了浏览器时间完成DOM更新,所以await之后的代码能拿到正确的位置。
内容的提问来源于stack exchange,提问作者Adam Cole

