从 innerWidth 到 scrollHeight,从 getBoundingClientRect 到 Visual Viewport;对照一张心智表分清「布局盒」「可见区域」「滚动尺寸」与移动端动态工具栏下的视口单位。
移动端适配、全屏弹层、自定义滚动容器时,「宽度」「高度」一词往往指的不是同一件东西:浏览器窗口、布局视口、视觉视口、文档流、元素盒模型各自有一套可读尺寸。下面按脚本可读 API与CSS 单位两块归纳,便于对照选用。
1. window:与「窗口」相关的尺寸
| API | 含义 |
|---|---|
window.innerWidth / innerHeight | 视口(viewport)内部宽高,包含滚动条占用的区域(若出现经典滚动条)。常用于「当前可见布局区域」的近似。 |
window.outerWidth / outerHeight | 整个浏览器窗口外沿(含标题栏、工具栏等因浏览器/系统而异的部分)。少用于布局,多用于诊断。 |
window.devicePixelRatio | 设备像素比,不是「宽度」本身,但影响 CSS 像素 ↔ 物理像素 的换算;高清屏上为 2、3 等。 |
与「文档有多长」无关:inner* 只描述窗口里的那块视口,文档可能比它高很多。
2. screen:物理屏幕与可用区域
| API | 含义 |
|---|---|
screen.width / screen.height | 屏幕分辨率(设备相关,单位逻辑像素侧通常与系统报告一致)。 |
screen.availWidth / screen.availHeight | 扣除系统任务栏、Dock 等之后的可用区域。 |
一般不参与页面布局,偶尔用于大屏 kiosk、 Electron 或统计。
3. document / documentElement / body:文档与根元素
页面真实「内容有多宽多高」,要看根元素和 body(与浏览器怪异模式有关,现代标准下以 document.documentElement 为主)。
对 document.documentElement(即 <html>) 与常见块级 body:
| 属性 | 典型含义 |
|---|---|
clientWidth / clientHeight | 内容区 + padding,不含滚动条、不含 border;可视区域内能「容纳」的客户端尺寸,受视口与样式影响。 |
offsetWidth / offsetHeight | 见下文 「offset 专节」:本质是 border box 的布局宽高(整数像素),不含 margin。 |
scrollWidth / scrollHeight | 可滚动内容的总宽高(含溢出不可见部分)。scrollHeight 常用来判断「整页内容高度」。 |
记忆口诀:
- client:看「padding 以内的可视区域」有多大(还要再扣掉元素上的滚动条);
- offset:看「border box 在布局里占了多少宽/高」;
- scroll:看「如果全部展开,内容一共多大」。
offsetWidth / offsetHeight 到底量的是什么
在标准盒模型下,可以把它们理解成 CSS 的 border box 外沿之间的间距(单位:CSS 像素),并且是 向零取整后的整数:
offsetWidth≈border-left+padding-left+ 内容区宽度 +padding-right+border-rightoffsetHeight≈ 上、下方向的border+padding+ 内容区高度
因此:
- 不包含
margin。外边距不参与「元素自身报告的宽高」。 - 包含
border与padding。改大border会直接推高offset*。 - 与
transform无关。scale、rotate等只影响绘制与getBoundingClientRect(),不改变offsetWidth/offsetHeight(它们反映的是布局尺寸,不是屏幕上变形后的包络)。 - 与
position无关。offset*不是坐标,只是宽高。
只问一句:offset* 包不包含滚动条?
- 包含——前提是这个滚动条画在当前元素自己身上,且是占位式(经典)竖条/横条:它会占掉 border 内侧的一块厚度,算进
offsetWidth(常见是纵向条占宽度)或offsetHeight(横向条占高度)。 - 对照:
clientWidth/clientHeight不包含这条滚动条,量的是「扣掉滚动条之后、padding 以内的可视区域」;所以offset* - client*在只有一条经典滚动条时,常被用来估算滚动条粗细。 - 例外:系统/浏览器用 overlay 滚动条(不挤占布局、浮在内容上方)时,可能几乎不占布局宽度,
offset*与client*的差会变小;移动端、macOS「覆盖滚动条」等以本机实测为准。
和 clientWidth / clientHeight 对比(仅记关系即可):
client*:大致是 padding box 内侧的可视宽高,减去元素上的滚动条,不含 border。offset*:border box 总宽高,含 border,对滚动条的处理与client*不同,所以二者之差不总是「两倍的 border」,需要结合是否有滚动条判断。
offsetLeft / offsetTop / offsetParent(顺带)
与「宽高」配套的定位读数:
offsetParent:作为偏移参照的最近一层定位父级(position非static的祖先,或table/td/th、body等规则,详见 MDN)。offsetLeft/offsetTop:当前元素 border box 左上角相对offsetParent的 padding 内侧的偏移(同样大致对应布局,不受transform)。
要做「相对视口」的碰撞检测,仍优先 getBoundingClientRect();offset* 家族更适合 「布局尺寸 / 相对 offsetParent 的排版位置」。
4. 普通元素:同一套 client / offset / scroll
任意 HTMLElement 上三类属性与上一节同名:度量的是当前元素自身的盒,不是相对视口。
额外常用:
| API | 含义 |
|---|---|
element.getBoundingClientRect() | 相对视口的 left/top/right/bottom/width/height(浮点,受 CSS 变换影响)。滚动后也会变,是「此刻屏幕上在哪」的首选。 |
window.scrollY / scrollX(或 pageYOffset) | 文档纵向/横向已滚动距离,与 getBoundingClientRect 配合可算「相对文档」的位置。 |
offsetWidth 与 getBoundingClientRect().width 何时不一致:前者是未变换的布局 border box 整数宽;后者是变换后在视口中的轴对齐包络宽(可非整数)。做动画缩放时用后者;做「占栅格多宽」时用前者。
5. Visual Viewport:移动端地址栏、缩放与虚拟键盘
移动浏览器里,布局视口与「用户眼睛看到的区域」可能不一致。window.visualViewport(若存在)描述视觉视口:
| 属性 | 含义 |
|---|---|
width / height | 当前视觉视口 CSS 像素尺寸(会随缩放、地址栏显隐、键盘弹出等变化)。 |
offsetTop / offsetLeft | 视觉视口相对布局视口的偏移。 |
scale | pinch 缩放因子。 |
监听 resize、scroll 可做:键盘顶起输入框、固定底栏避让等。详见 MDN:Visual Viewport API。
6. CSS 长度与视口单位
| 单位 | 含义 |
|---|---|
px | CSS 像素,布局基础单位。 |
% | 相对包含块对应维度;高度链路上若父级未定义高度,容易出现「百分比无效」。 |
vw / vh | 视口宽/高的 1%。经典问题:移动端 100vh 常包含「地址栏收起前后」不一致的区域,导致整屏高度跳动。 |
svh / lvh / dvh(现代浏览器) | 小视口、大视口、动态视口高度:dvh 随工具栏显隐变化,更接近「当前可见高度」;svh 偏保守(偏小),lvh 偏大。 |
vmin / vmax | vw 与 vh 中较小/较大者的 1%。 |
工程上:全屏一屏高优先尝试 100dvh(并配 100vh 回退),比单用 vh 稳。
7. 选用建议(很短)
- 「浏览器窗口里可见区域有多大」 →
window.innerWidth/innerHeight;移动端精细行为 →visualViewport。 - 「整页内容总高度」 →
document.documentElement.scrollHeight(注意body与html的 margin/高度样式)。 - 「border box 在布局里占多宽」(含 border/padding、整数、不吃 transform)→
offsetWidth;「当前在视口里画出来多大」(浮点、吃 transform)→getBoundingClientRect()。 - CSS 全屏高度 → 优先
dvh,兼容层用vh+min-height: -webkit-fill-available等老方案按需补。
8. 小结表
| 层级 | 典型读取方式 |
|---|---|
| 窗口视口 | innerWidth/innerHeight |
| 视觉视口(移动) | visualViewport.width/height |
| 文档滚动尺寸 | document.documentElement.scrollWidth/scrollHeight |
| 元素布局盒 | offsetWidth/offsetHeight |
| 元素视口矩形 | getBoundingClientRect() |
| 样式一屏高 | 100dvh(+ 100vh 回退) |
把「相对谁」(视口、文档、父元素、布局盒)先想清楚,再选 API 或单位,一般就不会和「宽度对不上」这类问题缠斗太久。
评论
加载中...