我正在观看并跟随Jake Archibald关于事件循环的演讲。在某些时候,他试图移动一个框,首先是1000px,然后回到500px。你也可以在这里观看视频:
Jake Archibald: In The Loop - JSConf。亚洲(22:10)
他提供了两种解决方案,我不能让他们中的任何一个为我工作,即使(我认为)我做的完全一样。第一个解决方案是:
button.addEventListener("click", () => {
box.style.transform = "translateX(1000px)";
box.style.transition = "transform 1s ease-in-out";
requestAnimationFrame(() => {
requestAnimationFrame(() => {
box.style.transform = "translateX(500px)";
});
});
});
第二个"hacky"如他所说,其中之一是:
button.addEventListener("click", () => {
box.style.transform = "translateX(1000px)";
box.style.transition = "transform 1s ease-in-out";
getComputedStyle(box).transform;
box.style.transform = "translateX(500px)";
});
在这两种情况下,盒子都只是移动到500px,而它应该先移动到1000px,然后再回到500px。
我错过了什么吗?我做错了什么吗?或者自2018年以来情况发生了很大变化,以至于这种解决方案不再有效?
注:我在Ubuntu 22.04上使用Chrome版本102.0.5005.61。
视频显示的是错误的。
对于这个解释,我将使用"hacky"因为它是同步的,所以看起来更容易理解,因此我们不必担心什么时候会发生回流。(如果你知道你在做什么,它实际上并不比任何异步方式更黑客)。
要使转换工作,我们需要一个初始值,一个目标和一个标志,告诉我们可以转换给定的属性更改。
当此标志被触发时,对可转换值的任何更改都将被用作目标值,并且当前值将被用作我们的转换的初始值。
在这个例子中,在我们点击按钮之前,我们只有一个初始值(none
,即translateX(0px)
)。
button.addEventListener("click", () => {
然后我们设置新的transform
和transition
值。
box.style.transform = "translateX(1000px)";
box.style.transition = "transform 1s ease-in-out";
在这一点上,CSS布局还没有更新,它不知道我们做了什么改变。下一行,强制回流,将处理这个问题。
getComputedStyle(box).transform;
在这里,CSS引擎看到transform
的过渡标志升高,transform
的值发生了变化。因此,它启动了从当前值(0px
)到新值(1000px
)的转换。
但是在它开始渲染过渡之前,
box.style.transform = "translateX(500px)";
将目标更改为500px
。因此,它将更新过渡从,无论它在这一点(仍然是0px
)到新的目的地(500px
)。
同样的情况显然也发生在异步版本中,除了当我们将新值设置为500px
时,我们的元素已经移动了一点(在60Hz显示器上移动了~17px,而它应该只移动了一半)。
这个值不太明显,因为它只有一帧,500px/s和1000px/s之间的速度差异在一帧中不太明显,但如果你有更大的差异,你可以看得更清楚:
const box = document.querySelector(".box");
const button = document.querySelector(".button");
button.addEventListener("click", () => {
// Forcing a huge value here will make the transition
// to start from much farther on the right
// because it will travel a quite big distance on the first frame
// and then start the new transition from the current position
// Depending on your monitor's refresh rate you may even see it
// coming from the right.
box.style.transform = "translateX(500000px)";
box.style.transition = "transform 1s ease-in-out";
requestAnimationFrame(() => {
requestAnimationFrame(() => {
box.style.transform = "translateX(500px)";
});
});
});
// So we can try multiple times
document.querySelector(".reset").onclick = (evt) => {
box.style.transform = "none";
box.style.transition = "none";
box.style.offsetWidth;
}
.box {
width: 50px;
height: 50px;
border: 1px solid;
}
<button class="button">click me</button>
<button class="reset">reset</button>
<div class="box"></div>
我将再次播放"hacky"主在这里。使用同步方式,您可以控制发生的事情。只是要非常小心,不要再污染CSS布局后,你已经强制回流,你会很好,浏览器不会在渲染步骤中重新计算它。
据我所知,这一直是预期的和实际的行为,我非常怀疑自从这次谈话以来这已经改变了,你甚至可以在视频下检索2019年的一些评论,这些评论讨论了视频中解释的不工作。
要获得预期的行为,您需要在计算新的初始位置的回流之后,将转换与目标值的设置一起设置。
const box = document.querySelector(".box");
const button = document.querySelector("button");
button.addEventListener("click", () => {
box.style.transform = "translateX(1000px)";
getComputedStyle(box).transform;
box.style.transform = "translateX(500px)";
box.style.transition = "transform 1s ease-in-out";
});
// So we can try multiple times
document.querySelector(".reset").onclick = (evt) => {
box.style.transform = "none";
box.style.transition = "none";
box.style.offsetWidth;
}
.box {
width: 50px;
height: 50px;
border: 1px solid;
}
<button class="button">click me</button>
<button class="reset">reset</button>
<div class="box"></div>
或
const box = document.querySelector(".box");
const button = document.querySelector("button");
button.addEventListener("click", () => {
box.style.transform = "translateX(1000px)";
requestAnimationFrame(() => {
requestAnimationFrame(() => {
box.style.transform = "translateX(500px)";
box.style.transition = "transform 1s ease-in-out";
});
});
});
// So we can try multiple times
document.querySelector(".reset").onclick = (evt) => {
box.style.transform = "none";
box.style.transition = "none";
box.style.offsetWidth;
}
.box {
width: 50px;
height: 50px;
border: 1px solid;
}
<button class="button">click me</button>
<button class="reset">reset</button>
<div class="box"></div>