观察者们

Intersection Observer

Intersection Observer API提供了一种异步观察目标元素与祖先元素或顶级文档viewport的“交集"中的变化的方法。

兼容性

介绍

一直以来,检测元素的可视状态或者两个元素的相对可视状态都不是件容易事,毕竟大部分解决办法并非完全可靠,也极易拖慢整个网站的性能。然而,随着网页发展,对上述检测的需求也随之增加了。多种情况下都需要用到元素交集变化的信息,比如:

  • 当页面滚动时,懒加载图片或其他内容。
  • 实现“可无限滚动”网站,也就是当用户滚动网页时直接加载更多内容,无需翻页。
  • 为计算广告收益,检测其广告元素的曝光情况。
  • 根据用户是否已滚动到相应区域来灵活开始执行任务或动画。

以往我们的做法是绑定容器的scroll事件,或者设定时器不停地调用getBoundingClientRect() 获取元素位置,但是这些代码都是在主线程上运行。所以这样做的性能会有一定的影响。

我们现在的mall-core里的imglazyload就是用的传统的方式

API

var observer = new IntersectionObserver(callback, options)

以上代码会返回一个IntersectionObserver实例,callback是当元素的可见性变化时候的回调函数,options是一些配置项(可选)

返回的这个实例呢,也比较简单,有三个方法。

  • observe 观察某个元素 observer.observe(ele)
  • unobserve 停止观察某个元素 observer.unobserve(ele)
  • disconnect 关闭观察器 observer.disconnect()

Intersection observer options
传递到IntersectionObserver()构造函数的 options 对象,允许控制调用观察者的回调的环境。它也是有3个字段

  • root
    指定根(root)元素,用于检查目标的可见性。必须是目标元素的父级元素。如果未指定或者为null,则默认为浏览器视窗。用作父级元素的时候,取值为父级元素的getboundingClientRect()
  • rootMargin
    root元素的外边距。类似于css中的 margin 属性,比如 "10px 20px 30px 40px" (top, right, bottom, left)。如果有指定root参数,则rootMargin也可以使用百分比来取值。该属性值是用作root元素和target发生交集时候的计算交集的区域范围,使用该属性可以控制root元素每一边的收缩或者扩张。默认值为0。
    用图来解释

通过该图就可以知道,原本被观察的元素在通过rootMargin扩大后,会提前触发callback
  • threshold
    用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是0,

设置为 [0, 0.5, 1] 就是指当元素出现0%、50%、100%时都会触发callback

callback
当元素的可见性发生变化时,就会触发callback函数。

function callback(entries, observer) {
    // 回调接受两个参数,一个是IntersectionObserverEntry数组,一个是obsever自己
    for (var i = 0; i < entries.length; i++) {
            console.log(entries[i]);
    }
}
  • boundingClientRect 目标元素的矩形信息
  • intersectionRatio 相交区域和目标元素的比例值
  • intersectionRect/boundingClientRect 不可见时小于等于0
  • intersectionRect 目标元素和视窗(根)相交的矩形信息 可以称为相交区域
  • isIntersecting 目标元素当前是否可见 Boolean值 可见为true
  • isvisible 感觉一直都是false,官方也没有介绍
  • rootBounds 根元素的矩形信息,没有指定根元素就是当前视窗的矩形信息
  • target 观察的目标元素
  • time 返回一个记录从IntersectionObserver的时间到交叉被触发的时间的时间戳

请留意,你注册的回调函数将会在主线程中被执行。所以该函数执行速度要尽可能的快。如果有一些耗时的操作需要执行,建议使用 Window.requestIdleCallback() 方法。

需要注意的是
由于观察名叫交叉,所以第一思维会是和边相交,很容易理解为元素和根元素的边相交,实际上这里的触发方式并不是说一定就是和根元素的边相交,而是元素的可见性出现在根元素整体视窗内就算相交。

接下来,就用这个来做一个简易的懒加载模块

核心代码

