requestAnimationFrame 和 getComputedStyle 无法按预期工作(跟随 Jake Archibalds "In the Loop"演讲)



我正在观看并跟随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", () => {

然后我们设置新的transformtransition值。

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>

相关内容

  • 没有找到相关文章

最新更新