【问题标题】:Wait for reflow to end after requestFullscreenrequestFullscreen 后等待回流结束
【发布时间】:2019-10-17 09:19:24
【问题描述】:

好吧,伙计们,这对你们所有的巫师来说都是一个棘手的问题......

我正在开发一个沉浸式网络应用程序,它会覆盖移动设备上的默认触摸滚动行为。内容分为使用 100% 视口的页面,并且导航是通过在页面之间之间上下滑动来处理的。

在第一次滑动时,我在 body 元素上调用 requestFullscreen(),这当然会在视口调整大小时导致回流。问题是我还希望第一次滑动来触发自定义滚动行为,但我使用的是Element.nextElementSibling.scrollIntoView({ block : start, behavior : 'smooth' }),直到回流完成,下一页的顶部边缘(HTMLSectionElement)已经可见,所以滚动不不会发生。

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

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

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

最后,我从窗口resize 事件处理程序内部尝试,但这会在回流发生之前触发。我也在这里添加了requestIdleCallback,但没有任何区别。

所以我的问题是...... 有没有可靠的方法来检测回流操作的结束? 或者......有没有人有比放弃使用 @987654332 更好的 B 计划@ 并将我自己的滚动效果编码到窗口调整大小处理程序中。

【问题讨论】:

  • reflow 操作是同步的。可能不是什么时候调用它。见this answer of mine。所以,不,你并不是真的在寻找“检测回流操作的结束”。不过,我不确定您在寻找什么,因为您的问题缺少minimal reproducible example。也许您更愿意“强制”重排,以便在您开始滚动时 CSSOM 是最新的,或者您愿意检测调整大小何时结束?但你没有问正确的问题。
  • 大约 2 年前,我问过这个问题,是关于我当时正在开发的一个网络应用程序的一个具体问题。不幸的是,Fullscreen API 请求在 iframe 元素中不起作用,因此添加一个有效的问题 sn-p 是不可行的。无论如何,我 2 年前遇到的具体问题已经不再重要,所以我可能会做的就是重写这个问题,使其成为一个对更广泛的受众更通用和有用的问题。

标签: javascript html window-resize reflow html5-fullscreen


【解决方案1】:

好吧,未来的谷歌员工,几年后我又回来了,为这个问题提供了 REAL非 hacky 的答案。

诀窍是在ResizeObserver 中使用两步requestAnimationFrame 链。第一个回调在回流发生之前触发。您可以使用此回调 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 节点上的任何调整大小操作。

【讨论】:

  • 我已经把它打包成一个整洁的 ES6 类,可以在这里找到github.com/besworks/ReflowObserver
  • 为什么又要等一整帧?只需等待一个任务,或者更好地使用requestPostAnimationFrame(如果可用),请参阅this answer of mine。另请注意,所有浏览器不会同时触发 auto-reflow。例如,Safari 甚至会在 rAF 触发之前将其调用为空闲状态,因此在该浏览器中,如果您在 rAF 中弄乱了 CSSOM,您实际上会在绘画之前强制进行新的、不必要的回流。
  • 优秀笔记@kaiido,我没听说过requestPostAnimationFrame。当我在 Chrome 89 中进行测试时,这确实可以代替第二个 requestAnimationFrame。对于我的用例,我只担心基于 Chromium 的浏览器,但您关于其他浏览器中时间安排的注释可能会帮助其他人缩小他们的问题。
  • 哦,如果您只针对 Chromium 浏览器,请注意他们的 requestAnimationFrame 实现是 completely broken。如果您不从动画文档中调用 rAF,那么您可能根本不会在下一次绘制之前。
  • 帮了大忙,谢谢!
【解决方案2】:

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

这不是最优雅的解决方案,并且滚动效果可能会延迟超过绝对必要的时间,但至少滚动永远不会被回流阻塞,我可以忍受。

希望有一天我们会有一个ReflowEndEvent 事件或类似的东西,我们可以收听。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-02
    • 1970-01-01
    • 1970-01-01
    • 2019-01-31
    • 2013-12-03
    • 1970-01-01
    相关资源
    最近更新 更多