requestFullscreen 后等待回流结束
Wait for reflow to end after requestFullscreen
好的,伙计们,这是给你们所有巫师的一个棘手的问题...
我正在开发一个沉浸式网络应用程序,它会覆盖移动设备上的默认触摸滚动行为。内容分为使用 100% 视口的页面,导航通过在 页面 .
之间上下滑动来处理
在第一次滑动时,我在 body
元素上调用 requestFullscreen()
,这当然会在视口调整大小时引起回流。问题是我还希望第一次滑动触发自定义滚动行为,但我使用的是 Element.nextElementSibling.scrollIntoView({ block : start, behavior : 'smooth' })
,直到回流完成,下一页的顶部边缘(HTMLSectionElement
)已经可见所以滚动不会发生。
如果我使用 setTimeout
等待大约 600 毫秒直到回流完成,滚动效果会按预期工作,但我对这种 hacky 解决方法不满意,我更愿意使用更优雅的异步解决方案.
我首先尝试从 requestFullscreen 返回的 Promise 的 resolve
执行器内部触发滚动效果,但这没有帮助。这个承诺在执行流程的早期就解决了。
然后我在 fullscreenchange
事件处理程序中尝试。这里也不走运,因为此事件会立即触发 在 全屏更改发生之前。
最后,我尝试从 window resize
事件处理程序内部进行尝试,但这会在回流发生之前触发。我在这里也添加了一个requestIdleCallback
,但没有任何区别。
所以我的问题是... 是否有任何可靠的方法来检测回流操作的结束? 或者...有没有人有比提供更好的 B 计划继续使用 scrollIntoView
并将我自己的滚动效果编码到 window 调整大小处理程序中。
万一有人想知道,我通过 debounce 代理函数从 window 调整大小事件侦听器触发我的滚动效果来处理这种情况。它仍然使用 setTimeout
但不断重置计数器以确保在所有调整大小事件都已触发后发生滚动(在 Chrome 的情况下为 4)。
这不是最优雅的解决方案,滚动效果有可能比绝对必要的延迟更多,但至少滚动永远不会被回流阻塞,我可以接受。
希望有一天我们会有一个 ReflowEndEvent
事件或类似的事件,我们可以监听。
好的,未来的 google 员工,几年后我带着 REAL、non-hacky 这个问题的答案回来了。
诀窍是使用两步 requestAnimationFrame
chain inside a ResizeObserver
。第一个回调在回流发生之前触发。您可以使用此回调对 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 节点上的任何调整大小操作。
好的,伙计们,这是给你们所有巫师的一个棘手的问题...
我正在开发一个沉浸式网络应用程序,它会覆盖移动设备上的默认触摸滚动行为。内容分为使用 100% 视口的页面,导航通过在 页面 .
之间上下滑动来处理在第一次滑动时,我在 body
元素上调用 requestFullscreen()
,这当然会在视口调整大小时引起回流。问题是我还希望第一次滑动触发自定义滚动行为,但我使用的是 Element.nextElementSibling.scrollIntoView({ block : start, behavior : 'smooth' })
,直到回流完成,下一页的顶部边缘(HTMLSectionElement
)已经可见所以滚动不会发生。
如果我使用 setTimeout
等待大约 600 毫秒直到回流完成,滚动效果会按预期工作,但我对这种 hacky 解决方法不满意,我更愿意使用更优雅的异步解决方案.
我首先尝试从 requestFullscreen 返回的 Promise 的 resolve
执行器内部触发滚动效果,但这没有帮助。这个承诺在执行流程的早期就解决了。
然后我在 fullscreenchange
事件处理程序中尝试。这里也不走运,因为此事件会立即触发 在 全屏更改发生之前。
最后,我尝试从 window resize
事件处理程序内部进行尝试,但这会在回流发生之前触发。我在这里也添加了一个requestIdleCallback
,但没有任何区别。
所以我的问题是... 是否有任何可靠的方法来检测回流操作的结束? 或者...有没有人有比提供更好的 B 计划继续使用 scrollIntoView
并将我自己的滚动效果编码到 window 调整大小处理程序中。
万一有人想知道,我通过 debounce 代理函数从 window 调整大小事件侦听器触发我的滚动效果来处理这种情况。它仍然使用 setTimeout
但不断重置计数器以确保在所有调整大小事件都已触发后发生滚动(在 Chrome 的情况下为 4)。
这不是最优雅的解决方案,滚动效果有可能比绝对必要的延迟更多,但至少滚动永远不会被回流阻塞,我可以接受。
希望有一天我们会有一个 ReflowEndEvent
事件或类似的事件,我们可以监听。
好的,未来的 google 员工,几年后我带着 REAL、non-hacky 这个问题的答案回来了。
诀窍是使用两步 requestAnimationFrame
chain inside a ResizeObserver
。第一个回调在回流发生之前触发。您可以使用此回调对 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 节点上的任何调整大小操作。