浏览器常见Observer的使用

227 阅读6分钟

浏览器为开发者提供了很多好用的observer api。observer Api 属于微任务,创建的时候会调用一次,然后每次监听的相关事件触发的时候会执行回调。下面介绍IntersectionObserver、mutationObserver、resizeObserver、performanceObserver、reportingObserver的使用。

一、IntersectionObserver

IntersectionObserver API提供了一种异步检测目标元素与祖先元素或视口相交情况变化的方法。

检测一个元素是否可见的传统方法:

监听到scroll事件后,调用目标元素的getBoundingClientRect方法,获取相关元素的边界信息,判断是否在视口之内。这种方法的缺点是,由于scroll事件密集发生,调用目标元素的getBoundingClientRect方法是在主线程上执行,计算量很大,容易造成性能问题。

Intersection Observer API 会注册一个回调函数,每当被监视的元素进入或者退出另外一个元素时 (或者视口),或者两个元素的相交部分大小发生变化时,该回调方法会被触发执行。这样,我们网站的主线程不需要再为了

监听元素相交而辛苦劳作,浏览器会自行优化元素相交管理。

1.1 API

var io = new IntersectionObserver(callback, option);

(1) option配置对象:

  • root:

即根元素。指定目标元素所在的容器节点。如果未指定或者为null,则默认为浏览器视窗。

  • threshold:

阈值。即两个元素(目标元素与根元素)的相交部分。可以是单一的 number 也可以是 number 数组。target 元素和 root 元素相交程度达到该值的时候 IntersectionObserver 注册的回调函数将会被执行。

  • rootMargin:

根 (root) 元素的外边距。类似于 CSS 中的 margin 属性,比如 "10px 20px 30px 40px" (top、right、bottom、left)。

(2) callback回调函数:

当元素可见比例超过指定阈值后(第一次监听目标元素的时候也会执行),会调用一个回调函数,此回调函数接受两个参数:

  • entries

    一个IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。

  • observer

          被调用的IntersectionObserver实例

只要目标满足为 IntersectionObserver 指定的阈值(观察者可见性发生变化),就会调用回调。回调接收 IntersectionObserverEntry 对象观察者的列表:

var io = new IntersectionObserver( (entries, observer) => { // 观察者列表 console.log(entries); } )

entries是一个数组,每个成员都是一个IntersectionObserverEntry对象。IntersectionObserverEntry接口描述了目标元素与其根元素容器在某一特定过渡时刻的交叉状态

IntersectionObserverEntry对象属性:

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  • target:被观察的目标元素,是一个 DOM 节点对象
  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
  • boundingClientRect:目标元素的矩形区域的信息
  • intersectionRect:用来描述根和目标元素的相交区域信息
  • intersectionRatio:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0
  • isIntersecting:目标元素与根元素是否相交

创建一个交叉观察器后,需要给定一个元素进行观察:

// 开始观察 
io.observe(document.getElementById('example'));
// 停止观察 
io.unobserve(element); 
// 关闭观察器
io.disconnect();

1.2  应用

(1)懒加载

有时,我们希望某些静态资源(比如图片),只有用户向下滚动,它们进入视口时才加载,这样可以节省带宽,提高网页性能。这就叫做"惰性加载"。只有目标区域可见时,才会加载资源。

function query(selector) {
  return Array.from(document.querySelectorAll(selector));
}

// 创建观察器
var observer = new IntersectionObserver(function (entries, observer) {
  entries.forEach(function (entry) {
    if (entry.isIntersecting) {
      entry.target.src = entry.target.dataset.src;
      // 停止观察
      observer.unobserve(entry.target);
    }
  });
});

query(".lazy-loaded").forEach(function (item) {
  // 开始观察
  observer.observe(item);
});

(2) 无限滚动

无限滚动时,最好在页面底部有一个页尾栏。一旦页尾栏可见,就表示用户到达了页面底部,从而加载新的条目。

var intersectionObserver = new IntersectionObserver(function (entries) {
  // 页尾栏不可见,就返回
  if (entries[0].intersectionRatio <= 0) return;
  // 页尾栏可见,加载新条目
  loadItems(10);
  console.log("Loaded new items");
});

// 开始观察
intersectionObserver.observe(document.querySelector(".scrollerFooter"));

(3)  固定导航栏

当用户滑动页面内容,导航栏快消失时,固定导航栏

const stickyNav = (entries, observer) => {
  const [entry] = entries;
  // 没有交叉时导航栏不需要固定;有交叉时导航栏固定
  if (!entry.isIntersecting) {
    this.showFixNav = false;
  } else {
    this.showFixNav = true;
  }
};

var intersectionObserver = new IntersectionObserver(stickyNav, {
  threshold: [0, 1],
});

const navigationBar = document.querySelector(".navigation-bar");

intersectionObserver.observe(navigationBar);

(4) 滚动动画

在元素进入视口时,再给元素添加动画,让内容出现得更平滑,优化用户体验

const elements = document.querySelectorAll('.observer-item')
 
const observer = new IntersectionObserver(callback);
elements.forEach(ele => {
  // 开始观察
  observer.observe(ele);
})
 
 
function callback(entries, observer) {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const element = entry.target;
            element.classList.add("come-in");
            observer.unobserve(element);
        }
    })
}
 
