主题
读取元素的尺寸(只读)与读取元素的位置 (只读)(Day13 2/3)
本页对应 Day13_DOM,共 6 段视频,约 1 小时 38 分。
学习目标
- 能使用
offsetWidth/offsetHeight、clientWidth/clientHeight、getBoundingClientRect()读取元素尺寸,并说明各自是否包含边框、滚动条及其适用场景。 - 能使用
offsetLeft/offsetTop与getBoundingClientRect()读取元素位置,并准确区分二者分别参照定位祖先元素与视口。 - 能独立实现图片延迟加载:监听页面滚动,判断图片是否进入视口,并通过自定义属性把图片地址写入
src。 - 能读写元素的
scrollTop/scrollLeft控制元素内部内容滚动,并说明其可读可写、最小值为 0、需设置非默认overflow等条件。 - 能通过
document.documentElement.scrollTop读写页面滚动距离,并实现按钮控制页面上下滚动。 - 能分析无缝滚动的实现条件,使用定时器修改
scrollLeft使内容持续滚动,并说明到达边界后如何设计循环方案。
知识点
为什么需要读取元素尺寸
写页面时,很多判断不能靠“看起来差不多”。比如做图片延迟加载、判断元素是否进入视口、做滚动动画,都要先拿到元素的真实尺寸。这里的关键是:读取尺寸和读取样式不是一回事。
转写稿里先演示了用计算样式读宽高:
js
getComputedStyle(box).width
getComputedStyle(box).height如果 CSS 里写的是 width: 600px,读到的往往是字符串 "600px",不是数字;高度没写时可能读到 auto 的计算结果,比如 277。它读到的是内容宽高,不是“总宽总高”。要算总宽总高,还得自己加内边距、边框,既麻烦又容易漏。所以需要专门的一组只读属性来读元素尺寸。
元素的三组尺寸
笔记里给了三组属性,先保留原代码,注意其中 offfsetWidth、offfsetHeight、offsetHeihgt 是拼写错误,正确是 offsetWidth、offsetHeight:
js
元素对象.offfsetWidth / 元素对象.offfsetHeight 获取元素的总宽总高
元素对象.clientWidth / 元素对象.clientHeight 获取元素宽高,内容+内边距
元素对象.scrollWidth / 元素对象.scrollHeight 获取元素宽高,client加上溢出的部分怎么理解?
offsetWidth / offsetHeight:元素占位的总宽高,通常包含内容、内边距、边框,不含外边距。clientWidth / clientHeight:内容区加内边距,不含边框。scrollWidth / scrollHeight:在 client 的基础上,把溢出、需要滚动才能看到的内容也算进去。
怎么用?先问自己要的是“元素占多大”“可视内容区多大”还是“内容总共多大”。比如判断内容有没有溢出,就看 scrollHeight 是否大于 clientHeight。易错点是:这些属性读出来是数字,不带 px,可以直接计算;而 getComputedStyle 读宽度常带单位,不能直接拿来加减。
getBoundingClientRect 读尺寸
笔记还给了:
js
元素对象.getBoundingClientRect() 返回对象,对象包含元素的位置和尺寸信心,对象有如下属性:
元素对象.getBoundingClientRect().width 同offsetWidth
元素对象.getBoundingClientRect().height 同offsetHeihgt这里的“信心”是同音字,应为“信息”;offsetHeihgt 应为 offsetHeight。这个方法返回一个对象,里面既有尺寸也有位置信息。本节只先记尺寸:width 同 offsetWidth,height 同 offsetHeight。易错点是不要把它当成能改尺寸的方法,它只是读取;也不要和后面的位置属性混在一起,本节先掌握尺寸。
获取视口尺寸
笔记代码:
js
// 会包括滚动条本身的宽度
window.innerWidth
window.innerHeight
// 不会包括滚动条本身的宽度
document.documentElenment.clientWidth
document.documentElenment.clientHeightdocumentElenment 是拼写错误,正确是 document.documentElement。为什么需要它?因为 window.innerWidth/innerHeight 能读视口,但会把滚动条本身也算进去;document.documentElement.clientWidth/clientHeight 不算滚动条。转写稿演示:页面没滚动条时两者看起来一样,比如宽 889、高 667;后来加一个高 div 让页面出现纵向滚动条,宽度差了约 17px,这 17px 就是滚动条宽度。大多数浏览器默认滚动条宽度约 17px。
怎么想?元素一般不会跑到滚动条上面,所以做布局判断时,不算滚动条的视口宽高更贴近元素可用区域。怎么做?优先用:
js
document.documentElement.clientWidth
document.documentElement.clientHeight易错点有三个:第一,document.documentElement 不要拼错;第二,根元素用 clientWidth/clientHeight 读视口属于特殊用法,别拿普通元素的规则硬套;第三,视口尺寸是只读的,代码不能随便改用户屏幕大小,转写稿也强调“不能太离谱”,不要想着用代码改视口。
调试与总结
调试时先分清对象:读普通元素尺寸用元素对象;读视口尺寸用 window 或 document.documentElement。再分清口径:总宽高看 offset,内容加内边距看 client,内容溢出看 scroll。最后记住本节主题是“只读”:这些属性用来读,不用来改。真正要改元素尺寸,还是回到 CSS 或行内样式;而视口尺寸不是页面代码能随意修改的东西。
为什么需要读取元素位置
做页面交互时,经常要知道“这个元素现在在哪儿”。比如做定位提示、拖拽、滚动加载、判断元素是否进入视口,都离不开位置信息。靠肉眼估、靠 CSS 里写的值猜都不行:元素可能被父级内边距、边框、外边距影响,页面还可能滚动。因此需要一组只读属性/方法,直接从浏览器布局结果里读位置。本节要分清三套东西:offsetLeft / offsetTop、clientLeft / clientTop、getBoundingClientRect()。它们名字都像“位置”,但参照物完全不同。
offsetLeft / offsetTop:相对第一个定位祖先或页面
为什么需要:想知道元素在文档布局中的横向、纵向偏移,尤其是相对某个定位父容器时,offsetLeft / offsetTop 很直接。怎么想:它的规则和定位的“包含块”很像——找第一个定位的祖先元素,以它的左上角为原点;如果祖先都没有定位,就参照页面。怎么做:
html
<div id="book">内容</div>
<script>
const box = document.querySelector('#book');
console.log(box.offsetLeft, box.offsetTop);
</script>转写稿里的调试很关键:一开始父元素没有定位,读取到类似 30 和 188,这是一直算到页面边界的结果;给父元素加上 position: relative 后,值变成 20 和 178,说明参照物变成了这个定位父元素,而且是从父元素的内边距区域开始算,不是从边框开始。易错点:offsetLeft 不是相对视口;祖先没定位时参照页面,不是参照窗口;页面滚动后它通常不因滚动而改变,因为元素和页面是一起滚的。
clientLeft / clientTop:读的是边框宽度
为什么需要:有时计算内部可用区域,需要知道元素左边框、上边框有多宽。怎么想:别被名字骗了,clientLeft / clientTop 不是元素位置。怎么做:
css
#book {
border-left: 10px dashed #999;
border-top: 2px solid #999;
}js
console.log(box.clientLeft); // 左边框宽度,示例中为 10
console.log(box.clientTop); // 上边框宽度,示例中为 2转写稿演示:没有边框时读到 0;给左边框设 10px 后 clientLeft 变成 10。易错点:它不表示“离左边多远”;无边框就是 0;右边框、下边框不在这两个属性里。
getBoundingClientRect:相对视口的位置
为什么需要:判断元素是否出现在用户眼前、做滚动加载时,必须知道元素相对“视口”的位置。怎么想:这个方法返回一个对象,里面既有尺寸也有位置;本节关注 left、top、x、y、right、bottom。left/top 是元素左上角相对视口左上角的坐标,x 同 left,y 同 top;right 是元素右边的 x 坐标,bottom 是元素底部的 y 坐标。怎么做:
js
const rect = box.getBoundingClientRect();
console.log(rect.left, rect.top);
console.log(rect.x, rect.y);
console.log(rect.right, rect.bottom);转写稿用画图演示:黑色当视口,红色/灰色当长页面。页面滚动后,元素在页面上的位置不变,但它在视口里的 top 会变小,甚至变成 0 或负数。易错点:right 不是“距离视口右边多远”,bottom 不是“距离视口底部多远”,它们仍是元素右边、底部的坐标;参照物永远是视口左上角,和祖先定不定位无关。
调试对比与易错点汇总
把父元素定位去掉,页面不滚动时,offsetLeft/offsetTop 和 getBoundingClientRect().left/top 可能看起来一样;一旦页面滚动,二者就分开了:前者仍按页面/定位祖先算,后者按视口算。易错点再强调:offsetLeft/Top 找第一个定位祖先,没有就参照页面;clientLeft/Top 是边框宽度;getBoundingClientRect() 是相对视口,且 right/bottom 是坐标不是距离。读位置属性是只读操作,别试图用它们直接改布局。
元素内容滚动的前提:先让元素“能滚”
为什么需要:页面整体能在视口里滚动,元素内部的内容也可能比元素本身高或宽,这时就要读取“内容在元素里滚了多远”,或者用代码控制它滚到某个位置。
怎么想:元素内部内容溢出后,默认 overflow 是 visible,内容会直接溢出显示,这时不能形成“内容在元素内部滚动”的场景。转写稿中先给外层元素设宽高、内边距、边框,再给 overflow: auto,才出现滚动条;换成 hidden 也能滚,只是看不到滚动条。
怎么做:给元素设置固定宽高,并设置 overflow 为 auto、hidden 等非 visible 的值。
易错点:overflow: visible 是默认值,内容溢出就露出来,不能滚;hidden 虽然看不见滚动条,但内容仍可被程序滚动。
读取元素内容滚动:scrollTop / scrollLeft
为什么需要:要知道内容当前滚到哪,才能做按钮控制、滚动监听、图片延迟加载、无缝滚动等效果。
怎么想:转写稿中用画图方式推导:元素像一个小视口,内容比它高;滚动条往下拖,内容往上走,scrollTop 从 0 开始变大;滚动条往右拖,内容往左走,scrollLeft 从 0 开始变大。两者最小值都是 0,到边界后浏览器会限制,不会无限增大。
怎么做:直接读取元素对象的这两个属性:
scrollLeft 内容在元素中向左滚动的距离
scrollTop 内容在元素中向上滚动的距离注意: 需要设置元素 overflow 的值不是 visible.
调试时可在元素的 scroll 事件里输出 box.scrollTop、box.scrollLeft,拖动滚动条观察数值变化。
易错点:方向别记反:scrollTop 变大表示内容向上滚;scrollLeft 变大表示内容向左滚。滚动条位置和内容移动方向是相反的。
写入元素内容滚动的位置:可读也可写
为什么需要:除了用户拖滚动条,程序也要能控制内容滚动,例如点按钮让内容左移、右移、上移、下移。
怎么想:既然 scrollTop、scrollLeft 可读可写,就可以在事件里修改它们。转写稿中用按钮演示:点左箭头让 box.scrollLeft += 20,点右箭头用 -=;上下同理改 scrollTop。
怎么做:先获取可滚动元素,再在按钮点击事件中修改:
js
box.scrollLeft += 20;
box.scrollTop -= 20;易错点:到边界后继续加或减不会生效,浏览器会自动限制;代码控制滚动也会触发 scroll 事件,不要以为只有用户拖滚动条才触发。
监听元素内容滚动:scroll 事件
为什么需要:内容一滚,往往要重新判断位置、加载内容或更新界面,所以需要监听滚动过程。
怎么想:转写稿中把 scroll 事件监听给外层可滚动元素,而不是只监听 window;元素内容滚动时事件会不停触发。
怎么做:给元素绑定 onscroll 或 addEventListener('scroll', ...),在回调里读取 scrollTop、scrollLeft。
易错点:监听对象要选对:元素内部滚动监听元素;整个页面滚动监听 window。不要把两者混在一起。
读写整个页面在视口中滚动的位置
为什么需要:整个页面也会在视口中滚动,需要知道页面滚了多少,或用代码控制页面滚动。
怎么想:转写稿强调 window 不是 DOM 元素,没有 scrollTop;页面在视口中滚动,要用根元素 document.documentElement 来读写。这是特殊用法,记住即可。
怎么做:读取和写入页面滚动位置:
js
document.documentElement.scrollLeft
document.documentElement.scrollTop页面滚动监听用 window.onscroll,按钮控制可用 document.documentElement.scrollTop += 20。
易错点:不要写 window.scrollTop;页面滚动时,元素在“页面上的位置”不变,但它在“视口上的位置”会变,这是转写稿中用画图反复推导的区别。
调试过程与易错点汇总
转写稿的调试顺序是:先让元素出现滚动条,再监听 scroll 输出 scrollTop/scrollLeft,观察拖动时从 0 增大;然后改用按钮写入属性,验证代码控制也能滚,并且到边界自动停;最后把同样思路映射到页面,用 document.documentElement.scrollTop 输出页面滚动距离。无缝滚动案例的思路也是让 scrollLeft 不停变大,但滚到头会停,需要自己思考如何接上,转写稿把它留作练习。核心易错点:overflow: visible 不能滚;scrollTop/scrollLeft 最小为 0;方向别反;元素滚动和页面滚动监听对象不同;页面滚动用根元素是特殊用法。
先说明:标题与转写稿不一致
本节笔记标题写的是“元素节点的添加/删除/替换/克隆”,但转写稿实际讲的是元素尺寸、元素位置、图片延迟加载、元素/页面滚动距离与无缝滚动,并未出现节点增删替换克隆的 API。为避免编造,下面按转写稿真实内容整理;转写稿中没有图片,因此不插入图片。
读取元素尺寸:计算样式与 client 系列
为什么需要:做布局、拖拽、懒加载时,经常要先知道元素或视口有多大。
怎么想:getComputedStyle 能读计算样式,但读到的宽高通常只是内容宽高;要总宽高还得加内边距、边框等。视口尺寸可用 window.innerWidth/innerHeight,也可用根元素 document.documentElement.clientWidth/clientHeight。
怎么做:
js
const box = document.querySelector('#box');
const cs = getComputedStyle(box);
console.log(cs.width, cs.height); // 常见为内容宽高,height 可能是 auto 计算值
console.log(window.innerWidth, window.innerHeight);
console.log(
document.documentElement.clientWidth,
document.documentElement.clientHeight
);转写稿演示:页面出现纵向滚动条后,两种视口宽度差约 17px,因为 window.innerWidth 包含滚动条本身,而 documentElement.clientWidth 不包含。
易错点:不要把计算样式宽高直接当总宽高;documentElement.clientWidth/clientHeight 获取视口属于特殊用法,记住即可;修改尺寸仍应写行内样式或改样式,不能靠读尺寸属性反向修改。
读取元素位置:offset、clientLeft/Top 与 getBoundingClientRect
为什么需要:定位、吸顶、懒加载都要判断“元素在谁坐标系里的哪里”。
怎么想:offsetLeft/offsetTop 参照第一个定位祖先元素;若没有定位祖先,则参照页面。clientLeft/clientTop 不是位置,而是左边框、上边框宽度。getBoundingClientRect() 的 left/top 是元素相对视口的坐标;right/bottom 是元素右边、底边的视口坐标,不是“距离视口右边/底边多少”。
怎么做:
js
const p = document.querySelector('#book');
console.log(p.offsetLeft, p.offsetTop);
console.log(p.clientLeft, p.clientTop); // 边框宽度
const rect = p.getBoundingClientRect();
console.log(rect.left, rect.top, rect.right, rect.bottom);
console.log(rect.x, rect.y); // x 等同 left,y 等同 top易错点:给父元素加 position: relative 后,offsetLeft/offsetTop 会改为相对该父元素;页面滚动后,getBoundingClientRect().top 会变化,而元素在页面文档中的位置不变。
图片延迟加载:用滚动事件判断进入视口
为什么需要:页面图片很多时,一次性下载全部图片会占用带宽、拖慢加载;用户没看到的图片可能根本不需要下载。
怎么想:图片先不写 src,把真实地址放在 data-src;监听页面滚动,判断图片是否进入视口。判断条件可用 getBoundingClientRect().top < 视口高度。
怎么做:
html
<img class="lazy" data-src="./img/01.jpg" alt="示例图一">
<img class="lazy" data-src="./img/02.jpg" alt="示例图二">js
const imgs = document.querySelectorAll('.lazy');
function loadImages() {
const viewH = document.documentElement.clientHeight;
imgs.forEach(img => {
if (img.getBoundingClientRect().top < viewH && !img.src) {
img.src = img.dataset.src;
}
});
}
loadImages(); // 首屏先加载一次
window.onscroll = loadImages;易错点:只在 scroll 里执行会漏掉首屏,所以要先调用一次;已加载图片不应反复处理,转写稿把它留作优化作业:加载过的不要再遍历或不要再设 src。
读写元素与页面的滚动距离
为什么需要:自定义滚动、返回顶部、轮播无缝滚动都要读或改滚动距离。
怎么想:元素内容滚动看 scrollTop/scrollLeft,它们可读可写;前提是元素 overflow 不能是默认的 visible,auto、hidden 等才可滚。页面滚动则用 document.documentElement.scrollTop/scrollLeft。
怎么做:
js
const box = document.querySelector('#box');
box.onscroll = function () {
console.log(box.scrollTop, box.scrollLeft);
};
box.scrollTop += 20; // 内容向上滚
box.scrollLeft += 20; // 内容向左滚
window.onscroll = function () {
console.log(document.documentElement.scrollTop);
};
document.documentElement.scrollTop += 20; // 页面往下滚易错点:scrollTop/scrollLeft 最小为 0,到边界后继续加也不生效,浏览器会自动限制;元素滚动必须设置非 visible 的 overflow。
无缝滚动的起点与作业
为什么需要:横向图片列表滚到头会停,视觉上不连续。
怎么想:定时器不断增大容器的 scrollLeft,滚到末尾前做处理,让用户感觉“又接上了”。
怎么做:
js
const box = document.querySelector('#box');
setInterval(() => {
box.scrollLeft += 1;
}, 10);转写稿只写到“到头会停”,并把“如何实现真正无缝”留作作业。合理方向是:判断是否到达临界值,再调整 scrollLeft 或复制内容;但具体实现转写稿未给完整代码,不能虚构为标准答案。
易错点:只加 scrollLeft 一定会停;不要死记代码形式,要分析“什么时候到达边界、什么时候重置或换内容”。
课堂画面






