引言

  最近突然看到了有关图片懒加载的问题,大致意思就是初始状态下页面只加载浏览器可视区域的图片,剩余图片在当浏览器可视区域滚动到其位置时才开始加载。貌似现在许多大型网站都有实现懒加载,所以我便就此问题思考了一下。首先第一个问题是浏览器没有相关的 API 方法可以检测某个元素是否在可视区域,那么就只能我们人工计算,所以这里就涉及到了元素长宽,滚动条位置的知识。本文涉及的到的知识有元素长宽 clientWidth/offsetWidth/scrollWidth 的区别、以及 clientTop/offsetTop/scrollTop 的区别,并给了获取元素坐标的源代码。

一、 clientWidth、offsetWidth、scrollWidth 的区别

  通常大家获取元素的长宽的时候都会使用一些框架封装好的方法,比如 jQuery.prototype.width() ,这些框架使用起来方便快捷,不过其中涉及到的知识还是非常多的,关于元素的长宽,有多种的获取方法,其代表的实际意义也是不同的。

  简单来说可以使用下列公式:

  clientWidth = width(可视区) + padding

  offsetWidth = width(可视区) + padding + border

  scrollWidth = width(内容区) 

  假设有我们以下一个元素:

 #test {
width: 100px;
height: 100px;
margin: 10px;
border: 10px solid #293482;
padding: 10px;
background-color: yellow;
overflow: auto;
}
clientWidth offsetWidth scrollWidth
     

  以上 DEMO 是常规情况下的区别,下面加上一个滚动条我们们再来观察以下:

clientWidth offsetWidth scrollWidth

注意这里不包括滚动条的长度

   

这里实际上相当于内容的宽度

二、clientTop、offsetTop、scrollTop 的区别

  我们使用以下公式:

  clientTop = border

  offsetTop = 元素边框外围至父元素边框内围

  scrollTop = 元素可视区域顶部至实际内容区域的顶部

  给定以下两个元素 container 和 test

 #container {
background-color: #F08D8D;
padding: 10px;
}
#test {
position: relative;
top: 10px;
width: 100px;
height: 100px;
margin: 20px;
border: 15px solid #293482;
padding: 10px;
background-color: yellow;
}
clientTop  offsetTop scrollTop

 
 

   

三、获取页面元素绝对定位坐标

  有了以上知识基础之后,我们现在需要考虑的问题是,如何获取页面元素的绝对位置,也就是在文档流内容区的位置。我们知道,元素的 offsetTop 属性可以获取当前元素边框外围至父元素边框内围的的距离,clientTop 可以获取元素边框的宽度。那么现在用一个递归的公式就可以求得当前元素在页面中的绝对位置:

  Element.absoluteTop = Element.parent.absoluteTop + Element.offsetTop + Element.clientTop;

  同理,我们用参照元素的长宽减去 left 和 top 和定位,即可得到 right 和 bottom 的定位;

  所以我们可以编写以下工具来获取元素的绝对位置,也就是在内容区的定位(参照元素必须是目标元素的祖先元素):

 var Position = {};
(function () {
Position.getAbsolute = function (reference, target) {
//因为我们会将目标元素的边框纳入递归公式中,这里先减去对应的值
var result = {
left: -target.clientLeft,
top: -target.clientTop
}
var node = target;
while(node != reference && node != document){
result.left = result.left + node.offsetLeft + node.clientLeft;
result.top = result.top + node.offsetTop + node.clientTop;
node = node.parentNode;
}
if(isNaN(reference.scrollLeft)){
result.right = document.documentElement.scrollWidth - result.left;
result.bottom = document.documentElement.scrollHeight - result.top;
}else {
result.right = reference.scrollWidth - result.left;
result.bottom = reference.scrollHeight - result.top;
}
return result;
}
})();

  此方法可以获取一个元素相对于一个父元素的定位,如果要获取元素在整张页面,直接传入 document 即可:

 Position.getAbsolute(document, targetNode); //{left: left, right: right, top: top, bottom: bottom}

