zf-fe

✍️ Tangxt ⏳ 2020-07-01 🏷️ DOM 操作

32-DOM操作-offset系列(获取元素的偏移值)

★前言

回顾一下之前了解到的东西:

// 获取盒子可视区域的宽高(内容宽度+左右PADDING)
box.clientWidth
box.clientHeight

// 获取盒子左边框和上边框的大小
box.clientLeft
box.clientTop

// 在CLIENT的基础上加上BORDER == 盒子本身的宽高
box.offsetWidth
box.offsetHeight

// 在没有内容溢出的情况下,获取的结果和CLIENT是一样的
// 在有内容溢出的情况下,获取的结果约等于真实内容的宽高(上/左PADDING + 真实内容的高度/宽度)
box.scrollWidth
box.scrollHeight

// 竖向滚动条卷去的高度 -> 在13个属性中,只有该属性可读写,其余的只能读,不能写……
// 横向滚动条卷去的宽度
box.scrollTop
box.scrollLeft

★offsetParent & offsetTop & offsetLeft

box.offsetParent
box.offsetTop
box.offsetLeft

父参照物


Q:参照物?

JS里边的参照物概念,与CSS里边用于定位的参照物概念是一致的!

在物理里边,我们知道运动是相对,如你在路边看到一辆巴士经过,那么巴士里边的人动了吗?

对于你而言,是动了

对于巴士里边的人来说,是静止的

在研究机械运动时,人们事先选定的、假设不动的,作为基准的物体叫做参照物

注意:一般不以研究对象为参照物,选择不同的参照物来描述同一个物体的运动状态,可能得出不同结论,所以运动和静止都是相对的

如我们想要确定巴士里边的人的运动状态:

所以,在CSS的定位里边:

body > container > box

默认所有的小盒子的参照物都是 body 元素,就像是我们通常以地面为参照物一样,即便参照物是可以任意选定的!

如果小盒子设置了绝对定位,那么默认情况下是相对于body元素定位的 -> 小盒子脱离文档流 -> 小盒子的外边框(左上角)会被吸附到body的内边框(左上角)

Q:如果父参照物有border呢?如有10px的边框,那么 `box` 的 `offsetLeft` 和 `offsetTop` 实际是多少?

offsetLeft的特殊性

Q:如果盒子绝对定位,脱离文档流,打算改变默认的父参照物,那么 `offsetLeft/Top` 的值是不是因此而改变呢?

定位

.container {
  position: relative;
  width: 500px;
  height: 500px;
  border: 10px solid yellowgreen;

}

.box {
  /* or relative */
  position: absolute;
  top: 20px;
  left: 20px;
  margin-left: 20px;
  /* 默认的content-box也是如此 */
  box-sizing: border-box;
  width: 300px;
  height: 300px;
  padding: 20px;
  border: 10px solid yellow;
  line-height: 32px;
  overflow: auto;
}

box 绝对定位了,我们让父参照物为 container ,然而 ** offsetTop 的计算规则发生了变化,即是 container 的内壁到 box 的外壁**,而 offsetLeft 依旧一样,即还是 container 的内壁到 box 的外壁

双重 relative 也是可以改变父参照物的,我一直以为只有「子绝父相」才会改变子的父参照物,没想到「子相父相」也行

Q:我们知道同一平面中,所有后代元素的父参照物是 `body` 元素,那么 `body` 元素的父参照物是谁?

document.body.offsetParent === null;

null 值啦!即「没有」……可以说 body 元素就是参照物的终点!

★如何获取元素的偏移值?(存在很多级嵌套,body元素不一定是xxx元素的父参照物)

◇思路

  1. 求出 xxx 元素的 offsetLeft
  2. 求出 xxx 元素的父级参照物 pppborder+offsetLeft
  3. 循环第二步,直到父级参照物是 body

思路

◇代码

/*
 * offset:获取当前元素距离BODY的左/上偏移(不论其父参照物是谁)
 *   @params
 *      curEle:current element当前要操作的元素
 *   @return
 *      [object]包含上/左偏移的信息  => {top:xxx,left:xxx}
 * by zhufengpeixun on 2019/08/14
 */
