Interection Observer如何观察变化

原文地址:css-tricks.com/an-explanat…
原文做者:Travis Almand
翻译:刘辉css

有不少精彩的文章探讨了如何使用Intersection Observer API,包括Phil Hawksworth,Preethi和Mateusz Rybczonek等。 我这篇文章将讲一些不同的东西。 我在今年早些时候有幸向达拉斯VueJS聚会介绍了VueJS过渡组件,我在CSS-Tricks的第一篇文章就是以此为基础的。 在演讲的问答环节中,有人问我基于滚动事件触发过渡怎么样 - 我说固然能够,可是一些听众建议我了解一下Intersection Observergit

这让我开始思考。我对Intersection Observer有基本的了解,而且可以用其完成简单的示例。 我是否知道它的工做原理而不只仅是使用它?它到底为咱们开发人员提供了什么? 做为一个资深开发者,我如何向新手甚至不知道它存在的开发者解释它的工做原理?github

在花了一些时间进行研究,测试和验证后,我决定分享本身学到的东西。web

Intersection Observer 简述

W3C公共工做草案摘要(日期为2017年9月14日的初稿)将Intersection Observer API描述为:数组

本规范描述了一个API,可用于了解DOM元素(targets)相对于包含元素或顶级视口(root)的可见性和位置。 该位置是异步传递的,对于理解元素的可见性以及实现DOM内容的预加载和延迟加载颇有用。浏览器

这个API的整体思路是提供一种观察子元素并在其进入其父元素之一的边界框内时获得通知的方法。 目标元素滚动到根元素视图中时最经常使用。 在引入Intersection Observer以前,此类功能是经过侦听滚动事件来完成的。bash

尽管Intersection Observer是针对此类功能的更高性能的解决方案,但我不建议咱们将其视为滚动事件的替代品。 相反,我建议咱们将此API视为与滚动事件在功能上互补的额外工具。 在某些状况下,二者能够一块儿解决特定的问题。服务器

基本示例

我知道我有可能重复其余文章中已经讲过的内容,不过仍是让咱们先来看一个Intersection Observer的基本示例及其提供的能力。微信

Observer由四部分组成:异步

  1. root,是观察者所绑定的父元素,能够是viewport
  2. target,它是被观察的子元素,能够有多个
  3. options对象,它定义了观察者某些方面的行为
  4. 回调函数,每次观察到父子元素的交集变化时都会调用

基本示例的代码以下所示:

const options = {
  root: document.body,
  rootMargin: '0px',
  threshold: 0
}

function callback (entries, observer) {
  console.log(observer);

  entries.forEach(entry => {
    console.log(entry);
  });
}

let observer = new IntersectionObserver(callback, options);
observer.observe(targetElement);

复制代码

代码的第一部分是options对象,它具备rootrootMarginthreshold属性。

root是父元素,通常是有滚动条的元素,其中包含被观察的元素。根据须要,这几乎能够是页面上的任何单个元素。若是不提供该属性,或者该值设置为null,跟元素就是viewport。

rootMargin描述了根元素的外边距,由rootMargin规定的矩形的每一边都会被添加至root元素的边框盒(bounding box)的相应边。它的行为很像CSS margin属性。你可使用相似10px 15px 20px的值,这使咱们的顶部边距为10px,左侧和右侧边距为15px,底部边距为20px。仅边界框受影响,元素自己不受影响。请记住,惟一容许的长度是像素和百分比值,能够是负数或正数。另请注意,若是root元素不是页面上的实际元素(例如viewport),则rootMargin无效。

threshold是用于肯定什么时候触发交集改变事件的值。数组中能够包含多个值,以便同一目标能够屡次触发交集改变事件。不一样的值是使用0到1的百分比,很是相似于CSS中的不透明度,所以将0.5的值视为50%,依此类推。这些值与目标的交叉比例有关,稍后将对其进行说明。阈值为0时,目标元素的第一个像素与根元素相交就会触发交集改变事件。阈值为1时,整个目标元素都在根元素内部时才会触发交集改变事件。

代码的第二部分是回调函数,只要观察到交集改变,就会调用该函数。传递了两个参数;entries是个数组,表明触发交集更改的每一个目标元素。这提供了不少信息为开发人员所用。第二个参数是有关观察者自己的信息。若是目标绑定到多个观察者,能够经过此参数识别是哪一个观察者。