代码示例
常见错误
把
getComputedStyle().width当成元素总宽- 现象:读到的宽度只有 600,和实际占位对不上,算布局时总差边框、内边距。
- 原因:计算样式里的
width只是内容宽,不是盒子的总宽;offsetWidth才是总宽。 - 修法:要总宽高用
offsetWidth/offsetHeight;要内容+内边距用clientWidth/clientHeight;要内容实际宽高用scrollWidth/scrollHeight。先明确“要哪个尺寸”,再选 API。
用
offsetLeft/offsetTop当视口坐标- 现象:给父级加上
position: relative后,同一个元素的offsetLeft/offsetTop突然变了;页面滚动后它又不变。 - 原因:
offsetLeft/offsetTop参照的是第一个定位祖先,没有定位祖先才参照页面,不是视口。 - 修法:要视口坐标用
getBoundingClientRect().left/top;要页面坐标用offsetLeft/offsetTop,或用rect.left + 页面滚动距离换算。
- 现象:给父级加上
把
rect.right / rect.bottom当成“距右 / 距底的距离”- 现象:页面一滚动,
bottom变小,和“距离底部”的直觉相反。 - 原因:
right/bottom是元素右边、底边在视口中的坐标,不是到视口右/底边的距离。 - 修法:距右 =
innerWidth - rect.right,距底 =innerHeight - rect.bottom;坐标和距离要分清。
- 现象:页面一滚动,
滚动事件绑错对象,读到的
scrollTop一直是 0- 现象:元素内部滚动时监听
window,或页面滚动时读元素scrollTop,值始终为 0。 - 原因:滚动源和读取对象不匹配。
- 修法:元素内滚动绑元素、读
element.scrollTop/scrollLeft;整个页面滚动绑window,读document.documentElement.scrollTop/scrollLeft。
- 现象:元素内部滚动时监听
给
scrollTop赋值不生效- 现象:代码里写了
box.scrollTop = 100,页面没有任何滚动。 - 原因:元素
overflow仍是默认的visible,没有形成可滚动容器。 - 修法:给元素设置
overflow: auto / hidden / scroll(只要不是visible);同时注意scrollTop有边界,浏览器会自动限制,不会滚出内容区。
- 现象:代码里写了
图片延迟加载首屏空白,滚动一下才出现
- 现象:刷新页面后第一屏图片不加载,必须滚动才显示。
- 原因:加载判断只写在
scroll事件里,初始状态没有触发滚动事件。 - 修法:把“检测并加载”封装成函数,先主动执行一次处理首屏,再绑定
scroll。
延迟加载每次滚动都全量遍历图片
- 现象:图片多时滚动卡顿,已经加载过的图片仍被反复判断。
- 原因:没有区分“已加载”和“未加载”,每次
scroll都遍历整个集合。 - 修法:只遍历未加载的图片,加载后加标记或从待处理集合中移除;滚动处理里避免无意义重复计算。
无缝滚动只靠
scrollLeft一直加- 现象:滚到最右边就停住,再怎么加也不动。
- 原因:
scrollLeft有最大值,浏览器会自动限制。 - 修法:在到达边界前做重置、复制内容或改用位移方案,核心是处理“边界”而不是无限递增。
练习
准备一个带边框、内边距、固定宽高且内容溢出的元素,分别读取
offsetWidth/offsetHeight、clientWidth/clientHeight、scrollWidth/scrollHeight,写出三组值的差值来源;再修改overflow观察哪些值发生变化。页面上放一个普通元素,分别记录页面未滚动和滚动后的
offsetTop与getBoundingClientRect().top,解释两者变化差异;再给其父级加position: relative,观察offsetTop的变化并说明原因。实现一个元素内滚动控制:两个按钮分别让内容向上、向下滚动固定距离,要求滚到顶部/底部时不报错、不越界;用
scrollTop完成读写,并说明为什么赋值到边界后继续加没有效果。实现图片延迟加载:首屏图片立即加载,页面滚动时只加载进入视口的图片;要求已加载图片不被重复处理,并说明你如何标记或过滤已加载项。
实现横向无缝滚动:图片排成一行,用定时器让
scrollLeft增加,到达右边界后仍要“看起来无限滚动”。写出你的边界处理方案,并说明为什么不能只让scrollLeft无限递增。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 读取元素尺寸02 | 10:42 |
| 2 | 读取元素位置 | 23:07 |
| 3 | 图片延迟加载案例 | 24:44 |
| 4 | 读写元素中内容滚动的距离 | 18:18 |
| 5 | 读写页面滚动的距离 | 14:21 |
| 6 | 无缝滚动案例 | 07:38 |
小结
尺寸读取先分清三套值:
offset是总盒,client是内容+内边距,scroll是内容实际占位;选错 API 比算错公式更常见。位置有两套坐标系:
offsetLeft/offsetTop是定位祖先或页面坐标,getBoundingClientRect()是视口坐标;页面滚动只影响后者。right/bottom是坐标,不是距离;需要“距右/距底”时必须自己用视口尺寸换算。滚动读写要成对:元素内滚动绑元素读元素,页面滚动绑
window读documentElement;scrollTop/scrollLeft可读可写,但有边界。元素可滚动的前提是
overflow不是visible;页面滚动不要依赖body.scrollTop。延迟加载的核心是“进入视口再赋
src”,首屏要主动执行一次检测,滚动过程中要避免全量重复遍历。无缝滚动的难点不在“让值变大”,而在边界处理;浏览器会自动限制
scrollLeft/scrollTop,不能假设可以无限递增。尺寸、位置、视口尺寸基本都是只读的;代码不能修改视口大小,能读能写的只有滚动位置这类状态。