import { useEffect } from 'react';
function loadImg(entries, observer) {
    for (var i = 0; i < entries.length; i++) {
        const img = entries[i].target;
        var src = img.getAttribute("data-src");
        console.log(entries[i]);
        if (entries[i].isIntersecting) {
            img.src = src;
            
            img.removeAttribute("data-src");
            img.classList.remove('imglazy');
            observer.unobserve(img);
            // 实验0.5,1 解开
        }
    }
}
function observerImgs(className, observer) {
    const imgs = document.querySelectorAll(`img.${className}`);
    if(!imgs.length) {
        return;
    }
    imgs.forEach((img) => {
        observer.unobserve(img);
        observer.observe(img);
    });
}
function useImgLazy(className, list) {
    useEffect(() => {
        const observer = new IntersectionObserver(loadImg,{
            // root: document.querySelector('.product_list'),
            // rootMargin: '0px',
            threshold: [0.5, 1]
        });
        observerImgs(className, observer);
        return () => {
            observer.disconnect();
        };
    }, [className, list]);
}
export default useImgLazy;

为了观察更直观,所以demo是用的0.5露出才变更src

MutationObserver

MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。

兼容性

介绍

MutationObserver构造函数只有一个callback参数,callback和上面的类似,一个是被改动的MutationRecord数组,一个是观察对象。

实例拥有3个方法

  • disconnect()

阻止 MutationObserver 实例继续接收的通知,直到再次调用其observe()方法,该观察者对象包含的回调函数都不会再被调用。

  • observe()

配置MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。

  • takeRecords()

MutationObserver的通知队列中删除所有待处理的通知,并将它们返回到MutationRecord对象的新Array中,什么是待处理呢,因为这里的所有操作都是异步的,takeRecords 立刻执行。

observe 方法需要提供两个参数

  • target

DOM树中的一个要观察变化的DOM Node (可能是一个Element) , 或者是被观察的子节点树的根节点。

  • options 可选

一个可选的MutationObserverInit 对象,此对象的配置项描述了DOM的哪些变化应该提供给当前观察者的callback。当监听的时候,里面的属性至少有一个为true,否则会抛出异常。

mutationObserver.observe(content, {
    attributes: true, // Boolean - 观察目标属性的改变
    characterData: true, // Boolean - 目标节点或子节点树中节点所包含的字符数据的变化
    childList: true, // Boolean - 目标节点(如果subtree为true,则包含子孙节点)添加或删除新的子节点。默认值为false。
    subtree: true, // Boolean - 目标以及目标的后代改变都会观察,就是如果这个值为true,其他属性为true后就会都包含子节点。
    attributeOldValue: true, // Boolean - 表示需要记录改变前的目标属性值
    characterDataOldValue: true, // Boolean - 设置了characterDataOldValue可以省略characterData设置
    // attributeFilter: ['src', 'class'] // Array - 观察指定属性
});

mutationRecord数组里的属性有

MutationRecord = {
  type:如果是属性变化,返回"attributes",如果是一个CharacterData节点(Text节点、Comment节点)变化,返回"characterData",节点树变化返回"childList"
  target:返回影响改变的节点
  addedNodes:返回添加的节点列表
  removedNodes:返回删除的节点列表
  previousSibling:返回分别添加或删除的节点的上一个兄弟节点,否则返回null
  nextSibling:返回分别添加或删除的节点的下一个兄弟节点,否则返回null
  attributeName:返回已更改属性的本地名称,否则返回null
  attributeNamespace:返回已更改属性的名称空间,否则返回null
  oldValue:返回值取决于type。对于"attributes",它是更改之前的属性的值。对于"characterData",它是改变之前节点的数据。对于"childList",它是null
}

主要知道了哪些元素哪些属性发生了某些变化后,可以针对性的对某个元素做一些操作。
演示demo

应用

  • 监听JS脚本创建的DOM渲染完成
  • 监听图片/富文本编辑器/节点内容变化及处理
  • 关于vue对于MutationObserver的应用