四、获取元素的可视区定位坐标

  在上一小节中,我们封装了一个函数,这个函数可以用来获取一个元素的相对于一个祖先元素的绝对定位坐标,在这一小节中,我们来获取元素相对于浏览器窗口可视区域的定位坐标。在上一个函数中,我们可以获取一个元素在 document 当中的定位,还记得我们在第二小节中的 scrollTop 属性吗?该属性可以获取滚动窗口可视区域顶端距离内容区顶端的距离,我们用元素的绝对定位坐标减去 document 的滚动定位就是我们想要的浏览器窗口定位啦(相对于浏览器左上角):

  ViewportTop = Element.absoluteTop - document.body.scrollTop;

  这里需要注意一个兼容性的问题,在 Chrome 中可以用 document.body.scrollTop 和 window.pageYOffset,IE 7/8 只能通过 document.documentElement.scrollTop 获取, FireFox 和 IE9+ 可以用 document.documentElement.scrollTop 和 window.pageYOffset 获取,Safari 需要 window.pageYOffset 获取。所以这里我们需要做一下浏览器兼容:

  scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;

  注意这里的顺序,在 IE7/8 中 window.pageYOffset 是 undefined ,document.body.scrollTop 在任何浏览器中都有,只是不支持的值为 0,如果表达式返回 undefined ,会影响后面的计算操作。而 || 运算符是一个短路取真运算符,所以我们要所有浏览器都有的 document.body.scrollTop 方法放在最后,关于 || 运算符的问题,可以参考 《探寻 JavaScript 逻辑运算符(与、或)的真谛》

  我们在刚才的工具上添加一个方法:

 var Position = {};
(function () {
Position.getAbsolute = function (reference, target) {
var result = {
left: -target.clientLeft,
top: -target.clientTop
}
var node = target;
while(node != reference && node != document){
result.left = result.left + node.offsetLeft + node.clientLeft;
result.top = result.top + node.offsetTop + node.clientTop;
node = node.parentNode;
}
if(isNaN(reference.scrollLeft)){
result.right = document.documentElement.scrollWidth - result.left;
result.bottom = document.documentElement.scrollHeight - result.top;
}else {
result.right = reference.scrollWidth - result.left;
result.bottom = reference.scrollHeight - result.top;
}
return result;
}
Position.getViewport = function (target) {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
var absolutePosi = this.getAbsolute(document, target);
var Viewport = {
left: absolutePosi.left - scrollLeft,
top: absolutePosi.top - scrollTop,
}
return Viewport;
}
})();

  通过 Position.getViewport 方法可以获取元素相对于浏览器窗口的定位:

 Postion.getViewport(targetNode);  //{left: left, top: top}

五、判断可视区域

  在上面的几个方法中,我们可以获取元素的文档流定位和视窗定位,不过这还是不能判断一个元素是否在可视区域内,因为视窗定位可以是非常大的数字,这样元素就在视窗的后面。这里我们需要使用浏览器视窗高度 window.innerHeight 属性,在 IE8 以下需要用 document.documentElement.clientHeight 来获取。

  windowHeight = window.innerHeight || document.documentElement.clientHeight;

  现在,我们用窗口的高度,减去相对于浏览器窗口的定位,即可获取相对于浏览器窗口右下角的定位;

 var Position = {};