代码的第三部分是观察者自己的建立以及观察对象。建立观察者时,回调函数和options对象能够放在观察者外部。 若是须要,能够在多个观察者之间使用相同的回调和options对象。而后,将须要观察的目标元素传递给observe()方法。它只能接受一个目标,可是能够在同一观察者上针对多个目标重复调用该方法。

注意代码中的console.log,能够看看控制台输出了什么。

观察者对象

传递给回调函数的观察者数据以下:

IntersectionObserver
  root: null
  rootMargin: "0px 0px 0px 0px"
  thresholds: Array [ 0 ]
  <prototype>: IntersectionObserverPrototype { }

复制代码

...本质上是建立对象时传递给观察者的选options对象。 这可用于肯定相交所绑定的根元素。 注意即便原始选项对象的rootMargin值为0px,该对象也将其转为0px 0px 0px 0px,这是CSS边距规范所须要的。而后是观察者正在使用的一系列阈值。

entry对象

传递给回调函数的entry对象数据以下:

IntersectionObserverEntry
  boundingClientRect: DOMRect
    bottom: 923.3999938964844, top: 771
    height: 152.39999389648438, width: 411
    left: 9, right: 420
    x: 9, y: 771
    <prototype>: DOMRectPrototype { }
  intersectionRatio: 0
  intersectionRect: DOMRect
    bottom: 0, top: 0
    height: 0, width: 0
    left: 0, right: 0
    x: 0, y: 0
    <prototype>: DOMRectPrototype { }
  isIntersecting: false
  rootBounds: null
  target: <div class="item">
  time: 522
  <prototype>: IntersectionObserverEntryPrototype { }

复制代码

能够看到,这里作了不少工做。

对于大多数开发人员而言,最可能有用的两个属性是intersectionRatioisIntersectingisIntersecting属性是一个布尔值,在交集更改时目标元素与根元素是否相交。intersectionRatio是当前与根元素相交的目标元素的百分比。它也是零到一之间的百分比表示,很是相似于观察者的options对象中threshold

三个属性(boundingClientRectintersectionRectrootBounds)表示交集相关的三个方面的具体数据。 boundingClientRect属性为目标元素的边界框提供从viewport左上角开始的bottom,left,right和top值,就像Element.getBoundingClientRect()同样。而后,将目标元素的高度和宽度做为X和Y坐标提供。 rootBounds属性为根元素提供相同形式的数据。intersectionRect提供类似的数据,它描述了由目标元素在根元素内部的相交区域造成的矩形,该区域也被用于计算intersectionRatio值。传统的滚动事件须要手动完成此计算。

要注意的是,表明这些不一样元素的全部这些形状始终都是矩形。不管所涉及元素的实际形状如何,它们老是会缩小到包含该元素的最小矩形。

target属性是指正在观察的目标元素。在观察者包含多个目标的状况下,这是肯定哪一个目标元素触发了此相交更改的简便方法。

time属性提供从首次建立观察者到触发此交集改变的时间(以毫秒为单位)。经过这种方式,你能够跟踪观看者遇到特定目标所花费的时间。即便稍后将目标再次滚动到视图中,此属性也会提供新的时间。这可用于跟踪目标进入和离开根元素的时间。

除了每次观察到交集改变时咱们能够得到这些信息外,观察者第一次启动时也会向咱们提供这些信息。例如,在页面加载时,页面上的观察者将当即调用回调函数,并提供它正在观察的每一个目标元素的当前状态。

Intersection Observer以很是高效的方式提供了有关页面上元素之间关系的数据。

Intersection Observer 可用的方法

Intersection Observer 主要有三个方法:observe(),unobserve()和disconnect()。

  • observe():observe方法用来添加观察者要监视的目标元素。 观察者能够具备多个目标元素,可是此方法一次只能接受一个目标。
  • unobserve():unobserve方法用来从观察的元素列表中移除元素。
  • disconnect():disconnect方法用来中止观察其全部目标元素。观察者自己仍处于活动状态,但没有目标。在disconnect()以后,目标元素仍然能够经过observe()传递给观察者。

这些方法提供了监视和取消监视目标元素的功能,可是一旦建立,便没法更改传递给观察者的options对象。 若是须要修改,则必须手动从新建立观察者。

Intersection Observer和滚动事件的性能对比

在探索Intersection Observer以及将其与使用滚动事件进行比较时,我须要进行一些性能测试。我只想大体了解二者之间的性能差别,为此我建立了三个简单的测试。

