在很多场景下我们需要通过JavaScript来获取视口或DOM元素的大小、位置以及滚动高度。最常见的一个效果,导航吸顶的一个效果。那么今天就来学习这方面相关的知识。
window 和 document
在开始了解视口宽高、位置和滚动高度相关的知识之前,先简单的来了解window和document。在学习新的API之前,我都喜欢在调式工具中将对应的API打印出来。比如:
![1e661c44d8fc2449d45df9b88329fc99.png](https://i-blog.csdnimg.cn/blog_migrate/b7e026a95d57e8ac3d545b36b2b2f3c5.jpeg)
window对象表示一个包含DOM文档的窗口,其document属性指向窗口中截入的DOM文档。window对象实现了Window接口。一些额外的全局函数、命名空间、对象、接口和构造函数与window没有典型的关联,但却是有效的,它们在JavaScript参考和DOM参考中列出。
再把document打印出来:
![0ee36c60d14cd565b8fa3c3e014fc84b.png](https://i-blog.csdnimg.cn/blog_migrate/26fa73a320be1dc3f831f678682f8c70.jpeg)
Document接口提供了一些在浏览器服务中作为页面内容入口点而加载的一些页面,也就是DOM树。DOM树包括诸如<body>、<head>以及其他元素。其也为document提供了全局性的函数,例如获取页面的URL,在文档中创建新的元素的函数。
两者之间的区别:
Window对象表示浏览器中打开的窗口;window对象可以省略。比如alert()、window.alert()
Document对象是Window对象的一部分。那么document.body就可以写成window.document.body。浏览器的HTML文档成为Document对象
视口宽高
这里的视口指的是浏览器窗口。在JavaScript中,可以通过window.innerHeight和window.outerHeight获取整个窗口的高度,window.innerWidth和window.outerWidth获取整个窗口的宽度。
![71085116d178f2fb6e7afbe2e134a340.png](https://i-blog.csdnimg.cn/blog_migrate/b8bd73d3b97bb02495393ccbbd03b6bd.png)
上图展示的是浏览器视口的高度的。
![7e56e2a12b2e0e513b9923fcbe33c47f.png](https://i-blog.csdnimg.cn/blog_migrate/3c82bd275b0bd79b3408c446798cd58c.png)
看一个实际页面:
![c444f7fe96e0ee89599c21c2fdae252f.png](https://i-blog.csdnimg.cn/blog_migrate/4d7f4fc07746303fbeecc1435da3c4b9.jpeg)
注意:IE8及以下版本不支持window.innerHeight和window.innerWidth等属性。
对于不支持window.innerHeight等属性的浏览器中,可以读取documentElement和body的高度。它们的大小和window.innerHeight是一样的。事实上也略有不同。
document.documentElement.clientHeight
document.body.clientHeight
其中documentElement是文档根元素,就是<html>标签;body就是<body>元素:
![1ee9521a25b93438622b75a73e393406.png](https://i-blog.csdnimg.cn/blog_migrate/439fad4abaf3d50d013458eca1f21661.jpeg)
而document.documentElement.clientHeight和document.body.clientHeight区别在于:
document.documentElement.clientHeight:不包括整个文档的滚动条,但包括<html>元素的边框
document.body.clientHeight:不包括整个文档的滚动条,也不包括<html>元素的边框,也不包括<body>的边框和滚动条
![60f09da739b3bffcf947337677796420.png](https://i-blog.csdnimg.cn/blog_migrate/0350a9ca695b606c90c11b49d0bac89e.png)
挂靠在window下的宽高还有window.screen,window.screen包含有关于用户屏幕的信息。它包括:
window.screen.width:显示器屏幕的宽度
window.screen.height:显示器屏幕的高度
window.screen.availHeight:浏览器窗口在屏幕上可占用的垂直空间,即最大高度
window.screen.availWidth:返回浏览器窗口可占用的水平宽度
window.screenTop:浏览器窗口在屏幕上的可占用空间上边距离屏幕上边界的距离
window.screenLeft:返回浏览器可用空间左边距离屏幕(系统桌面)左边界的距离
![7794545b8b7273122e85cbc4a361ab8a.png](https://i-blog.csdnimg.cn/blog_migrate/dccfde889ece18307187939ef64d07f3.png)
除此之外,还有偏移量的控制:
offsetHeight:元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数。通常,元素的offsetHeight是一种元素CSS高度的衡量标准,包括元素的边框、内边距和元素的水平滚动条(如果存在且渲染的话),不包含:before或:after等伪类元素的高度。对于文档的body对象,它包括代替元素的CSS高度线性总含量高。浮动元素的向下延伸内容高度是被忽略的。
offsetWidth:一个元素的布局宽度。offsetWidth是测量包含元素的边框、水平线上的内边距、竖直方向滚动条以及CSS设置的宽度的值。
offsetLeft:当前元素左上角相对于offsetParent 节点的左边界偏移的像素值。对块级元素来说,offsetTop、offsetLeft、offsetWidth 及 offsetHeight 描述了元素相对于 offsetParent 的边界框。然而,对于可被截断到下一行的行内元素(如 span),offsetTop 和 offsetLeft 描述的是第一个边界框的位置(使用 Element.getClientRects() 来获取其宽度和高度),而 offsetWidth 和 offsetHeight 描述的是边界框的尺寸(使用 Element.getBoundingClientRect 来获取其位置)。因此,使用 offsetLeft、offsetTop、offsetWidth、offsetHeight 来对应 left、top、width 和 height 的一个盒子将不会是文本容器 span 的盒子边界。
offsetTop:当前元素相对于其 offsetParent 元素的顶部的距离。
offsetParent:返回一个指向最近的(closest,指包含层级上的最近)包含该元素的定位元素。如果没有定位的元素,则 offsetParent 为最近的 table元素对象或根元素(标准模式下为 html;怪异模式下为 body)。当元素的 style.display设置为 none 或定位为fixed时,offsetParent 返回 null。
结合上面的,我们用一张图来阐述,更易帮我们理解:
![18c881c270bb8aee5ff1bc8948bac4cb.png](https://i-blog.csdnimg.cn/blog_migrate/76ba870cbbb966fc2c78903d1255912f.png)
简单的小结一下
那么我们常用位置和大小的计算,可以这样处理。都是基于浏览器的标准模式之下。
浏览器可视区宽高
// 不包含滚动条
// width
document.documentElement.clientWidth
// height
document.documentElement.clientHeight
// 包含滚动条(ie9+, 不是css规范)
// width
window.innerWidth
// height
window.innerHeight
其最佳的方式是:
let height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
let width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
其实使用offsetHeight作为Fallback要比clientHeight更好。
元素距离文档顶部距离
当offsetParent为body时,可以通过el.offsetTop确定元素距离文档顶部大小。当offsetParent不为body时,就需要一层层循环至offsetParent为null。
function getTop(el) {
let top = el.offsetTop;
let currentParent = el.offsetParent;
while (currentParent != null) {
top += currentParent.offsetTop;
currentParent = currentParent.offsetParent;
}
return top;
}
元素距离文档左侧距离
元素距离文档左侧距离实现思路和上面的元素距离文档顶部距离的类似。当offsetParent为body时,可以通过el.offsetLeft确定元素距离文档顶部大小。当offsetParent不为body时,就需要一层层循环至offsetParent为null。
function getLeft(el) {
let left = el.offsetLeft;
let currentParent = el.offsetParent;
while (currentParent != null) {
left += currentParent.offsetLeft;
currentParent = currentParent.offsetParent;
}
return left
}
滚动高度
与滚动scroll相关的方法主要有window对象下的scrollX、scrollY、scrollTo和scroll;Element对象下的scrollWidth、scrollHeight、scrollLeft和scrollTop。
属性名称 描述 备注
![a7c8aeeceebe8931410fd2a886e045c0.png](https://i-blog.csdnimg.cn/blog_migrate/454fec503939428e73a42fa4a04e400f.jpeg)
window.scrollX和window.scrollY两个属性在IE9以下的版本都未支持。如果我们要获取页面垂直和水平的滚动距离,我们一般这样来处理:
// 判断是否支持pageXOffset
let supportPageOffset = window.pageXOffset !== undefined
// 判断渲染模式是不是标准模式
let isCSS1Compat = ((document.compatMode || '') === 'CSS1Compat')
/**
* 如果支持pageXOffset,直接用window.pageXOffset。如果不支持,判断渲染模式
* 如果是标准模式,用document.documentElement.scrollLeft
* 如果是混合模式,用document.body.scrollLeft
**/
let x = supportPageOffset ? window.pageXOffset : isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft
let y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop
![a5b05200b7cdc285082c7eaba55c6622.png](https://i-blog.csdnimg.cn/blog_migrate/ac8072c757cf90e078406bf70f632d36.jpeg)
而window.scrollTo()不需要做兼容处理,可以直接使用,另外与window.scroll()相同。window.scroll()有两个参数:
- x-coord:值表示你想要置于左上角的像素点的横坐标
- y-coord:值表示你想要置于左上角的像素点的纵坐标
![a42ec19a8a92ae3f23600fcda4129d9b.png](https://i-blog.csdnimg.cn/blog_migrate/7498f2f5ce41379a57050ecde232295d.jpeg)
scrollWidth返回该元素区域宽度和自身宽度中较大的一个,若自身宽度大于内容宽度(存在滚动条),那么scrollWidth将大于clientWidth。需要注意的是,改属性返回的是四舍五入后的整数值,如果需要小数,则需要使用getBoundingClientRect()。
scrollHeight返回该元素内容高度。包括被overflow隐藏掉的部分,包含padding,但不包含margin。和scrollWidth类似,如果需要小数,则需要使用getBoundingClientRect()。
这两个属性最常见的使用场景就是:判断元素是否滚动到底部,比如下面的代码,如果返回的值为true,表示滚动到底部,反之则不是:
ele.scrollHeight - ele.scrollTop === ele.clientHeight。
特别是在移动端,经常会有下拉列表无限加载的需求。我们来看@Quickeryi提供的一个示例:
![488c80713a52070de841d20ff476d708.png](https://i-blog.csdnimg.cn/blog_migrate/45da9bb6ed84436e5cf3bed4f4b67560.jpeg)
/**
* @param warp {DOM || null} 外层容器,当为null时,默认以整个文档结构为外容器
* @param threshold {Number} 滚动阀值,即可以设置一个值,当滚动到离地步还有一段距离时,就开始执行callback
* @param cb {Function} 回掉函数
*/
let scrollToLoad = (warp, threshold, cb) => {
let scrollTop = 0,
warpHeight,
listHeight,
_threshold_ = threshold || 0;
if (!warp) {
// 获取滚动条当前的位置
if (document.documentElement && document.documentElement.scrollTop) {
scrollTop = document.documentElement.scrollTop;
} else if (document.body) {
scrollTop = document.body.scrollTop;
}
// 获取当前可视范围的高度
if (document.body.clientHeight && document.documentElement.clientHeight) {
warpHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight);
} else {
warpHeight = Math.max(document.body.clientHeight, document.documentElement.clientHeight);
}
// 获取list完整的高度
listHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
} else {
scrollTop = warp.scrollTop;
warpHeight = warp.clientHeight;
listHeight = warp.scrollHeight;
}
if (listHeight <= warpHeight + scrollTop - _threshold_) {
cb && cb();
}
}
scrollLeft代表元素滚动条距离元素左边的多少像素,其值可以是任意整数,然而:
如果元素不能滚动,比如没有内容溢出,那么scrollLeft的值是0
如果给scrollLeft设置的值小于0,那么其值将变为0
如果给scrollLeft设置的值大于元素内容最大宽度,那么其值将被设置为元素最大宽度
scrollTop和scrollLeft类似,只是方向不一样:
如果一个元素不能被滚动,内容未溢出, scrollTop将被设置为0
设置scrollTop的值小于0,其值将被设为0
如果设置了超出这个容器可滚动的值, 其值将被设为最大值
比如我们要获取或设置页面垂直方向的滚动距离,我们就可以这样操作:
//获取滚轮滚动的距离,适配所有的浏览器
function getScrollY(){
return window.pageYOffset || document.documentElement.scrollTop;
}
//设置垂直方向滚轮滚动的距离,适配所有的浏览器,num为滚动距离
function setScrollY(num){
document.body.scrollTop = document.documentElement.scrollTop = num;
}
水平方向的同理,只需要将window.pageYOffset更换成widnow.pageXOffset,document.documentElement.scrollTop更换成document.documentElement.scrollLeft。
上面的两个小示例中,我们总是把window下的scrollY(pageYoffset)、scrollX(pageXoffset)方法和element下的scrollTop、scrollLeft方法混在一起用,其实这两个是有本质区别的。一个获取的是window窗口的滚动距离,一个获取的是某一个元素的滚动距离,当获取的元素是body时,window.scrollY(window.pageYoffset) = document.body.scrollTop。
如果我们需要获取各种浏览器可见窗口大小的话,我们可以这样做:
function getWindowSizeInfo () {
let size = `网页可见区域宽度clientWidth: ${document.body.clientWidth},
网页可见区域高度clientHeight: ${document.body.clientHeight},
网页可见区域宽度offsetWidth: ${document.body.offsetWidth} (包括边线和滚动条宽度),
网页可见区域高度offsetHeight: ${document.body.offsetHeight} (包括边线的宽度),
网页正文全文宽度scrollWidth: ${document.body.scrollWidth},
网页正文全文高度scrollHeight: ${document.body.scrollHeight},
网页内容被卷去的高度scrollTop: ${document.body.scrollTop} (Firefox浏览器),
网页内容被卷去的高度scrollTop: ${document.documentElement.scrollTop} (IE浏览器),
网页内容被卷去的宽度scrollLeft: ${document.body.scrollLeft},
网页内容正文部分上screenTop: ${window.screenTop},
网页内容正文部分左screenLeft: ${window.screenLeft},
屏幕分辨率的高度height: ${window.screen.height},
屏幕分辨率的宽度width: ${window.screen.width},
屏幕可用区域高度availHeight: ${window.screen.availHeight},
屏幕可用区域宽度availWidth: ${window.screen.availWidth}`
return size
}
![55e1b2b3915c0f4baea66a12b928389b.png](https://i-blog.csdnimg.cn/blog_migrate/74d7fd02a627a4597e277c264f5213a0.jpeg)
另外如果我们需要获取网页客户区的宽度、滚动条宽度、滚动条距离左边和顶部的距离,我们可以这样做:
function getClientAndScrollInfo() {
let clientWidth = clientHeight = scrollHeight = scrollWidth = scrollLeft = scrollTop = 0;
if (document.compatMode == 'BackCompat') {
clientWidth = document.body.clientWidth;
clientHeight = document.body.clientHeight;
scrollWidth = document.body.scrollWidth;
scrollHeight = document.body.scrollHeight;
scrollTop = document.body.scrollTop;
scrollLeft = document.body.scrollLeft;
} else {
clientWidth = document.documentElement.clientWidth;
clientHeight = document.documentElement.clientHeight;
scrollWidth = document.documentElement.scrollWidth;
scrollHeight = document.documentElement.scrollHeight;
scrollTop = document.documentElement.scrollTop;
scrollLeft = document.documentElement.scrollLeft;
}
return info = `
clientWidth: ${clientWidth}px,
clientHeight: ${clientHeight}px,
scrollWidth: ${scrollWidth}px,
scrollHeight: ${scrollHeight}px,
scrollTop: ${scrollTop}px,
scrollLeft: ${scrollLeft}px
`
}
![5fccfe149bd8f76a2a259d89286b8530.png](https://i-blog.csdnimg.cn/blog_migrate/16b7a5b5e1f99ad6874371bd8521ca5c.png)
总结
由于使用JavaScript检测视窗或元素有六个DOM的尺寸属性:offsetWidth、offsetHeight、clientWidth、clientHeight、scrollWidth、scrollHeight。再加上offsetTop、offsetLeft、scrollTop、scrollLeft、clientTop和clientLeft等方向距离的属性。这样一来,让事情就变得复杂,对于像我这样的初学者而言,极难理解,也易产生一些错误。
由于内容过多,最后简单的总结一下下。首先上一张图:
![eff07597536ea108a6bfe1c3dbbb93ec.png](https://i-blog.csdnimg.cn/blog_migrate/9315fe30c89a27c192065e920cf2ea2d.jpeg)
一图胜过千言万语。熟悉CSS的同学应该知道,元素的盒模型分为content-box和border-box之类。那么在JavaScript中,上述的这些属性也略有不同。
content-box时情况
offsetWidth和offsetHeight:
元素盒子总宽高:width + padding + border
在box-sizing:content-box时,width= 内容区域的宽度
不管是否超出元素限制范围(内容有溢出容器)都是总宽高
clientWidth和clientHeight:
一般情况下,即元素盒子可见区的 width + padding
可视区只针对取值的元素本身,以元素本身的角度出发,也就是说当我们限制元素宽度时只有能看见的部分会列入计算,减去滚动条的宽度
如果有个子元素超过自己的宽高,那么clientWidth和clientHeight仍然是width + padding
scrollWidth和scrollHeight:
整个盒子内的总宽高
元素本身的padding加上内部元素的宽高
offsetTop和offsetLeft:
定义ele.offsetTop是可读属性,返回改元素与offsetParent元素的距离
当position为static时,offsetParent就会是根节点root或者外层结构中最接近的table cell元素,其他有position的属性(relative、absolute、fixed)都会让被设定的外层元素变成offsetParent
计算元素和offsetParent的距离(改元素本身的margin加上offsetParent的padding)
当元素CSS有display: none时,offsetParent的值为null
clientTop和clientLeft:
单纯就是border宽度
定义为返回该方向的border宽度
该属性不包含元素的padding或margin
使用类似document.getElementById('ele').style.borderTopWidth的方法取得一样的值
scrollTop和scrollLeft:
如果该目标元素没有滚动条,则值为0
从元素border内开始计算,scrollTop与scrollLeft是取有内容卷起的那个元素卷到哪
按照规定scrollTop不会小于0,但是在OSX系统下的Chrome和Safari浏览器下有可能会产生负值
border-box时情况
offsetWidth和offsetHeight:
由于border-box的关系,CSS设定的width会等于总宽
在border-box模式下width不等于内容区域的宽度,而是整个块区域的宽度,但不包含margin
clientWidth和clientHeight:
border-box状态时,算法变成:width - border = 内容区域 + padding
scrollWidth 和 scrollHeight:
取得值没有差异,虽然是往内减,但该有的padding还是存在
offsetTop和offsetLeft:
取得值没有差异
clientTop和clientLeft:
取得值没有差异
scrollTop和scrollLeft:
取得值没有差异
简而言之,上述的内容就是JavaScript中的三大系列offset、client和scroll。而这三大系列都是以DOM元素节点的属性形式存在的。类比访问关系,也是以属性形式存在。不同点在于,访问关系是为了获取其他节点,而三大系列是为了获取元素节点更多的信息。最后以网上最经典的一张图来展示这三大系列之间的关系:
![469df387d8dd7f0d59e6233af877df85.png](https://i-blog.csdnimg.cn/blog_migrate/99c2104fe80beb7b1b1c649cf27c9d9c.jpeg)
原文链接:视口宽高、位置与滚动高度_javascript_蜗牛不会跑-CSDN博客