那些被忽略但很好用的 Web API / ResizeObserver

你的改变,我看得见!

今天要介绍的是 ResizeObserver,它和昨天的 MutationObserver 非常相像,都是透过「观察者模式」的设计方式来监测元素,不过 ResizeObserver 监测的变动是元素的「大小」。


ResizeObserver

各位应该有使用过 window 的 resize 事件吧?只要视窗大小有更动,事件就会触发并执行 Callback,然後各位也一定跟我一样,肖想着把 resize 事件绑定在一般元素上,可想而知是不会成功的,但 ResizeObserver 的出现,终於可以实现我们的愿望了。

# Window.ResizeObserver

ResizeObserver 一样是一个建构函式,所以需要使用 new 关键字来建立实体,建立时需要传入一个 Callback Function 作为参数,该 Function 可以接到由 ResizeObserver 提供的一个阵列作为参数,该阵列中会一个或多个 ResizeObserverEntry 物件:

  • ResizeObserverEntry: 该物件中会有一些属性,让我们可以取得一些有关元素的「大小」、「位置」资讯,後面会再详细介绍。
const observer = new ResizeObserver(function (entries) {
  console.log(entries);
});

 

# ResizeObserver.observe

就和昨天说的一样,ResizeObserver 创建後并不会直接开始进行观察,我们需要透过 observe 来注册想要监测的元素,这样 ResizeObserver 才会在该元素发生变动时进行动作,这里有两个参数传入:

  • target: 一个要受到观察的 Element 元素。
  • options: 这是一个可选的参数,用来初始化观测的设定选项,目前只有一个属性:
    • box: 这个属性会决定要观测元素的哪一种「盒模型(Box Model)」。可接受的值有 content-boxborder-box 两种,预设为 content-box。(其实有第三种,但使用度很低)

如果对盒模型不太熟悉的朋友可以看这里了解一下

const observer = new ResizeObserver(function (entries) {
  console.log(entries);
});

const div = document.querySelector("div");
observer.observe(div, {
  box: "border-box",
});

这样只要受到监测的元素发生宽高的变化,ResizeObserver 就会执行我们指定的行为了。

 

# ResizeObserver.disconnect

和 MutationObserver 一样,只要使用 disconnect 这个 method 就可以注销目前所有被观察的元素,後续只要再次呼叫 observe 来注册一个被观察的元素,ResizeObserver 依然会持续运作。

const observer = new ResizeObserver(function (entries) {
  console.log(entries);
});

observer.disconnect();

 

# ResizeObserver.unobserve

ResizeObserver 除了 disconnect 之外,还额外多了一个 unobserve method,它可以让我们注销「单个」元素的观察,当某个元素已经不在需要受到监测,就可以将其做为参数,unobserve 便会把它从 ResizeObserver 的监测中移除。

const observer = new ResizeObserver(function (entries) {
  console.log(entries);
});

const div = document.querySelector("div");
observer.observe(div, {
  box: "border-box",
});
observer.unobserve(div);

 

# ResizeObserver 特性

一样的,ResizeObserver 为了优化效能问题,如果有一连串连续且即时的元素尺寸变动,那 ResizeObserver 并不会一次次触发,而是会将它们合并成一次变动,并且只会纪录最终的结果。

而如果同一时间中,有多个观测中的元素都发生了尺寸变动,那 ResizeObserver 就会有相应数量的纪录,这也就是为什麽会以阵列形式来包装 ResizeObserverEntry 物件。

const observer = new ResizeObserver(function (entries) {
  entries.forEach((entry) => {
    console.log(entry); // ResizeObserverEntry 物件
  });
});

const element1 = document.querySelector("#element1");
const element2 = document.querySelector("#element2");
observer.observe(element1);
observer.observe(element2);

element1.style.width = "300px";
element1.style.height = "200px";
element2.style.width = "300px";
element2.style.height = "200px";
// 看似是四次变动,但 ResizeObserver 只会视为「两个元素的一次变动」
// 因此 callback 只会触发一次,且 entries 中会有两组 ResizeObserverEntry

 

# ResizeObserverEntry 物件