首先,我建立了一个样本HTML文件,该文件包含一百个设置了高度的div,以此建立一个长滚动页面。把页面放在静态服务器上,而后我用Puppeteer加载了HTML文件,启动了跟踪,让页面以预设的增量向下滚动到底部,一旦到达底部,就中止了跟踪,最后保存跟踪的结果。这样测试能够重复屡次并输出每次的结果数据。而后,我复制了样本HTML,并为要运行的每种测试类型在脚本标签中编写了js。每一个测试都有两个文件:一个用于Intersection Observer,另外一个用于滚动事件。

全部测试的目的是检测目标元素什么时候以25%的增量向上滚动经过视口。每次增长时,都会应用CSS类来更改元素的背景颜色。换句话说,每一个元素都应用了DOM修改,这将触发重绘。每次测试都在两台不一样的计算机上运行了五次:个人开发用的Mac是最新的设备,而个人我的Windows 7计算机多是当前的平均水平。记录脚本,渲染,绘画和系统的跟踪结果,而后取平均值。

第一个测试有一个观察者或一个滚动事件,每一个事件都有一个回调。对于观察者和滚动事件,这是一个至关标准的设置。尽管在这种状况下,滚动事件还有不少工做要作,由于滚动事件试图模仿观察者默认提供的数据。完成全部这些计算后,就像观察者同样,将数据存储在条目数组中。而后,在二者之间删除和应用类的功能彻底相同。另外我使用了requestAnimationFrame对滚动事件进行了节流处理。

第二个测试有100个观察者或100个滚动事件,每种类型都有一个回调。每一个元素都分配有本身的观察者和事件,但回调函数相同。这其实是低效的,由于每一个观察者和事件的行为都彻底相同,可是我想要一个简单的压力测试,而没必要建立100个惟一的观察者和事件-尽管我已经看到了许多以这种方式使用观察者的示例。

第三次测试具备100个观察者或100个滚动事件,每种类型具备100个回调。这意味着每一个元素都有其本身的观察器,事件和回调函数。固然,这是极其低效的,由于这是存储在巨大阵列中的全部重复功能。可是这种低效率是该测试的重点。

Intersection Observer和滚动事件的压力测试对比

在上面的图表中,你能够看到,第一列表明咱们的基准,根本没有运行JavaScript。接下来的两列表明第一种测试类型。 Mac的运行都很是好,符合我对开发用高端计算机的预期。 Windows机器给了咱们一个不同的结果。对我来讲,主要的兴趣点是红色所表明的脚本。在Mac上,观察者的差别约为88毫秒,而滚动事件的差别约为300毫秒。在Mac上,每种测试的整体结果都至关接近,可是脚本在滚动事件方面表现出色。对于Windows机器,它要差得多得多。观察者大约是150毫秒,而第一次和最简单的测试是1400毫秒。

对于第二个测试,咱们开始看到滚动测试的效率变得更加明显。 Mac和Windows机器都运行了观察者测试,结果与之前几乎相同。对于滚动事件测试,脚本陷入了更多困境,没法完成给定的任务。 Mac跃升到几乎一整秒的脚本编写时间,而Windows计算机跃升到惊人的3200ms。

对于第三次测试,状况没有变坏。结果与第二项测试大体相同。要注意的一件事是,在全部三个测试中,观察者的结果对于两台计算机都是一致的。尽管没有为提升观察者测试的效率作出任何优化,但Intersection Observer的性能表现仍是远远超过了滚动事件。

所以,在我本身的两台机器上进行了非科学性测试以后,我感到对滚动事件和Intersection Observer之间的性能差别有一个不错的了解。 我敢确定,我能够经过一些努力使滚动事件更有效,但这值得吗? 在某些状况下,滚动事件的精度是必需的,可是在大多数状况下,Intersection Observer就足够了-尤为是由于它看起来更加高效,而无需付出任何努力。

搞清intersectionRatio属性

IntersectionObserverEntry给咱们提供的intersectionRatio属性,表示目标元素在交集更改上的根元素边界内的百分比。 我发现我一开始不太了解这个值的实际含义。 因为某种缘由,我认为这是目标元素外观的一种简单的0%到100%的表示形式。 它与建立时传递给观察者的阈值相关。 例如,它可用于肯定哪一个阈值是刚刚触发相交更改的缘由。 可是,它提供的值并不老是很简单。