PerformanceObserver()

PerformanceObserver 用于监测性能度量事件,在浏览器的性能时间轴记录下一个新的 performance entries 的时候将会被通知 。
它会实时的根据每个资源的加载来通知。

使用方法

    const observer = new PerformanceObserver(performanceCallBack);
    observer.observe({entryTypes: ['paint', 'resource']});  
    observer.disconnect();
    observer.takeRecords();

其中,callback里第一个参数是PerformanceObserverEntryList,有一个getEntries方法,可以取得监控的list,第二个参数是observer对象。

说到这个吧,就得说
Performance.timingperformance.getEntries()

对比如下
1:在window.onload函数里面我们进行loadEventEnd的取值会取不到,而在PerformanceObserver则不存在这样的问题;
2:使用PerformanceObserver我们发现没有navigationStart,domLoading的值。
3:PerformanceObserver更精确。

【Prompt for unload】- 用户跳转行为(在地址栏输入url后按回车,或者点击a标签跳转等)
 navigationStart、startTime // 当前浏览器窗口的前一个网页关闭开始执行的时间戳
 unloadStart // 前一个页面unload触发开始时间戳
【unload】- 前一个页面unload时间
 unloadEnd // 前一个页面unload触发结束时间戳
 redirectStart // 返回第一个HTTP跳转开始时的时间戳如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0
【redirect】- 重定向
 redirectEnd // 返回最后一个HTTP跳转结束时(即跳转回应的最后一个字节接受完成时)的时间戳,如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0
 fetchStart // 返回浏览器准备使用HTTP请求读取文档时的时间戳。该事件在网页查询本地缓存之前发生
【App cache】- 网页查询本地缓存
 domainLookupStart // 返回域名查询开始时的时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值
【DNS】- 域名查询
 domainLookupEnd // 返回域名查询结束时的时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值
 connectStart // 返回建立TCP链接开始向服务器发送时的时间戳。如果使用持久连接(persistent connection),则返回值等同于fetchStart属性的值
【TCP】
 secureConnectionStart // 它的值是安全连接握手之前的时刻。如果该属性不可用,则返回undefined。如果该属性可用,但没有使用HTTPS,则返回0
 connectEnd // 返回浏览器与服务器之间的连接建立时的时间戳。如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束
回浏览器与服务器开始安全链接的握手时的时间戳。如果当前网页不要求安全连接,则返回0
 requestStart // 返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的时间戳
【Request】 - 网络请求
 responseStart // 返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的时间戳
【Response】
 responseEnd // 返回浏览器从服务器收到(或从本地缓存读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的时间戳
 domLoading // 返回当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的readystatechange事件触发时)的时间戳
【Processing】
 domInteractive // 返回当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的时间戳
 domContentLoadedEventStart // 返回当前网页DOMContentLoaded事件发生时(即DOM结构解析完毕、所有脚本开始运行时)的时间戳
 domContentLoadedEventEnd // 返回当前网页所有需要执行的脚本执行完成时的时间戳
 domComplete // 返回当前网页DOM结构生成时(即Document.readyState属性变为“complete”,以及相应的readystatechange事件发生时)的时间戳
 loadEventStart // 返回当前网页load事件的回调函数开始时的时间戳。如果该事件还没有发生,返回0
【onLoad】- window.onLoad触发
 loadEventEnd // 返回当前网页load事件的回调函数运行结束时的时间戳。如果该事件还没有发生,返回0。通过while循环持续判断直到loadEventEnd>0则表示完全加载完毕了!网络不再有任何数据请求、dom也渲染完毕了

demo;

前端性能中,有一些比较重要的指标,比如
白屏时间
首屏时间

那白屏时间其实就是用我们的页面第一个内容渲染出来时的时间,就是白屏时间,js、css的加载都是会阻塞页面的渲染的。

首屏时间的话的话有多种方式计算,标准也不一样,这里不多描述了。

延伸。优化首页性能~~~

你可能感兴趣的:(javascript)