ResizeObserverEntry 的属性虽然不多,但都较为复杂,所以下面就一一拉出来说明:

  • ResizeObserverEntry.target
    这是其中比较单纯的属性,读取到的会是变动的元素(element)。
  • ResizeObserverEntry.contentRect
    这个属性的值为一个 DOMRectReadOnly 物件,该物件会纪录很多 target 的相关资讯:

    • x: 变动元素之 contentBox 在该元素中的X座标,通常等於 padding 宽度。
    • y: 变动元素之 contentBox 在该元素中的Y座标,通常等於 padding 宽度。
    • width: 变动元素之 contentBox 的宽度。
    • height: 变动元素之 contentBox 的高度。
    • top: 变动元素之 contentBox 的「顶边」的Y座标,通常与 y 相同。
    • bottom: 变动元素之 contentBox 的「底边」的Y座标,通常与 y + height 相同。
    • left: 变动元素之 contentBox 的「左侧」的X座标,通常与 x 相同。
    • right: 变动元素之 contentBox 的「右侧」的X座标,通常与 x + width 相同。

https://ithelp.ithome.com.tw/upload/images/20211005/20125431HscZesqXjQ.png

  • ResizeObserverEntry.borderBoxSize
    这个属性会是一个阵列,而阵列中会是一个或多个 borderBoxSize 物件,该物件中又会有两个属性:

    • blockSize: 这个属性的值会是一个数值,该数值通常代表元素的高,会说通常是因为,这取决於元素被设定的书写方向,blockSize 代表的是「垂直」於书写方向的元素边长尺寸(单位:px)。

    • inlineSize: 而 inlineSize 刚好相反,它代表的是「平行」於书写方向的元素边长尺寸(单位:px)。

  • ResizeObserverEntry.contentBoxSize
    这个属性会是一个阵列,而阵列中会是一个或多个 contentBoxSize 物件,而物件中一样会有两个属性,其实跟 borderBoxSize 物件是一样的,但是两者的差别在於尺寸的计算方式,borderBoxSize 是依照 border-box 的方式计算,contentBoxSize 则是依 content-box 进行计算。

    • blockSize: 「垂直」於书写方向的元素边长尺寸(单位:px)。

    • inlineSize: 「平行」於书写方向的元素边长尺寸(单位:px)。

https://ithelp.ithome.com.tw/upload/images/20211005/20125431oRgQxV2dtD.png

以上这些资讯我们都可以在 Callback 中取得,想要做任何判断或计算都是非常方便,所以大家可以根据需求去决定需要利用的资讯有哪些。

const observer = new ResizeObserver(function (entries) {
  entries.forEach((entry) => {
    const target = entry.target;
    const borderBox = entry.borderBoxSize[0];
    console.log(`${target.id} 宽度: ${borderBox.inlineSize} 高度: ${borderBox.blockSize}`);
  });
});

 

相比 MutationObserver,ResizeObserver 的使用情境就比较多了,因为今天元素的变动会受到很多因素影响,且大部分都不是我们可以完全预测掌控的,例如视窗大小的压缩、元素内容的增加/减少、RWD 的控制的等等,这时就可以透过 ResizeObserver 来监测元素的大小,并做出相对应的动作。


<<:  30-20 之 Domain Layer - UnitOfWork

>>:  [Day22] - Django-REST-Framework GenericAPIViews 和 Mixins 介绍

D24: 工程师太师了: 第12.5话

工程师太师了: 第12.5话 杂记: <纯靠北工程师>是个Facebook匿名社群粉专,...

jquery实例演练02

JQuery DOM元素操作 透过text()、Val()来显示文本内容 范例一 <p id=...

[用 Python 解 LeetCode] (003) 80. Remove Duplicates from Sorted Array II

题干懒人包 给定一个排列好的列表,将它整理成重复项最多出现两次,比方说以下 [1,1,1,2,2,3...

Day4 中秋节就是要烤肉阿-韩式烤五花肉

中秋节就是要烤肉阿! 台式烤肉吃腻了来换换口味吧, 韩剧及韩综中常常出现韩国烤五花肉,在家就可以吃!...