以这个demo为例:

demo

在此demo中,已为观察者分配了父容器做为根元素。 具备目标背景的子元素已分配为目标元素。 已建立阈值数组,其中包含100个条目,其顺序为0、0.0一、0.0二、0.03,依此类推,直到1。观察者触发目标元素在根元素内部出现或消失的每个百分比,以便每当比率 更改至少百分之一,此框下方的输出文本将更新。 若是您感到好奇,可使用如下代码来完成此阈值:

[...Array(100).keys()].map(x => x / 100) }
复制代码

我不建议你以这种方式为项目中的具体用途设置阈值。

首先,目标元素彻底包含在根元素中,而且按钮上方的输出将显示比率1。它应该是第一次加载的,可是咱们很快就会发现该比率并不老是精确的;该数字可能在0.99到1之间。这彷佛很奇怪,可是有可能发生,所以,若是你对等于特定值的比率进行检查,请记住这一点。

单击“left”按钮将使目标元素向左转换,以使其一半在根元素中,另外一半不在。而后,ratioRatio应该更改成0.5,或者接近0.5。如今咱们知道目标元素的一半与根元素相交,可是咱们不知道它在哪里。之后再说。

单击“top”按钮具备相同的功能。它将目标元素转换为根元素的顶部,并再次将其移入和移出。再一次,交集比率应该在0.5左右。即便目标元素位于与之前彻底不一样的位置,结果比率也相同。

再次单击“corner”按钮,会将目标元素转换为根元素的右上角。此时,目标元素中只有四分之一位于根元素内。intersectionRatio应以大约0.25的值反映出来。单击“center”会将目标元素转换回中心并彻底包含在根元素中。

若是单击“large”按钮,则将目标元素的高度更改成高于根元素。相交比应为0.8左右。这是依赖intersectionRatio的棘手部分。根据提供给观察者的阈值建立代码可使阈值永远不会触发。在此“large”示例中,基于阈值1的任何代码都将没法执行。还要考虑能够调整根元素大小的状况,例如将视口从纵向旋转为横向。

查找位置

那么,咱们如何知道目标元素相对于根元素的位置呢?此数据由IntersectionObserverEntry提供,所以咱们只须要进行简单的比较便可。

看这个demo:

demo2

该演示的设置与以前的设置大体相同。 父容器是根元素,内部具备目标背景的子容器是目标元素。 阈值是一个0、0.5和1的数组。在根元素中滚动时,将出现目标,而且其位置将在按钮上方的输出中报告。

下面执行这些检查的代码:

const output = document.querySelector('#output pre');

function io_callback (entries) {
  const ratio = entries[0].intersectionRatio;
  const boundingRect = entries[0].boundingClientRect;
  const intersectionRect = entries[0].intersectionRect;

  if (ratio === 0) {
    output.innerText = 'outside';
  } else if (ratio < 1) {
    if (boundingRect.top < intersectionRect.top) {
      output.innerText = 'on the top';
    } else {
      output.innerText = 'on the bottom';
    }
  } else {
    output.innerText = 'inside';
  }
}

复制代码

我应该指出,我没有遍历entrys数组,由于我知道老是只有一个条目,由于只有一个目标。我走了捷径,使用entries[0]

您会发现比率为零会将目标置于“外部”。小于1的比率将其放在顶部或底部。这样一来,咱们就能够查看目标的“顶部”是否小于交集矩形的顶部,这实际上意味着目标在页面上更高,并被视为“顶部”。实际上,检查根元素的“顶部”也能够解决此问题。从逻辑上讲,若是目标不在顶部,则它必须在底部。若是比率刚好等于1,则它在根元素“内部”。除了使用left或right属性检查水平位置外,其余检查方法相同。

这是高效使用Intersection Observer的一部分。开发人员无需在节流的滚动事件上从多处请求此数据,而后进行计算。它是由观察者提供的,所须要的只是一个简单的if检查。

首先,目标元素要比根元素高,所以永远不会将其报告为“内部”。单击“切换目标大小”按钮以使其小于根。如今,上下滚动时目标元素能够位于根元素内部。

经过再次单击“toggle target size”,而后单击“toggle root size”按钮,将目标元素恢复为其原始大小。这将调整根元素的大小,使其比目标元素高。再次,当上下滚动时,目标元素可能位于根元素内部。