(function () {
Position.getAbsolute = function (reference, target) {
var result = {
left: -target.clientLeft,
top: -target.clientTop
}
var node = target;
while(node != reference && node != document){
result.left = result.left + node.offsetLeft + node.clientLeft;
result.top = result.top + node.offsetTop + node.clientTop;
node = node.parentNode;
}
if(isNaN(reference.scrollLeft)){
result.right = document.documentElement.scrollWidth - result.left;
result.bottom = document.documentElement.scrollHeight - result.top;
}else {
result.right = reference.scrollWidth - result.left;
result.bottom = reference.scrollHeight - result.top;
}
return result;
}
Position.getViewport = function (target) {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
var windowHeight = window.innerHeight || document.documentElement.offsetHeight;
var windowWidth = window.innerWidth || document.documentElement.offsetWidth;
var absolutePosi = this.getAbsolute(document, target);
var Viewport = {
left: absolutePosi.left - scrollLeft,
top: absolutePosi.top - scrollTop,
right: windowWidth - (absolutePosi.left - scrollLeft),
bottom: windowHeight - (absolutePosi.top - scrollTop)
}
return Viewport;
}
})();

  现在我们使用 Position.getViewport(targetNode) 方法可以获取元素左上角相对于窗口4个方向的定位:

 Position.getViewport(targetNode);  //{left: left, top: top, right: right, bottom: bottom}

  有了这个方法,现在就可以真正的判断元素是否在可视区域内了:

 var Position = {};
(function () {
Position.getAbsolute = function (reference, target) {
//因为我们会将目标元素的边框纳入递归公式中,这里先减去对应的值
var result = {
left: -target.clientLeft,
top: -target.clientTop
}
var node = target;
while(node != reference && node != document){
result.left = result.left + node.offsetLeft + node.clientLeft;
result.top = result.top + node.offsetTop + node.clientTop;
node = node.parentNode;
}
if(isNaN(reference.scrollLeft)){
result.right = document.documentElement.scrollWidth - result.left;
result.bottom = document.documentElement.scrollHeight - result.top;
}else {
result.right = reference.scrollWidth - result.left;
result.bottom = reference.scrollHeight - result.top;
}
return result;
}
Position.getViewport = function (target) {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
var windowHeight = window.innerHeight || document.documentElement.offsetHeight;
var windowWidth = window.innerWidth || document.documentElement.offsetWidth;
var absolutePosi = this.getAbsolute(document, target);
var Viewport = {
left: absolutePosi.left - scrollLeft,
top: absolutePosi.top - scrollTop,
right: windowWidth - (absolutePosi.left - scrollLeft),
bottom: windowHeight - (absolutePosi.top - scrollTop)
}
return Viewport;
}
Position.isViewport = function (target) {
var position = this.getViewport(target);
//这里需要加上元素自身的宽高,因为定位点是元素的左上角
if(position.left + target.offsetWidth < 0 || position.top + target.offsetHeight < 0){
return false;
}
if(position.bottom < 0 || position.right < 0){
return false;
}
return true;
}
})();

  判断理由很简单,如果有一边的定位是负值,那么元素就不在视窗内。

 Position.getAbsolute(document, targetNode);  //获取元素在文档流中的绝对坐标
Position.getViewport(targetNode); //获取元素相对于浏览器视窗的坐标
Position.isViewport(targetNode); //判断元素是否在浏览器视窗内

  浏览器兼容性:

Chrome FireFox IE Safari Edge
Support Support IE8+ Support Support Support

  IE7 也可以使用,不过结果可能会有一点差异。

扩展:图片懒加载

  在文章的开始,我们提到过图片懒加载的问题,那么具体需要怎么实现呢?这里只是给出一个思路:

  初始状态下不设置 img 的 src,将图片的真实 url 缓存在 Img 标签上,我们可以设置为 data-src ,这样图片就不会加载了,随后给鼠标添加 mousescroll 事情,每次鼠标滚动的时候将进入可视区域的图片的 src 还原,这样也就实现了图片懒加载效果。不过初始状态下需要将页面可视区域的图片先加载出来。

参考文献:

  阮一峰 — 用 JavaScript 获取元素页面元素位置

  张媛媛 — js实现一个图片懒加载插件