// css
.come-in {
  opacity: 1;
  transform: translateY(150px);
  animation: come-in 1s ease forwards;
}
@keyframes come-in {
  100% {
    transform: translateY(0);
  }
}

二、 MutationObserver

MutationObserver 接口提供了监视对 DOM 树所做更改的能力。可以监听对元素的属性的修改、对它的子节点的增删改。

2.1 实例方法

MutationObserver.disconnect()
取消特定观察者目标上所有对元素的监听。

MutationObserver.observe(el, options)
开始对指定元素的监听。

2.2 应用:

文章水印被人通过 devtools 去掉了,那么就可以通过 MutationObserver 监听这个变化,然后重新加上,让水印无法去掉。

//template
  <div class="MutationObserver">
    <div class="observer-item">
        <div class="water-mark">我是水印</div>
    </div>
  </div>
 
//script
// 创建监听器
const mutationObserver = new MutationObserver((mutationsList) => {
    const removeNode = mutationsList[0].removedNodes[0]
    if(removeNode) {
        document.querySelector(".observer-item").appendChild(mutationsList[0].removedNodes[0])
    }
});
// 观察元素
mutationObserver.observe(document.querySelector(".observer-item"), {
    attributes: true,  // 观察属性变动
    childList: true,  // 观察目标子节点的变化,是否有添加或者删除
    subtree: true, // 观察所有后代节点
});

三、  ResizeObserver

窗口我们可以用 addEventListener 监听 resize 事件,那元素呢?

元素可以用 ResizeObserver 监听大小的改变,当元素 width、height 变化时会触发回调。

3.1 实例方法

ResizeObserver.disconnect()
取消特定观察者目标上所有对元素的监听。

ResizeObserver.observe(el, options)
开始对指定元素的监听。

ResizeObserver.unobserve(el)
结束对指定元素的监听。

 3.2 应用

<template>
  <div class="ResizeObserver">
    <div :class="['box']">box</div>
  </div>
</template>
 
<script>
export default {
    data() {
        return{
        }
    },
    mounted() {
        const resizeObserver = new ResizeObserver(entries => {
            console.log('当前大小', entries)
        });
        resizeObserver.observe(document.querySelector(".box"), {
            box: 'border-box' // 设置盒模型
        });
    },
    methods: {
    }
}
</script>

四、 PerformanceObserver

浏览器提供了 performance 的 api 用于记录一些时间点、某个时间段、资源加载的耗时等。

PerformanceObserver 用于监听记录 performance 数据的行为,一旦记录了就会触发回调,这样我们就可以在回调里把这些数据上报。

4.1 实例方法:

PerformanceObserver.observe(options)
options只有一个键值对entryTypes: [],entryTypes为性能条目。当性能条目被记录,性能观察者对象的回调函数会被调用。

PerformanceObserver.disconnect()
性能监测回调停止接收 性能条目。

PerformanceObserver.takeRecords()
返回当前存储在性能观察器的 performance entry 列表,并将其清空。

4.2 应用:

<template>
  <div class="performanceObserver">
    <div class="button" @click="measureClick">click me</div>
    <img src="https://p9-passport.byteacctimg.com/img/user-avatar/4e9e751e2b32fb8afbbf559a296ccbf2~300x300.image" />
  </div>
</template>
 
<script>
export default {
    mounted() {
      const performanceObserver = new PerformanceObserver(list => {
      list.getEntries().forEach(entry => {
        console.log('性能监测回调',entry);// 上报
      })
    });
    performanceObserver.observe({entryTypes: ['resource', 'mark', 'measure']}); // 监听时间点、时间段、资源加载耗时
 
    performance.mark('registered-observer');
    },
    methods: {
        measureClick() {
            performance.measure('button clicked');
        }
    }
}
</script>

五、 ReportingObserver

ReportingObserver 可以监听过时的 api、浏览器干预等报告等的打印,在回调里上报,这些是错误监听无法监听到,但对开发者了解网页运行情况很有必要。

应用:

const reportingObserver = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        console.log(report.body);//上报
    }
}, {types: ['intervention', 'deprecation']});
 
reportingObserver.observe();