此demo演示了有关Intersection Observer的两件事:如何肯定目标元素相对于根元素的位置以及调整两个元素的大小时会发生什么。这种对调整大小的响应让咱们看到了Intersection Observer相对于滚动事件的另外一个优点-不用再单独处理resize事件。

建立位置粘性事件

CSS position属性的“sticky”是一个有用的功能,但在CSS和JavaScript方面却有一些限制。粘性节点的样式只能是一种设计,不管是处于其正常状态仍是处于其粘性状态内。没办法让js知道这些变化。到目前为止,尚未伪类或js事件使咱们知道元素的状态变化。

我已经看到了使用滚动事件和Intersection Observer进行粘性定位事件的示例。使用滚动事件的解决方案始终存在与将滚动事件用于其余目的类似的问题。观察者的一般解决方案是用一个定位元素,仅做为观察者的目标元素使用。我喜欢避免使用诸如此类的单一目的的元素,所以我决定修改这个特定的想法。

在此demo中,上下滚动以查看章节标题对各自章节的粘性反应。

demo3

这个示例检测粘性元素什么时候位于滚动容器顶部,而后给其添加一个css类。 这是经过在给观察者特定的rootMargin时利用DOM的一个有趣的特性来实现的。 给出的值是:

rootMargin: '0px 0px -100% 0px'
复制代码

这样会将根边界的底部边缘推到根元素的顶部,从而留下一小部分可用于相交检测的零像素区域。 能够说,即便目标元素碰触到零像素区域,也会触发相交变化,即便它不存在于数字中也是如此。 考虑一下,咱们能够在DOM中具备折叠高度为零的元素。

该解决方案经过识别粘性元素始终位于根元素顶部的“粘性”位置来利用这一优点。 随着滚动的继续,粘性元素最终移出视野,而且相交中止。 所以,咱们根据输入对象的isIntersecting属性添加和删除类。

下面是HTML:

<section>
  <div class="sticky-container">
    <div class="sticky-content">
      <span>&sect;</span>
      <h2>Section 1</h2>
    </div>
  </div>

  {{ content here }}

</section>

复制代码

class为sticky-container的外部div是观察者的目标。 该div将被设置为粘性元素并充当容器。 用于根据粘性状态设置样式和更改元素的元素是class为sticky-content的div及其子元素。 这样能够确保实际的粘性元素始终与根元素顶部缩小的rootMargin接触。

下面是CSS:

.sticky-content {
  position: relative;
  transition: 0.25s;
}

.sticky-content span {
  display: inline-block;
  font-size: 20px;
  opacity: 0;
  overflow: hidden;
  transition: 0.25s;
  width: 0;
}

.sticky-content h2 {
  display: inline-block;
}

.sticky-container {
  position: sticky;
  top: 0;
}

.sticky-container.active .sticky-content {
  background-color: rgba(0, 0, 0, 0.8);
  color: #fff;
  padding: 10px;
}

.sticky-container.active .sticky-content span {
  opacity: 1;
  transition: 0.25s 0.5s;
  width: 20px;
}
复制代码

你会看到.sticky-container在top为0的位置建立了咱们的粘滞元素。 其他部分是.sticky-content中的常规状态和.active .sticky-content中的粘滞状态样式的混合。 一样,您几乎能够在粘性内容div中作任何您想作的事情。 在此demo中,当粘滞状态处于活动状态时,在延迟的过渡中会出现一个隐藏的章节符号。没有Intersection Observer之类的辅助手段,很难达到这种效果。

JavaScript:

const stickyContainers = document.querySelectorAll('.sticky-container');
const io_options = {
  root: document.body,
  rootMargin: '0px 0px -100% 0px',
  threshold: 0
};
const io_observer = new IntersectionObserver(io_callback, io_options);

stickyContainers.forEach(element => {
  io_observer.observe(element);
});

function io_callback (entries, observer) {
  entries.forEach(entry => {
    entry.target.classList.toggle('active', entry.isIntersecting);
  });
}
复制代码

这其实是使用Intersection Observer完成此任务的很是简单的示例。 惟一的例外是rootMargin中的-100%值。 请注意,这对于其余三个方面也能够重复; 它只须要一个具备本身独特的rootMargin的新观察者,对于相应方面,它具备-100%的值。 将会有更多独特的粘性容器,它们具备本身的类,例如sticky-container-topsticky-container-bottom