详细解析 JavaScript 获取元素的坐标的更多相关文章

  1. JavaScript获取元素样式

    原生的JavaScript获取写在标签内部的样式很简单: <div class="test" id="test" style="width:10 ...

  2. javascript 获取元素样式的方法

    javascript 获取元素样式常用方法. Javascript获取CSS属性值方法:getComputedStyle和currentStyle  1 .对于元素的内联CSS样式(<div s ...

  3. JavaScript获取元素尺寸和大小操作总结

    一.获取元素的行内样式 复制代码 代码如下: var obj = document.getElementById("test"); alert(obj.height + " ...

  4. javascript获取元素的计算样式

    使用css控制页面有4种方式,分别为行内样式(内联样式).内嵌式.链接式.导入式. 行内样式(内联样式)即写在html标签中的style属性中,如<div style="width:1 ...

  5. JavaScript获取元素尺寸和大小操作总结(转载)

    一.获取元素的行内样式 var obj = document.getElementById("test"); alert(obj.height + "\n" + ...

  6. javascript获取元素样式值

    使用css控制页面有4种方式,分别为行内样式(内联样式).内嵌式.链接式.导入式. 行内样式(内联样式)即写在html标签中的style属性中,如<div style="width:1 ...

  7. javascript获取元素的方法[xyyit]

    1. javascript默认的方法: <div id=”div_id” class=”div_class” name=”div_name”></div> //1. 根据id ...

  8. JavaScript 获取 Div 的坐标

    示例代码: <html> <head> <script> function CPos(x, y) { this.x = x; this.y = y; } /** * ...

  9. JavaScript获取元素CSS计算后的样式

    原文链接https://www.w3ctech.com/topic/40 我们在开发过程中,有时候需要根据元素已有样式来实现一些效果,那我们应该如何通过JavaScript来获取一个元素计算后的样式值 ...

随机推荐

  1. Java 多线程详解(五)------线程的声明周期

    Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程: ...

  2. java-web中生成文档(一)

    基于Java的解决方案也是很多的,包括使用Jacob.Apache POI.Java2Word.iText等各种方式,其实在从Office 2003开始,就可以将Office文档转换成XML文件,这样 ...

  3. Qt之新手打包发布程序

    工具:电脑必备.QT下的windeployqt Qt 官方开发环境使用的动态链接库方式,在发布生成的exe程序时,需要复制一大堆 dll,如果自己去复制dll,很可能丢三落四,导致exe在别的电脑里无 ...

  4. 【初码干货】记一次分布式B站爬虫任务系统的完整设计和实施

    [初码文章推荐] 程序员的自我修养 Azure系列文章 阿里云系列文章 爬虫系列文章 [初码产品推荐] AlphaMS开发模式 闪送达城市中央厨房 今天带来一个有意思的东西-分布式B站爬虫任务系统 这 ...

  5. 30多个Android 开发者工具 带你开发带你飞

    文中部分工具是收费的,但是绝大多数都是免费的. FlowUp 这是一个帮助你跟踪app整体性能的工具,深入分析关键的性能数据如FPS, 内存, CPU, 磁盘, 等等.FlowUp根据用户数量收费. ...

  6. 在附件管理模块中增加对FTP 上传和预览的支持

    在之前介绍的附件管理模块里面<Winform开发框架之通用附件管理模块>以及<Winform开发框架之附件管理应用>,介绍了附件的管理功能,通过对数据库记录的处理和文件的管理, ...

  7. python加载sqlite3报错:No module named _sqlite3

    环境为Ubuntu16.04 Apache2.4 Python2.7.13 django 1.8 今天部署apache+django,经过各种折腾,好不容易配置完了,发现错误Apache的日志里有一项 ...

  8. 《Python编程从入门到实践》_第四章_操作列表

    for循环遍历整个列表 pizzas = ['pizzahut','dicos','KFC'] for pizza in pizzas: print ("I like "+ piz ...

  9. SQL之trigger(触发器)

    先来看一小段程序 有如下三张表: 帐户(编号,姓名,余额,建立日期,储蓄所编号) 储蓄所(编号,名称,地址,人数,所属城市) 借贷(帐户,借贷类型,金额,日期) create trigger tri_ ...

  10. JavaScript Trick

    JavaScript 判断 一个元素 是否在 数组中 : indexOf 原理 : array.indexOf(item) 如果 item 不在 array 中 , 则返回 -1 ; 如果 item ...