如何获取页面元素的位置

背景:最近在商品列表项目迭代中,需要在商品列表底部增加一个分销商品广告位,另外接收到一个产品曝光度的埋点需求,需要知道产品出现在用户视口后在进行数据统计!

基于虚拟 DOM 数据驱动的思想,最不提倡的就是 jquery 时代的 DOM 操作!但是在目前一些复杂的页面中经常还是会用 javascript 处理一些 DOM 元素,实现一些动态效果;最常见的是用到一些元素的位置和尺寸的计算,但是其中浏览器的兼容性问题也是不可忽略的一部分,要想写出预想效果的JavaScript代码,我们需要了解一些基本知识。

基本概念

网页大小:一张网页的全部面积,就是它的大小。通常情况下,网页的大小由内容和 CSS 样式表决定。 浏览窗口大小:指的是在浏览器窗口中看到的那部分网页面积,又叫做 viewport (视口)。 如果网页的内容能够在浏览器中全部显示(也就不出现滚动条),那么网页的大小和浏览器窗口的大小是相等的。如果不能全部显示,则滚动浏览器窗口,可以显示出网页的各个部分。

基本元素属性

在每个HTML元素都有下列属性。

offsetWidth clientWidth scrollWidth
offsetHeight clientHeight scrollHeight
offsetLeft clientLeft scrollLeft
offsetTop clientTop scrollTop

为了理解方便这些属性,我们需要知道 HTML 元素的实际内容有可能比分配用来容纳内容的盒子更大,因此可能会出现滚动条,内容区域是视口,当实际内容比视口大的时候,需要把元素的滚动条位置考虑进去。

  • clientHeight 和 clientWidth 用于描述元素内尺寸,是指元素内容+内边距大小,不包括边框(IE下实际包括)、外边距、滚动条部分
  • offsetHeight 和 offsetWidth 用于描述元素外尺寸,是指元素内容+内边距+边框,不包括外边距和滚动条部分
  • clinetTop 和 clinetLeft 返回内边距的边缘和边框的外边缘之间的水平和垂直距离,也就是左,上边框宽度
  • offsetTop 和 offsetLeft 表示该元素的左上角(边缘外边框)与已定位的父容器(offsetParent对象)左上角的距离
  • offsetParent 对象是指元素最近的定位(relative、absolute)祖先元素,递归上溯,如果没有祖先元素是定位的话,会返回 null

获取视口大小

网页上的每个元素,都有 clientHeight 和 clientWidth属性。这两个属性指元素的内容部分再加上 padding 的所占据的视觉面积,不包括 border 和滚动条占用空间。

因此,document 元素的 clientHeight 和 clientWidth 属性,就代表了网页的大小

function getViewport() {
  if(!document) {
    return {}
  }
  if (document.compatMode === 'BackCompat') {
    return {
      width: document.body.clientWidth,
      height: document.body.clientHeight
    };
  }
  return {
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  };
}
复制代码

上面的 getViewport 函数就是可以返回浏览器窗口的高和宽。使用的时候,有三个地方需要注意:

  • 该函数必须在页面加载完成后才能运行,否则 document 对象还没有生成,浏览器会报错。
  • 大多数情况下,都是 document.documentElement.clientWidth 返回正确值。但是,在 IE6 的 quirks 模式中, document.body.clientWidth 返回正确的值,因此函数中加入了对文档模式的判断
  • clientWidth 和 clientHeight 都是只读属性,不能对它们赋值。

获取视口大小的另一种方式

网页中的每一个元素还有 srcollHeight 和 scrollWidth 属性,指包含滚动在内的该元素的视觉面积。 那么,document 对象的 scrollHeight 和 scrollWidth 属性就是网页的大小,意思就是滚动条滚过的所有长度和宽度。

仿照 getViewport 函数,可以写出 getPagearea() 函数。

function getPagearea() {
 if (document.compatMode == 'BackCompat') {
  return {
     width: Math.max(document.body.scrollWidth, document.body.clientWidth),
     height: Math.max(document.body.scrollHeight, document.body.clientHeight)
    };
 }
 return {
    width: Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth),
   height: Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight)
  };
}