这样作的限制是,粘性元素的top,right,bottom或left属性必须始终为零。 从技术上讲,你可使用其余值,但随后必须进行数学运算以找出rootMargin的正确值。 这很容易作到,可是若是调整大小,不只须要再次进行数学运算,还必须中止观察者并使用新值从新启动它。 将position属性设置为零,并使用内部元素以所需的方式设置样式更加容易。

和滚动事件结合

到目前为止,咱们已经在一些演示中看到了,intersectionRatio可能不精确且有些局限。使用滚动事件能够更精确,但会下降性能的效率。那把二者结合起来怎么样?

demo4

在此demo中,咱们建立了一个Intersection Observer,而且回调函数的惟一目的是添加和删除侦听根元素上的scroll事件的事件侦听器。 当目标首次进入根元素时,将建立滚动事件侦听器,而后在目标离开根元素时将其删除。 滚动时,输出仅显示每一个事件的时间戳,以实时显示事件的变化-比单独的观察者要精确得多。

下面是JavaScript。

const root = document.querySelector('#root');
const target = document.querySelector('#target');
const output = document.querySelector('#output pre');
const io_options = {
  root: root,
  rootMargin: '0px',
  threshold: 0
};
let io_observer;

function scrollingEvents (e) {
  output.innerText = e.timeStamp;
}

function io_callback (entries) {
  if (entries[0].isIntersecting) {
    root.addEventListener('scroll', scrollingEvents);
  } else {
    root.removeEventListener('scroll', scrollingEvents);
    output.innerText = 0;
  }
}

io_observer = new IntersectionObserver(io_callback, io_options);
io_observer.observe(target);
复制代码

这是一个至关标准的例子。 请注意,咱们但愿阈值为零,由于若是阈值不止一个,咱们将同时得到多个事件监听器。 回调函数是咱们感兴趣的,甚至是一个简单的设置:在if-else块中添加和删除事件监听器。 事件的回调函数仅更新输出中的div。 每当目标触发相交变化而且不与根相交时,咱们会将输出设置回零。

这个实例利用了Intersection Observer和滚动事件的优势。 考虑使用一个滚动动画库,该动画库仅在页面上须要它的部分实际可见时才起做用。 库和滚动事件在整个页面中并不是无效地活动。

浏览器的有趣差别

您可能想知道Intersection Observer有多少浏览器支持。 实际上,还蛮多的!

该浏览器支持数据来自Caniuse,更多信息。 数字表示浏览器支持该版本及更高版本的功能。

Caniuse

全部主要的浏览器都已经支持了一段时间。和预期同样,IE在任何级别都不支持它,可是W3C提供了一个polyfill来解决这个问题。

当我使用Intersection Observer尝试不一样的想法时,我确实遇到了两个示例在Firefox和Chrome之间的行为有所不一样。我不会在生产站点上使用这些示例,可是这些行为颇有趣。

这是第一个示例:

example1

目标元素经过CSS transform属性在根元素内移动。 该演示具备CSS动画,该动画可在水平轴上将目标元素移入和移出根元素。 当目标元素进入或离开根元素时,intersectionRatio会更新。

若是您在Firefox中查看此演示,则应在目标元素先后滑动时正确地看到intersectionRatio更新。 Chrome的行为有所不一样,彻底不更新intersectionRatio。 Chrome彷佛没有保留使用CSS转换过的目标元素的标签。 可是,若是咱们在目标元素移入和移出根元素时在浏览器中四处移动鼠标,则intersectionRatio确实会更新。 个人猜想是,只有在存在某种形式的用户交互时,Chrome才会“激活”观察者。

这是第二个示例:

example2

此次,咱们对一个剪裁路径进行动画处理,该剪裁路径将一个正方形变成重复循环中的一个圆形。正方形与根元素的大小相同,所以咱们获得的intersectionRatio将始终小于1。随着剪裁路径的动画化,Firefox根本不会更新intersectionRatio。此次移动鼠标不起做用。Firefox只是忽略元素大小的变化。另外一方面,Chrome实际上会实时更新intersectionRatio显示。即便没有用户交互,也会发生这种状况。

之因此会发生这种状况,是由于规范的一部分指出交集区域(intersectionRect)的边界应包括剪裁目标元素。

若是容器具备溢出剪裁或css剪裁路径属性,请经过应用容器的剪裁来更新intersectionRect。

所以,当剪裁目标时,将从新计算相交区域的边界。 Firefox显然还没有实现。

Intersection Observer, version 2

