彻底搞懂 offsetX、scrollX、clientX 的区别

无论在 iOS 还是前端开发中,关于如何定位一个元素是必须要掌握的知识,而在前端中,元素定位比较难理解,我们今天一起学习下。

在 DOM 设计中,主要通过这些 API 来确定某个元素的具体位置。

offsetTop, offsetLeft, offsetWidth, offsetHeight,

scrollTop, scrollLeft, scrollWidth, scrollHeight,

clientTop, clientLeft, clientWidth, clientHeight,

为什么会有这么多属性来定位一个元素?

我觉得和盒子模型有很大关系,一个盒子就是一个区域,包含 margin、border、padding 和 content。上面 6 个属性可以非常容易定位元素。

有没有类似 iOS 中 frame 的概念?

答案是有的。可以通过 Element.getBoundingClientRect() 来确定元素在可视区域中的位置。

这些属性究竟有什么不同,我们一一分析:

1、offsetX —— 相对谁偏移了多少?

offsetX 是 HTMLElement 的只读属性,不可以修改。offset 有“偏移” 的意思,你可能会把这个属性想成与滚动有关,其实与滚动没半毛钱关系。我们先看 offsetTop 这个属性,它表示当前元素顶部到 offsetParent 节点的距离。这里关键点是 offsetParent,它是指当前元素最近的使用 position 不为 static 的祖先节点,如果没有使用 position 的祖先节点,它的值将是 body 节点(这个值有可能也是 null,如果出现这些极端情况可以查 MDN)。其实关键点是「相对谁偏移了多少的问题」。一图胜千言。

上图中,由于小人的父元素 div 是 positioned,故它的 offsetParent 为 div,而不是 body,offsetTop 是指小人顶部距离 div 距离(图中红线部分)。

同理 offsetLeft 是距离左边的距离,只要明白了 top,left 属性也就明白了。

offsetWidth 和 offsetHeight 指宽高,包含 boder 、 padding 和 content。

总之,offset 相关的属性指元素相对某个元素的位置,与滚动没有半毛钱关系。

2. scrollX —— 滚动了多少?

与 scroll 相关的属性是 Element 的属性,offsetTop、offsetLeft 可读可写,offsetWidth、offsetHeight 只读。

scroll 才真正与滚动相关,我们以竖直方向滚动为例。

只有元素可以滚动 scrollTop 才会有值,否则为 0,也就是说当子元素的高度大于父元素时,设置父元素的 overflow 为 scroll 才会生效。

当 子div 的高度超出 父div 时,父div 需要通过把 overflow 设置为 scroll,这时候 子div 才可以滚动。这里有一点需要注意,图中所标记的 scrollTop 的属性是 子div 的还是 父div 的?欢迎留言说出你的答案。

scrollTop 的值可以修改,表示在滚动区域中,竖直方向滚动了多少。通常通过修改这个值来实现让某个元素滚动到指定位置。

关于 scrollHeight 这个也需要特别留意,父 div 的 scrollHeight 是通过子div 计算的,表示可滚动的高度。

scrollWidth 与 scrollHeight 类似。我特意写了一个 demo,想要搞懂这个必须通过 demo 实践。

3. clientX  —— 我自身的位置

它是 Element 的属性。client 相关的属性描述的是自身的位置,它没有相对元素。

clientTop 就是 border-top 的值,clientLeft 就是 border-left 的值。而 clientWidth 和 clientHeight 都不包含 border。

4. Element.getBoundingClientRect() —— 相对可视区域的位置

通过这个 API 获取元素相对可视区域的位置,返回值是浮点数。其中,大多数情况下 x 与 left 相等,y 与 top 相等,只有当 width 或 height 为负数的时候会有区别。left = x + with,top = y + height。图中黄色区域为边框。

注意观察 bottom 和 right 的值。

5. 少不了 demo

我写了一个输出这些属性的方法,方便同时查看这些属性的值:

var logBySelector = function (id) {let elm = document.querySelector(id);logFrame(elm);elm.onscroll = function () {logFrame(elm);if (elm.scrollHeight - elm.scrollTop === elm.clientHeight) {console.log('到底了');}else if (elm.scrollTop === 0) {console.log('到顶了');}}
};var logFrame = function (elm) {console.log(elm, '-------------------------');console.log(elm.getBoundingClientRect());for (key in elm) {let whiteList = ['offsetTop', 'offsetLeft', 'offsetWidth', 'offsetHeight','scrollTop', 'scrollLeft', 'scrollWidth', 'scrollHeight','clientTop', 'clientLeft', 'clientWidth', 'clientHeight','offsetParent'];if (whiteList.indexOf(key) !== -1) {console.log(key, ' = ', elm[key]);}}
}

demo 地址:https://github.com/lefex/FE/tree/master/%E7%AC%AC%E4%BA%94%E9%98%B6%E6%AE%B5

本文讲解了元素相关的位置属性,这几个属性容易弄混,我特意为大家准备了 demo。这些知识与前面讲的 CSS 布局 坚持14天学懂CSS布局(领电子书),盒子模型 第10天:撑起CSS布局的半壁江山—盒子模型 有很大关系。当然我们还需要一节内容来实践一下。大家加油。


推荐阅读:

我是一颗树 · DOM

回到工位“我”悟出了 DOM 设计的精华

打通 DOM 的设计架构

两种方法轻松找到 DOM 元素

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注