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.timing
和performance.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的加载都是会阻塞页面的渲染的。
首屏时间的话的话有多种方式计算,标准也不一样,这里不多描述了。
延伸。优化首页性能~~~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。