那么,该API的将来前景如何?

Google提供了一些建议,这些建议会为观察者添加一个有趣的功能。 即便Intersection Observer告诉咱们目标元素什么时候跨越根元素的边界,也不必定意味着该元素实际上对用户是可见的。 它可能具备零不透明度,或者可能被页面上的另外一个元素覆盖。 观察者能不能被用来肯定这些事情?

请记住,咱们仍在早期阶段才使用此功能,所以不该在生产代码中使用它。 这是更新后的提案,其中突出显示了与规范第一个版本的差别。

若是您一直在使用Chrome浏览本文中的演示,则可能已经注意到控制台中的几件事-例如Firefox中未出现的entries对象属性。 这是Firefox在控制台中打印内容的示例:

IntersectionObserver
  root: null
  rootMargin: "0px 0px 0px 0px"
  thresholds: Array [ 0 ]
  <prototype>: IntersectionObserverPrototype { }

IntersectionObserverEntry
  boundingClientRect: DOMRect { x: 9, y: 779, width: 707, ... }
  intersectionRatio: 0
  intersectionRect: DOMRect { x: 0, y: 0, width: 0, ... }
  isIntersecting: false
  rootBounds: null
  target: <div class="item">
  time: 261
  <prototype>: IntersectionObserverEntryPrototype { }

复制代码

如今,这是来自Chrome中相同控制台代码的输出:

IntersectionObserver
delay: 500
root: null
rootMargin: "0px 0px 0px 0px"
thresholds: [0]
trackVisibility: true
__proto__: IntersectionObserver

IntersectionObserverEntry
boundingClientRect: DOMRectReadOnly {x: 9, y: 740, width: 914, height: 146, top: 740, ...}
intersectionRatio: 0
intersectionRect: DOMRectReadOnly {x: 0, y: 0, width: 0, height: 0, top: 0, ...}
isIntersecting: false
isVisible: false
rootBounds: null
target: div.item
time: 355.6550000066636
__proto__: IntersectionObserverEntry

复制代码

在一些属性(例如targetprototype)的显示方式上存在一些差别,可是它们在两种浏览器中的操做相同。区别在于Chrome具备Firefox中不会显示的一些其余属性。observer对象具备一个称为trackVisibility的布尔值,一个称为delay的数字,而且entry对象具备一个称为isVisible的布尔值。这些是新提议的属性,这些属性试图肯定目标元素是否实际上对用户可见。

我将对这些属性进行简要说明,但若是您须要更多详细信息,请阅读此文章

trackVisibility属性是在options对象中提供给观察者的布尔值。此属性可使浏览器承担肯定目标元素的真实可见性的任务。

delay属性用途的猜想:它将交集改变的回调方法延迟指定的时间(以毫秒为单位)。这有点相似于将回调函数的代码包装在setTimeout中。为了使trackVisibility起做用,该值是必需的,而且必须至少为100。若是未提供适当的值,则控制台将显示此错误,而且将不会建立观察者。

Uncaught DOMException: Failed to construct 'IntersectionObserver': To enable the
'trackVisibility' option, you must also use a 'delay' option with a value of at
least 100. Visibility is more expensive to compute than the basic intersection;
enabling this option may negatively affect your page's performance. Please make sure you really need visibility tracking before enabling the 'trackVisibility' option. 复制代码

目标entry对象中的isVisible属性是报告可见性跟踪输出的布尔值。能够将它用做任何代码的一部分,就像使用isIntersecting同样。

在我使用这些功能进行的全部实验中,看到它实际上有时候有效有时候无效。 例如,当元素清晰可见时,延迟始终有效,可是isVisible并不老是报告true(至少对我而言)。 有时这是设计使然,由于规范确实容许出现第二类错误。这将有助于解释不一致的结果。

我我的火烧眉毛地但愿这项功能尽快完成,并在全部支持Intersection Observer的浏览器中都能正常工做。

写在最后

我对Intersection Observer的研究到此结束。 我花了不少晚上研究,试验和构建示例,以了解其工做原理。 这篇文章涉及了一些有关如何利用观察者的不一样功能的新想法。除此以外,我以为我能够清晰的解释观察者的工做原理。但愿本文对你有所帮助。


若是你以为这篇内容对你有价值,请点赞,并关注咱们的官网和咱们的微信公众号(WecTeam),每周都有优质文章推送:

WecTeam
相关文章
相关标签/搜索