浏览器 API 暴露多个坐标系,因为屏幕、浏览器窗口、视口、文档和元素回答不同的测量问题。 因此,仅根据 API 名称选择接口可能造成滚动条、舍入或滚动错误。
窗口、屏幕和视口测量 #
| API | 测量内容 |
|---|---|
window.innerWidth、innerHeight | 布局视口,包括已渲染的经典滚动条 |
window.outerWidth、outerHeight | 浏览器窗口的外部尺寸 |
screen.width、screen.height | CSS 像素中的屏幕尺寸 |
screen.availWidth、screen.availHeight | 排除操作系统保留区域后的屏幕空间 |
document.documentElement.clientWidth、clientHeight | 排除经典滚动条的布局视口 |
CSS 像素是逻辑单位。在高密度显示器上,一个 CSS 像素可能覆盖多个设备像素,
因此 screen.width 不一定等于面板的物理像素数。
outerWidth 和 outerHeight 包含浏览器界面。它们与内部尺寸的差值取决于窗口装饰、
标签页、工具栏和显示缩放;因此不应驱动页面布局。
const viewport = {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
};计算必须排除经典滚动条时,使用根元素的客户端尺寸。
覆盖式滚动条不占用布局宽度,因此 innerWidth 与 clientWidth 的差异取决于平台。
布局视口和视觉视口 #
布局视口建立页面布局使用的包含块。视觉视口是在缩放或屏幕键盘改变未遮挡区域后当前可见的部分。
在支持的浏览器中,window.visualViewport 暴露 width、height、scale、offsetLeft 和 offsetTop:
const viewport = window.visualViewport;
viewport?.addEventListener("resize", positionOverlay);
viewport?.addEventListener("scroll", positionOverlay);需要跟随可见区域的覆盖层应使用这些事件。普通文档布局仍应使用 CSS 和布局视口; 响应视觉视口事件移动整个页面可能会与浏览器滚动发生冲突。
元素盒尺寸 #
| API | 包含区域 | 精度 |
|---|---|---|
clientWidth、clientHeight | 内容和内边距,不含边框和滚动条 | 整数 |
scrollWidth、scrollHeight | 无需滚动时所需的最小内容加内边距范围 | 整数 |
offsetWidth、offsetHeight | 边框盒,包括已渲染的滚动条 | 整数 |
getBoundingClientRect() | 视口坐标中的边框盒几何信息 | 小数 |
不会创建 CSS 盒的内联元素,其 clientWidth 和 clientHeight 为零。
在根元素上,它们表示视口。历史上的 quirks 模式可能让 body 元素参与视口测量,
但标准模式文档应使用 document.documentElement。
可滚动溢出会使 scrollHeight 或 scrollWidth 超过对应的客户端尺寸:
function hasVerticalOverflow(element) {
return element.scrollHeight > element.clientHeight;
}伪元素内容可能贡献滚动尺寸。整数 API 会舍入子像素布局,
因此需要小数几何信息时应使用 getBoundingClientRect()。
坐标系和滚动 #
getBoundingClientRect() 返回相对于视口的坐标:
const rect = element.getBoundingClientRect();
console.log(rect.left, rect.top, rect.width, rect.height);将视口坐标转换为文档坐标时,加上当前滚动偏移:
const documentTop = rect.top + window.scrollY;
const documentLeft = rect.left + window.scrollX;变换会影响返回的矩形。结果是包含变换后边框盒的最小轴对齐视口矩形, 并不是元素未经变换的布局尺寸。
在正确时间测量 #
字体、图像、样式表或应用数据加载后,几何信息可能改变。
应在影响布局的依赖项可用后测量。代码必须响应后续尺寸变化时使用 ResizeObserver:
const observer = new ResizeObserver(([entry]) => {
const { inlineSize, blockSize } = entry.contentBoxSize[0];
console.log({ inlineSize, blockSize });
});
observer.observe(element);读取几何信息可能迫使浏览器完成待处理的样式和布局工作。 动画代码中应先批量读取,再批量写入,因为交替进行布局读取和样式修改可能反复触发同步布局。
requestAnimationFrame 选择渲染阶段,但不会让布局读取免费。
先读取所有所需几何信息,计算下一个状态,然后将样式或 DOM 写入作为单独批次应用。
评论
在 GitHub 上阅读或评论(发表评论需要登录)