复制代码

相对文档与视口的坐标

当我们在计算一个 DOM 元素位置也就是坐标的时候,会涉及到两种坐标系, 文档坐标__和__视口坐标。 我们经常用到的document就是整个页面部分,而不仅仅是窗口可见部分,还包括因为窗口大小限制而出现滚动条的部分,它的左上角就是我们所谓相对于文档坐标的原点。

视口是显示文档内容的浏览器的一部分,它不包括浏览器外壳(菜单,工具栏,状态栏等),也就是当前窗口显示页面部分,不包括滚动条。

如果文档比视口小,说明没有出现滚动,文档左上角和视口左上角相同,一般来讲在两种坐标系之间进行切换,需要加上或减去滚动的偏移量(scroll offset)。

为了在坐标系之间进行转换,我们需要判定浏览器窗口的滚动条位置。window对象的pageXoffset和pageYoffset提供这些值,IE 8及更早版本除外。也可以通过scrollLeft和scrollTop属性获得滚动条位置,正常情况下通过查询文档根节点(document.documentElement)来获得这些属性值,但在怪异模式下必须通过文档的body上查询

image.png | left | 500x374

文档坐标

任何HTML元素都拥有offectLeft和offectTop属性返回元素的X和Y坐标,对于很多元素,这些值是文档坐标,但是对于以定位元素后代及一些其他元素(表格单元),返回相对于祖先的坐标。我们可以通过简单的递归上溯累加计算

function getElementPosition(e) {
  let x = 0;
  let y = 0;
  while (e != null) {
    x += e.offsetLeft;
    y += e.offsetTop;
    e = e.offsetParent;
  }
  return { x, y };
}
复制代码

尽管如此,这个函数也不总是计算正确的值,当文档中含有滚动条的时候这个方法就不能正常工作了,我们只能在没有滚动条的情况下使用这个方法,不过我们用这个原理算出一些元素相对于某个父元素的坐标。

快速方法:

网页元素的相对位置就是:

let X = element.getBoundingClientRect().left;
let Y = element.getBoundingClientRect().top;
复制代码

视口坐标

计算视口坐标就相对简单了很多,可以通过调用元素getBoundingClientRect  方法。方法返回一个有left、right、top、bottom属性的对象,分别表示元素四个位置的相对于视口的坐标。getBoundingClientRect 所返回的坐标包含元素的内边距和边框,不包含外边距。兼容性很好,非常好用

function getElementViewTop(element) {
  let actualTop = element.offsetTop;
  let current = element.offsetParent;
  let elementScrollTop;

  while (current !== null) {
    actualTop += current.offsetTop;
    current = current.offsetParent;
  }

  if (document.compatMode == 'BackCompat') {
    elementScrollTop = document.body.scrollTop;
  } else {
    elementScrollTop = document.documentElement.scrollTop;
  }

  return actualTop - elementScrollTop;
}


function getElementViewLeft(element) {
  let actualLeft = element.offsetLeft;
  let current = element.offsetParent;
  let elementScrollLeft;

  while (current !== null) {
    actualLeft += current.offsetLeft;
    current = current.offsetParent;
  }

  if (document.compatMode == 'BackCompat') {
    elementScrollLeft = document.body.scrollLeft;
  } else {
    elementScrollLeft = document.documentElement.scrollLeft;
  }

  return actualLeft - elementScrollLeft;
}
复制代码

快速方法:

使用 getboundingClientRect() 方法。它返回一个对象,其中包含了 lefttopwidthheight 等属性。

let X = element.getBoundingClientRect().left;
let Y = element.getBoundingClientRect().top;
复制代码

再加上滚动 距离,就可以得到绝对位置:

const X= element.getBoundingClientRect().left+document.documentElement.scrollLeft;
const Y =element.getBoundingClientRect().top+document.documentElement.scrollTop;
复制代码

猜你喜欢

转载自juejin.im/post/5bdd5723518825171a180aaf
今日推荐