上次处理一个问题的时候,遇到了要处理元素的滚动位置,由于基础不扎实,头都搞大了。所以特意来总结下关于DOM元素的大小的的知识点。Dom中没有规定如何确定页面中元素的大小。IE为此率先引入了一些属性,以便开发人员使用。目前所有的浏览器都支持这些属性。
1. 偏移量(offset dimension)
偏移量用于描述元素在屏幕上占用的所有可见空间。元素的可见大小由宽度、高度决定,包括所有内边距、滚动条、边框大小(注意不包含外边距)。通过下列4个属性可以取得元素的偏移量。
offsetHeight
: 元素在垂直方向上占用的空间的大小,以像素为单位。包含元素的高度(内容的高度 +padding-top
+padding-bottom
)、(可见的)水平滚动条的高度、上边框高度和下边框高度。offsetWidth
: 元素在水平方向上占用的空间的大小,以像素为单位。包含元素的宽度(内容的宽度 +padding-left
+padding-right
)、(可见的)垂直滚动条的宽度、左边框的宽度和右边框宽度。offsetLeft
: 元素的左外边框到包含元素(最近的定位父元素)的左内边距的距离,单位为像素。offsetTop
: 元素的上边框到包含元素(最近的定位父元素)的上内边距之间的距离,单位为像素。
其中,offsetLeft
和 offsetTop
属性与包含元素有关系,包含元素的引用保存在 offsetParent
属性中。offsetParent
属性不一定与 parentNode
的值相等。可以用下面一张图来展示上面几个属性表示的不同大小。
如果想要知道某个元素在页面上的偏移量,将这个元素的 offsetLeft
和 offsetTop
与其 offsetParent
的相同属性相加,如此循环直到根元素,就可以得到一个基本准确的值。以下两个函数可以用于分别取得元素的左和上的偏移量:
function getElementLeft(element) {
var actualLeft = element.offsetLeft;
var current = element.offsetParent;
while (current !== null) {
actualLeft += current.offsetLeft;
current = current.offsetParent;
}
return actualLeft;
}
function getElementTop(element) {
var actualTop = element.offsetTop;
var current = element.offsetParent;
while (current !== null) {
actualTop += current.offsetTop;
current = current.offsetParent;
}
return actualTop;
}
这两个函数利用 offsetParent
属性在DOM层次中逐级向上回溯,将每个层次中的偏移量属性合计到一块。对于简单的CSS布局的页面,这两个函数可以得到非常精确的结果。对于使用表格和内嵌框架布局的页面,由于浏览器实现这些元素的方式不同,应此得到的值也就不太精确了。一般来说,页面中的所有元素都会被包含在几个 <div>
元素中,而这些 <div>
元素的 offsetParent
又是 <body>
元素,所以 getElementLeft()
与 getElementTop()
会返回与 offsetLeft
和 offsetTop
相同的值。
所有这些偏移量属性都是只读的,而且每次访问他们都需要重新计算,因此,应该尽量避免重复访问这些属性;如果需要重复使用其中某些属性,可以将它们保存在全局变量中,以提高性能。
2. 客户区大小(client dimension)
元素的客户区大小,指的是元素内容及其内边距占据的空间大小。有关各户区域的大小的属性有两个: clientWidth
和 clientHeight
。其中,clientWidth
属性是元素内容区域宽度加上左右内边距宽度;clientHeight
属性是元素内容区高度加上上下内边距高度。下图可以形象的说明这些属性表示的大小。
从字面上看,客户区大小就是元素内部的空间大小,因此滚动条占据的空间不计算在内。一个典型的应用就是取得浏览器的可视窗口的大小,可以使用 ducument.documentElement
或 ducument.body
(在IE7之前的版本中)的 clientWidth
和 clientHeight
;
function getViewport() {
// 首先检查 document.compatMode 属性。以确定浏览器是否运行在混杂模式下。
// Safari 3.1 之前的版本不支持这个属性。Chrome、Opera 和 Firefox 大多数情况下都是运行在标准模式下面的。
if (document.compatMode == "BackCompat") {
return {
width: document.body.clientWidth,
height: document.body.clientHeight
};
} else {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
}
}
与偏移量相似,客户区大小也是只读的,也是每次访问的时候都要重新计算的。
3. 滚动大小(scroll dimension)
滚动大小,指的是包含滚动内容的元素的大小。有些元素(例如 <html>
元素),即使没有执行任何代码也能自动的添加滚动条。但是另外一些元素。则需要通过CSS的 overfow
属性进行设置才能滚动。以下是4个与滚动大小相关的属性:
scrollHeight
: 在没有滚动条的情况下,元素内容的总高度。scrollWidth
: 在没有滚动条的情况下,元素内容的总宽度。scrollLeft
: 被隐藏在内容区域左侧的像素数。通过这个属性可以改变元素的滚动位置。scrollTop
: 被隐藏在内容区域上方的像素数。通过这个属性可以改变元素的滚动位置。
下图展示了这些属性代表的大小。
scrollWidth
和 scrollHeight
主要用于确定元素的实际大小。例如,通常认为 <html>
元素在Web浏览器窗口中滚动的元素(IE6 之前版本运行在混杂模式下时候是 <body>
元素)。因此,带有垂直滚动条的页面总高度就是 document.documentElement.scrollHeight
。
对于不包含滚动条的页面而言,scrollWidth
和 scrollHeight
与 clientWidth
和 clientHeight
之间的关系并不十分清晰。这种情况下,基于 document.documentElement
查看这些属性会在不同浏览器发现一下不一致的问题,如下所示:
Safari 3.1 之前的
scrollWidth
等于clientWidth
,scrollHeight
也等于clientHeight
。这组大小都等于浏览器视口大小。Firefox 中这两组属性始终是相同的,但是大小代表的是文档区域的实际尺寸,而非视口的尺寸。
Opera、Safari 3.1 及更高版本、Chrome 中的这两组属性是有差别的, 其中
scrollWidth
和scrollHeight
等于视口的大小,而clientWidth
和clientHeight
等于文档内容区域的大小。IE (标准模式) 中的这两组属性不相同,其中
scrollWidth
和scrollHeight
等于文档内容区域的大小。而clientWidth
和clientHeight
等于视口大小。
在确定文的那个的高度的时候(包括基于视口的最小高度时),必须取得 scrollWidth/clientWidth
和 scrollHeight/clientHeight
中的最大值,才能保证在跨浏览器的环境下得到精确的结果。下面就是这样的一个例子:
var docHeight = Math.max(document.documentElement.scrollHeight,
document.documentElement.clientHeight);
var docWidth = Math.max(document.documentElement.scrollWidth,document.documentElement.clientWidth);
通过 scrollLeft 和 scrollTop 属性可以确定元素当前滚动的状态,也可以设置元素的滚动位置。当元素尚未被滚动的时候,这两个属性的值都等于0。
4. 确定元素大小
IE、Firefox 3 及更高版本和 Opera 9.5 及更高版本 都为每个元素提供了一个 getBoundingClientRect()
方法。这个方法返回一个矩形对象,包含4个属性: left
、top
、right
和 bottom
。这些属性给出了元素在页面中相对于视口的位置。但是,浏览器的实现稍有不同。IE 认为文档的做上角坐标是 (2,2)
,而Firefox 和 Opera则将传统的 (0,0)
作为起点坐标。因此就需要在一开始检查一下坐标(0,0)处的元素位置,在IE中,会返回(2,2)
。在其他浏览器中会返回(0,0)
。来看看下面的函数:
function getBoundingClientRect(element){
if (typeof arguments.callee.offset != 'number'){
var scrollTop = document.documentElement.scrollTop;
var temp = document.createElement('div');
temp.style.cssText = "position:absolute;left:0;top:0;";
document.body.appendChild(temp);
arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
document.body.removeChild(temp);
temp = null;
}
var rect = element.getBoundingClientRect();
var offset = arguments.callee.offset;
return {
right: rect.right + offset,
left: rect.left + offset,
top: rect.top + offset,
bottom: rect.bottom + offset
};
}
对于不支持 getBoundingClientRect()
的浏览器,可以通过其他的手段获取相同的信息。一般来说,right
和 left
的差值与 offsetWidth
相等,而 bottom
和 top
的差值与 offsetHeight
相等。而且 left
和 top
属性大致等于前面的 getElmentLeft()
和 getElementTop()
函数取得的值。综上可以创建出一个跨浏览器的函数:
function getBoundingClientRect(element) {
var scrollTop = document.documentElement.scrollTop;
var scrollLeft = document.documentElement.scrollLeft;
if (element.getBoundingClientRect) {
if (typeof arguments.callee.offset != "number") {
var temp = document.createElement("div");
temp.style.cssText = "position:absolute;left:0;top:0;";
document.body.appendChild(temp);
arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
document.body.removeChild(temp);
temp = null;
}
var rect = element.getBoundingClientRect();
var offset = arguments.callee.offset;
return {
left: rect.left + offset,
right: rect.right + offset,
top: rect.top + offset,
bottom: rect.bottom + offset
};
} else {
var actualLeft = getElementLeft(element);
var actualTop = getElementTop(element);
return {
left: actualLeft - scrollLeft,
right: actualLeft + element.offsetWidth - scrollLeft,
top: actualTop - scrollTop,
bottom: actualTop + element.offsetHeight - scrollTop
}
}
}
在某些情况下,这个函数放回的值可能会有所不同,比如使用表格布局或者使用滚动元素的情况下。
本文章参考了 《JavaScript高级程序设计(第二版)》 第十一章中的内容。