chrome的bug:requestAnimationFrame

198 阅读3分钟

前言:

在阅读vue的设计与实现的时候发现一个问题之前使用的问题。 在vue中的Transition的实现的时候。通过类名的变化来实现动效,这里涉及虚拟dom和真实dom的变动,如果是真实dom自然是没有问题的,问题在虚拟dom的的渲染的时候,我们需要手动的控制循序来实现动画,但是在使用requestAnimationFrame的时候,按照预计是在下一帧的时候进行渲染,从而出现过度动画。但是不能实现。

解决:

使用双重raf(代码在最后);

问题溯源:

原始的问题出现在:交互

根据该帖子的描述,edge是可以正常执行的,并且已经在2020年修复,但是在我的这里是不行的(mac m1)。 原文的解释如下(也是一个回答):

  • It is always the case that any new main thread side effects will not actually commit until after the next rAF macrotask is completed (i.e. yields to event loop).
  • 任何新的主线程副作用始终不会真正提交,直到之后下一个 rAF 宏任务完成(即产生事件循环)。
  • Thus, adding blocking work to the very next rAF is expected to delay commit -- just as adding long blocking work to any task before the raf callback would have delayed.
  • 因此,向下一个 rAF 添加阻塞工作预计会延迟提交——就像在 raf 回调延迟之前向任何任务添加长阻塞工作一样。
  • Using a second rAF call is one of the many ways to "work around" this-- but only because it lets the task yield; You could have just queued any other high priority macrotask for your long blocking work.
  • 使用第二个 rAF 调用是“解决”此问题的多种方法之一 - 但这只是因为它可以让任务屈服;您可以将任何其他高优先级宏任务排队以进行长时间阻塞工作。

To re-iterate, the second rAF is not itself doing anything to force the animation, which I have seen being used as advise (see stack overflow:
重申一下,第二个 rAF 本身并没有做任何事情来强制动画,我已经看到它被用作建议(请参阅堆栈溢出:stackoverflow.com/questions/4…). We may have been the ones to start perpetuating this advise, and it may have been true once upon a time.
我们可能是开始延续这一建议的人,而且从前这可能是正确的。
Also, there are exceptions to these rules for when the first rAF actually runs, but I don't think it changes to advise above. For example, if you happen to call rAF from the context of an existing rAF call, you may unknowingly be doing a "double-raf" (surprisingly common when using animation library code).
此外,当第一个 rAF 实际运行时,这些规则也有例外,但我认为它不会改变上面的建议。例如,如果您碰巧从现有 rAF 调用的上下文中调用 rAF,您可能会不知不觉地执行“double-raf”(在使用动画库代码时非常常见)。

其实简单来说就是这个回调函数执行并不是真的在下一个"帧",他会添加到下一个循环(都是宏任务)。 这个好理解,但是这个是使用回调函数,但是这个api是有问题的,

 requestAnimationFrame((t) => {
    console.log('rAF at', t, ', perf now says', performance.now());
 });

mdn描述

回调函数会传入 DOMHighResTimeStamp 参数,该参数与 performance.now() 的返回值相同,它表示 requestAnimationFrame() 开始执行回调函数的时刻。 也就是说上面代码的t和performance.now()相似的, 下面的实例代码详细描述(代码来自chrome的bug):

<html>

<head>
</head>

<body>
  <script>

    const blockThread = (t) => {
      console.log('scheduling work at', t);
      console.log('perf now says', performance.now());
    }

    document.body.addEventListener('click', () => {
      console.log('click at', performance.now());
       requestAnimationFrame((t) => {
         console.log('rAF at', t, ', perf now says', performance.now());
       });
      
    })

  </script>
</body>
</head>
// 输出
// click at 2483418
// animation.html:16 rAF at 2483410.175 , perf now says 2483419

可以看到这里的t的输出时间不对,并不符合执行的的时间。但是使用双重rfa就可以了,

 requestAnimationFrame(() => requestAnimationFrame((t) => {
        console.log('rAF at', t, ', perf now says', performance.now());
      }));
      // 输出
      //click at 1676.3999999985099
//animation.html:21 rAF at 1685.368 , perf now says 1686.1999999955297