requestAnimationframe和getComputedStyle ton n''

发布于 2025-02-06 05:31:18 字数 1043 浏览 2 评论 0原文

我正在观看并跟随杰克·阿奇博尔德(Jake Archibald)关于活动循环的演讲。在某个时候,他想将一个盒子移至1000px,然后返回500px。您还可以在此处观看视频:

jake acrachibald:in loop -jsconf.asia(22:10)(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年以来,事情发生了太大变化,以使该解决方案不再起作用?

PS我在Ubuntu上使用Chrome版本102.0.5005.61 22.04。

I am watching and following along Jake Archibald's speech about the Event Loop. At some point he attemps to move a box, first to 1000px and then back to 500px. You can also watch in the video here:

Jake Archibald: In The Loop - JSConf.Asia (22:10)

He offers two solutions and I couldn't get any of them to work for me, even though (I think) I am doing exactly the same. The first solution is this:

button.addEventListener("click", () => {
box.style.transform = "translateX(1000px)";
box.style.transition = "transform 1s ease-in-out";

requestAnimationFrame(() => {
  requestAnimationFrame(() => {
    box.style.transform = "translateX(500px)";
  });
 });
});

And the second "hacky" one, as he says, is this:

button.addEventListener("click", () => {
   box.style.transform = "translateX(1000px)";
   box.style.transition = "transform 1s ease-in-out";
   getComputedStyle(box).transform;
   box.style.transform = "translateX(500px)";

});

On both occasions the box just moves to 500px, while it should first move to 1000px and then back to 500px.

Am I missing something? Am I doing something wrong? Or have things changed that much since 2018 so that this solution doesn't work anymore?

P.S. I am using Chrome Version 102.0.5005.61 on Ubuntu 22.04.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

方圜几里 2025-02-13 05:31:18

视频显示的是错误的。

对于此解释,我将使用“ hacky”方式,因为它似乎更容易理解,因为它是同步的,因此我们不必怀疑何时发生回流。 (如果您知道自己在做什么,实际上并没有比任何异步方式更骇客)。

为了过渡到工作,我们需要一个初始值,一个目的地和标志,告诉我们可以过渡给定的属性更改。
当升高此标志时,可以将其过渡值的任何更改都用作目的地,并且当前值将用作我们过渡的初始值。

在示例中,在单击按钮之前,我们只有一个初始值(none,即translatex(0px))。

button.addEventListener("click", () => {

然后,我们设置了新的转换transition值。

   box.style.transform = "translateX(1000px)";
   box.style.transition = "transform 1s ease-in-out";

此时,CSS布局尚未更新,它不知道我们确实更改了任何内容。
下一行迫使回流,将解决这一问题。

   getComputedStyle(box).transform;

在这里,CSS引擎可以看到它具有 transition flag transform提出的,并且transform值已更改。因此,它启动了从当前值(0px)的过渡到新的(1000px)。
但是,甚至在它开始渲染该过渡之前,

   box.style.transform = "translateX(500px)";

将目标更改为500px。因此,无论此时在哪里,它都会将过渡从更新到该新目标(500px)。

显然,异步版本也是如此,只是当我们将新值设置为500px时,我们的元素已经移动了一点(在60Hz监视器上〜17px,而它只能移动到其中一半)。
这对于此值并不是很明显,因为它只是一个帧,而在单个帧中,速度差和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>

What the video shows is wrong here.

For this explanation I will use the "hacky" way, since it seems easier to understand as it's synchronous and thus we don't have to wonder when the reflow happens. (And if you know what you're doing it's not actually more hacky than any async way).

For a transition to work, we need an initial value, a destination and a flag telling we're ok to transition a given property change.
When this flag is raised, any change to the value that can be transitioned will be used as the destination, and the current value will be used as the initial value of our transition.

In the example, before we click the button, we only have an initial value (none, i.e translateX(0px)).

button.addEventListener("click", () => {

Then we set both the new transform and transition values.

   box.style.transform = "translateX(1000px)";
   box.style.transition = "transform 1s ease-in-out";

At this point, the CSS layout hasn't been updated, it doesn't know we did change anything.
The next line, which forces the reflow, will take care of that.

   getComputedStyle(box).transform;

And here the CSS engine sees that it has the transition flag for transform raised AND that the transform value has changed. It thus initiates a transition from the current value (0px) to the new one (1000px).
But even before it can start rendering that transition,

   box.style.transform = "translateX(500px)";

changes the destination to be 500px. So it will update the transition to go from wherever it is at this point (still 0px) to that new destination (500px).

The same obviously happens with the async version, except that when we set the new value to 500px our element will already have moved a bit (by ~17px on a 60Hz monitor, while it should have only moved by half of that).
This isn't very noticeable with this value because it's only one frame and the speed difference between 500px/s and 1000px/s isn't very noticeable in a single frame, but if you have a much bigger difference, you can see it better:

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>

So once again I'll play the "hacky" advocate here. Using the synchronous way, you do have control over what happens. Just be very careful that you don't pollute the CSS layout anymore after you've forced that reflow and you'll be fine, the browser won't recalculate it in the rendering step.


As far as I can remember this has always been the expected and actual behavior, I highly doubt this has changed since this talk and you can even retrieve some comments from 2019 under the video that talk about this not working as explained in the video.

To get the expected behavior, you'd need to set the transition along with the setting of the destination value, after the reflow that calculated the new initial position.

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>

or

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>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文