function offset(curEle) {
  var par = curEle.offsetParent,
    l = curEle.offsetLeft,
    t = curEle.offsetTop;
  //存在父参照物,而且还没有找到BODY
  while (par && par.tagName !== "BODY") {
    //在原有偏移的基础上累加:父参照物的边框、父参照物的偏移
    if (!/MSIE 8\.0/.test(navigator.userAgent)) {
      //IE8中偏移值自已就算了边框了,不需要我们在加边框的值 navigator.userAgent获取当前浏览器的版本信息
      l += par.clientLeft;
      t += par.clientTop;
    }
    l += par.offsetLeft;
    t += par.offsetTop;
    //继续获取上级参照物
    par = par.offsetParent;
  }
  return {
    top: t,
    left: l,
  };
}

用循环的姿势来迭代得到结果……(while用在我们不知道循环多少次……) -> 兼容性处理,可以用惰性思想……

offset(box) 的执行结果是:

offsetLeft怎么了?

测试如果父参照物不是body:

父参照物不是body

IE8下的测试结果:

IE8


Q:IE8在计算 `offsetLeft` 的时候,是父参照物外壁到box的外壁,那么我们该如何确定当前浏览器是IE8呢?

IE8下的偏移值

IE8 不支持flex,不支持 let ,不支持 vh ……谁叫我去兼容IE8谁就是我的敌人……

获取浏览器版本信息:

window.navigator.userAgent

// Chrome
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36"

// Edge
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"

// IE8
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)"

★了解更多

➹:什么是参照物?参照物到底怎么判断,人与人是否也需要找对参照物! - 知乎

➹:position - CSS: Cascading Style Sheets - MDN

★总结

不用兼容IE8

★Q&A

1)怎样才算脱离文档流?

有人说 relative 的元素没有脱离文档流,而有些人如周老师认为它是脱离文档流的!

➹:css脱离文档流到底是什么意思,脱离文档流就不占据空间了吗?脱离文档流是不是指该元素从dom树中脱离? - 知乎

2) `offsetLeft` 与 `offsetTop` 的计算规则统一了?即都是父参照物的外壁到 `box` 的外壁(box `relative/absolute/fixed` 定位了,是父参照物内壁到 `box` 外壁)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    body,
    html {
      height: 100%;
    }

    body {
      display: flex;
      justify-content: center;
      align-items: center;
      border: 20px solid lightgreen;
    }

    .box {
      width: 300px;
      height: 300px;
      border: 30px solid yellow;
    }
  </style>
</head>

<body>
  <div class="box" id="box">
    1.你日渐平庸,甘于平庸,将继续平庸。—— 《以自己喜欢的方式过一生》
    2.我将融入剧烈争斗的大人世界,要在那边孤军奋战,必须变得比任何人都坚不可摧。——《海边的卡夫卡》
  </div>
  <script>
    let box = document.getElementById("box");
  </script>
</body>

</html>

效果:

offsetLeft的计算规则

我猜是不是 display: flex; 的问题?

结果测试一下,发现这并不是呀!

offsetLeft

我看了一下这个网站的介绍:

HTML DOM offsetLeft Property

The offsetLeft property returns the left position (in pixels) relative to the left side the offsetParent element.

The returned value includes:

总体上看,offsetLeft的值就是一个box元素的border外壁到父参照物如body元素的margin-left的距离!

Note: The offsetParent element is the nearest ancestor that has a position other than static.

Tip: To return the top position of an element, use the offsetTop property.

可以看到,这是有包括border的!

offsetLeft

margin也包括呀!

代码:https://www.w3schools.com/code/tryit.asp?filename=GGE63HT01282

➹:html - what is the difference between offsetLeft and ‘clientLeft’ in javascript - Stack Overflow

➹:offsetWidth与offsetLeft_willard_cui的博客-CSDN博客_offsetleft