如果我告诉你,有多个视口,该怎么办?
BRRRRAAAAAAAMMMMMMMM
而您当前使用的视口实际上是视口内的一个视口。
BRRRRAAAAAAAMMMMMMMM
有时,DOM 为您提供的数据指的是其中一个视口,而不是另一个。
BRRRRAAAAM...,等等?
千真万确,来看看:
布局视口与可视化视口
上面的视频展示了网页如何滚动和双指张合缩放,右侧是一个迷你地图,显示了视口在网页中的位置。
在常规滚动过程中,操作非常简单。绿色区域表示 position: fixed
项紧贴的布局视口。
引入双指张合缩放功能后,情况会很奇怪。红色框表示可视视口,即我们实际看到的网页部分。此视口可以四处移动,而 position: fixed
元素会留在原来的位置,并附加到布局视口。如果在布局视口的边界进行平移,布局视口会随其一起拖动。
提升兼容性
遗憾的是,Web API 在引用的视口方面不一致,并且在不同浏览器之间也不一致。
例如,element.getBoundingClientRect().y
会返回布局视口内的偏移量。这很酷,但我们经常需要指定在网页中的位置,因此我们会这样写:
element.getBoundingClientRect().y + window.scrollY
不过,许多浏览器为 window.scrollY
使用视觉视口,这意味着当用户通过双指张合进行缩放时,上述代码会中断。
Chrome 61 更改了 window.scrollY
以改为引用布局视口,这意味着上述代码即使在双指张合缩放时也能正常运行。事实上,浏览器正在缓慢地更改所有位置属性,以便引用布局视口。
但只有一个新媒体资源...
向脚本公开可视化视口
新的 API 将视觉视口公开为 window.visualViewport
。它是规范草稿,已获得跨浏览器批准,并将在 Chrome 61 中推出。
console.log(window.visualViewport.width);
window.visualViewport
可以为我们提供以下内容:
visualViewport 个房源 |
|
---|---|
offsetLeft
|
视觉视口左边缘与布局视口之间的距离,以 CSS 像素为单位。 |
offsetTop
|
视觉视口的上边缘与布局视口之间的距离,以 CSS 像素为单位。 |
pageLeft
|
视觉视口左边缘与文档左侧边界之间的距离(以 CSS 像素为单位)。 |
pageTop
|
可视视口的上边缘与文档上边界之间的距离(以 CSS 像素为单位)。 |
width
|
可视视口的宽度(以 CSS 像素为单位)。 |
height
|
可视视口的高度(以 CSS 像素为单位)。 |
scale
|
通过双指张合缩放应用的缩放。如果内容因缩放而大小是原来的两倍,则返回 2 。此问题不受 devicePixelRatio 影响。
|
此外,还有两种事件:
window.visualViewport.addEventListener('resize', listener);
visualViewport 事件 |
|
---|---|
resize
|
在 width 、height 或 scale 发生变化时触发。
|
scroll
|
在 offsetLeft 或 offsetTop 发生变化时触发。
|
演示
本文开头部分的视频是使用 visualViewport
创建的,请在 Chrome 61 及更高版本中查看。这段代码使用 visualViewport
使迷你地图固定在可视视口的右上角,并应用反向缩放,因此无论双指张合缩放如何,它都会始终以相同的大小显示。
问题
仅在可视化视口发生变化时触发事件
这似乎是显而易见的一句话,但当我第一次玩 visualViewport
时,它让我被深深吸引。
如果布局视口可调整大小,但可视视口未调整大小,则您不会收到 resize
事件。不过,在没有可视视口同时更改宽度/高度的情况下,布局视口调整大小的情况极为罕见。
真正的缺点是滚动。如果发生了滚动,但视觉视口相对于布局视口保持静态,则您不会在 visualViewport
上收到 scroll
事件,而这种情况很常见。在常规文档滚动期间,视觉视口会保持锁定在布局视口的左上角,因此 scroll
不会在 visualViewport
上触发。
如果您想了解视觉视口的所有更改(包括 pageTop
和 pageLeft
),还必须监听窗口的滚动事件:
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);
避免向多个监听器重复工作
类似于监听窗口上的 scroll
和 resize
,您可能会因此调用某种“更新”函数。但是,其中许多事件同时发生很常见。如果用户调整窗口大小,则会触发 resize
,但通常还会触发 scroll
。为了提高性能,请避免多次处理更改:
// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);
let pendingUpdate = false;
function update() {
// If we're already going to handle an update, return
if (pendingUpdate) return;
pendingUpdate = true;
// Use requestAnimationFrame so the update happens before next render
requestAnimationFrame(() => {
pendingUpdate = false;
// Handle update here
});
}
我针对此问题提交了规范问题,因为我认为可能有更好的方法,例如单个 update
事件。
事件处理脚本不起作用
由于 Chrome bug,以下操作无法执行:
错误 - 使用事件处理程序
visualViewport.onscroll = () => console.log('scroll!');
请改为执行以下操作:
运行 - 使用事件监听器
visualViewport.addEventListener('scroll', () => console.log('scroll'));
偏移值会四舍五入
我想(嗯,希望是)Chrome 的又一个错误。
offsetLeft
和 offsetTop
会四舍五入,当用户放大后,结果会非常不准确。您在演示过程中可以看到此问题:如果用户缓慢放大和平移,迷你地图会在未缩放的像素之间贴靠。
事件率较慢
与其他 resize
和 scroll
事件一样,这些事件也不会在每一帧时触发,尤其是在移动设备上。您可以在演示中看到这种情况 - 当您通过双指张合进行缩放后,迷你地图将无法保持在视口范围内。
无障碍功能
在演示中,我使用了 visualViewport
来抵消用户的双指张合缩放操作。对于此特定演示来说是合理的,但是在执行任何超出用户期望的放大操作之前,请务必仔细考虑。
visualViewport
可用于改进无障碍功能。例如,如果用户放大,您可以选择隐藏装饰性 position: fixed
项,以免对用户造成干扰。再次强调,一定要避免隐藏用户试图深入了解的内容。
您可以考虑在用户放大地图时将其发布到分析服务。这有助于您找出用户在默认缩放级别下遇到困难的网页。
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
这样就大功告成了!visualViewport
是一个很好的小 API,可以在此过程中解决兼容性问题。