在请求后等待重排结束

时间:2019-05-31 18:07:26

标签: javascript window-resize reflow html5-fullscreen

好的,伙计们,这对于所有向导来说都是一个棘手的问题...

我正在开发一个沉浸式Web应用程序,该应用程序将覆盖移动设备上的默认触摸滚动行为。内容分为使用视口100%的页面,导航是通过在页面之间上下滑动来实现的。

在第一次滑动时,我在requestFullscreen()元素上调用body,这当然会在视口调整大小时引起重排。问题是我也希望第一次滑动即可触发自定义滚动行为,但是我使用的是Element.nextElementSibling.scrollIntoView({ block : start, behavior : 'smooth' }),直到重排完成为止,下一页的上边缘(HTMLSectionElement)已经可见因此滚动不会发生。

如果我使用setTimeout等待大约600毫秒,直到回流完成,滚动效果将按预期工作,但是我对这种棘手的解决方法不满意,我更喜欢使用更优雅的异步解决方案。 / p>

我首先尝试从requestFullscreen返回的Promise的resolve执行程序内部触发滚动效果,但这没有帮助。这个承诺很快就会在执行流程中解决。

然后,我从fullscreenchange事件处理程序中进行了尝试。全屏更改发生之前之前,此事件立即被触发,因此在这里也没有运气。

最后,我在窗口resize事件处理程序中尝试过,但这在重排发生之前触发。我也在这里添加了requestIdleCallback,但没有任何区别。

所以我的问题是... 是否有任何可靠的方法来检测回流操作的结束?或……有人比放弃使用{{ 1}},然后将自己的滚动效果编码到窗口调整大小处理程序中。

2 个答案:

答案 0 :(得分:0)

万一有人想知道,我通过使用debounce代理函数从窗口调整大小事件监听器触发滚动效果来处理这种情况。它仍然使用setTimeout,但会不断重置计数器,以确保在触发所有调整大小事件后发生滚动(对于Chrome,为4)。

这不是最优雅的解决方案,滚动效果可能比绝对必要的延迟得更多,但至少滚动不会被回流所阻碍,我可以忍受。

希望有一天,我们将举行ReflowEndEvent事件或我们可以听的类似事件。

答案 1 :(得分:0)

好吧,未来的谷歌员工,几年后我回来了,对这个问题给出了真实非黑客的答案。

诀窍是在 requestAnimationFrame 内使用两步 ResizeObserver 链。第一个回调在回流发生之前触发。您可以使用此回调 this 对 DOM 进行任何最后一秒的更改。然后,在此回调中,我们再次使用 requestAnimationFrame 指定另一个回调,该回调将在前一帧的绘制之后发生。

您可以通过取消注释下面示例代码中的 debugger; 行来验证此方法的时间。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="description" content="React to element size changes after layout reflow has occured.">
    <title> Reflow Observer Test </title>
    <style>
      * { box-sizing: border-box; }
      html, body { height: 100vh; margin: 0; padding: 0; }
      body { background: grey; }
      body:fullscreen { background: orange;}
      body:fullscreen::backdrop { background: transparent; }
      #toggle { margin: 1rem; }
    </style>
  </head>
  <body>
    <button id="toggle"> Toggle Fullscreen </button>
    <script>
      let resizableNode = document.body;
      
      resizableNode.addEventListener('reflow', event => {
        console.log(event.timeStamp, 'do stuff after reflow here');
      });
      
      let observer = new ResizeObserver(entries => {
        for (let entry of entries) {
          requestAnimationFrame(timestamp => {
            // console.log(timestamp, 'about to reflow'); debugger;
            requestAnimationFrame(timestamp => {
              // console.log(timestamp, 'reflow complete'); debugger;
              entry.target?.dispatchEvent(new CustomEvent('reflow'));
            });
          });
        }
      });
      
      observer.observe(resizableNode);
      
      function toggleFullscreen(element) {
        if (document.fullscreenElement) {
          document.exitFullscreen();
        } else {
          element.requestFullscreen();
        }
      }
      
      document.getElementById('toggle').addEventListener('click', event => {
        toggleFullscreen(resizableNode);
      });
    </script>
  </body>
</html>

在这个例子中,我在 body 元素上使用全屏作为我的触发器,但这可以很容易地应用于任何 DOM 节点上的任何调整大小操作。