Intersection Observer实现锚点定位

994 阅读8分钟

锚点定位

1. href

缺点:点击锚点 浏览器url变化

<h2>  
    <a href="#div1">to div1</a>  
    <a href="#div2">to div2</a>  
    <a href="#div3">to div3</a>  
</h2>  
    <div id="div1">div1</div>  
    <div id="div2">div2</div>  
    <div id="div3">div3</div>  

2. window.location.hash方法

缺点:地址变化

3. scrollIntoView方法 推荐

document.getElementById("divId").scrollIntoView();

比如:

document.querySelector("#roll1").onclick = function(){
     document.querySelector("#roll1_top").scrollIntoView(true);
}  
// 参数 true 元素的顶端将和其所在滚动区的可视区域的顶端对齐。
// 参数 false 元素的底端将和其所在滚动区的可视区域的底端对齐

参数

  • alignToTop(可选)

    • 值为true,元素的顶端将和其所在滚动区的可视区域的顶端对齐
    • 值为false,元素的底端将和其所在滚动区的可视区域的底端对齐

    scrollIntoViewOptions(可选) 该参数为对象形式

    • behavior(可选):定义动画过渡效果, 值为 autosmooth 之一。默认为 auto
    • block(可选):定义垂直方向的对齐, 值为 start, center, end, 或 nearest之一。默认为 start
    • inline(可选):定义水平方向的对齐, 值为 start, center, end, 或 nearest之一。默认为 nearest

优点: URL不会变,同时能够响应相应的scroll事件

缺点:

  • 兼容性问题 不支持ios(可以使用scrollIntoView结合smoothscroll-polyfill插件实现 ios平滑滚动)

image.png

Demo

codepen.io/wang9977/pe…

平滑滚动 CSS属性 scroll-behavior

MDN介绍:当用户手动导航 或者CSSOM scrolling API 出发滚动操作时,scroll- behavior 为一个滚动框指定滚动行为 ,其他滚动不受影响。在根元素中指定这个属性时,它反而适用于视窗。

该属性有2个值可选

  • auto : 滚动框立即滚动,即默认效果,没有平滑滚动效果
  • smooth : 有平滑滚动效过

缺点:兼容性。不支持IE

例子 新打开一个窗口 查找 或者代码

锚点高亮怎么做?

Intersection Observer

Intersection Observer API 提供了一种异步观察目标元素与祖先元素或顶级文档视口 viewport 的交集变化的方法。 这个API叫做交叉观察器。它常用于追踪一个元素在窗口的可视问题,比如下图,滚动页面,顶部会提示绿色方块的可视性。

使用场景

  • 图片懒加载 - 图片滚动到可见时 再加载
  • 内容无限滚动
  • 检测广告的曝光情况 --为了计算广告收益,需要知道广告元素的曝光情况
  • 在用户看见某个区域时。执行任务-或者播放动画

传统位置计算的方式,依赖于对 DOM 状态的轮询计算,然而这种方式会在主线程里密集执行从而造成页面性能问题

getBoundingClientRect() 的频繁调用也可能引发浏览器的样式重计算和布局。如果是在 iframe 里,因为同源策略,我们不能直接访问元素,也就很难用传统方式去处理 iframe 里的元素。

Intersection Observer 的设计,就是为了更方便的处理元素的可视问题。使用 Intersection Observer 我们可以很容易的监控元素进入和离开可视窗口,实现节点的预加载和延迟加载。Intersection Observer 并不是基于像素变化的实时计算,它的反馈会有一定的延时,这种异步的方式减少了对 DOM 和 style 查询的昂贵计算和持续轮询,相比传统方式降低了 CPU、GPU 的消耗。

痛点:

需要频繁使用事件监听,频繁调用,Element.getBoundingClientRect() 方法以获取相关元素的边界信息,在主线程调用,事件会造成性能问题。

假如有一个无限滚动的网页,开发者使用了一个第三方库来管理整个页面的广告,又用了另外一个库来实现消息盒子和点赞,并且页面有很多动画(动画往往意味着较高的性能消耗)。两个库都有自己的相交检测程序,都运行在主线程里,而网站的开发者对这些库的内部实现知之甚少,所以并未意识到有什么问题。但当用户滚动页面时,这些相交检测程序就会在页面滚动回调函数里不停触发调用,造成性能问题,用户体验差。

原理:

注册一个回调函数,每当被监听的元素进入或退出另一个元素时,或者两个元素的相交部分发生变化时,该回调方法会被触发执行。

⚠️注意 Intersection Observer API 无法提供重叠的像素个数或者具体哪个像素重叠,更常见的使用方式是——当两个元素相交比例在 N% 左右时,触发回调,以执行某些逻辑。

一旦IntersectionObserver被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。

概念及用法

概念

  1. 配置一个回调函数,触发
    1. 每当目标元素target 与 视窗(或其他指定元素)发生交集的时候执行。

    2. Observer 第一次监听目标元素的时候

用法

创建一个intersectionObserver对象

创建一个 IntersectionObserver 对象,并传入相应参数和回调用函数,该回调函数将会在目标(target)元素和根(root)元素的交集大小超过阈值(threshold)规定的大小时候被执行。

let options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 1.0
}

let observer = new IntersectionObserver(callback, options);

阈值为1.0意味着目标元素完全出现在 root 选项指定的元素中可见时,回调函数将会被执行。

intersectionObserver 对象参数

callback回调函数

当元素可见比例超过指定阈值后,会调用一此回调函数,这个回调函数有两个参数enters和observer

entries: 一个IntersectionObserverEntry对象的数组。

observer: 被调用的IntersectionObserver实例

entries

let callback = (entries, observer) => {
  entries.forEach(entry => {
    // 每个entry都描述了一个观察到的目标元素交集的变化 
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

IntersectionObserverEntry对象。

  • boundingClientRect 返回包含目标元素(被检测的元素)的边界信息的DOMRectReadOnly. 边界的计算方式与 Element.getBoundingClientRect() 相同.

DOMRect 示例图![image-20220328155207881](/Users/wangyuan198/Library/Application Support/typora-user-images/image-20220328155207881.png)

  • intersectionRatio 返回intersectionRectboundingClientRect 的比例值.

    一个介于 0.0 和 1.0 之间的数字,表示在根的相交矩形内实际可见的目标元素有多少。更准确地说,该值是相交矩形 (intersectionRect) 的面积与目标边界矩形 (boundingClientRect) 的面积之比。
    
  • intersectionRect 目标元素与视口(或根元素)的交叉区域的信息

  • isIntersecting 布尔值

    返回一个布尔值, 如果目标元素与交叉区域观察者对象的根相交,则返回 true .不相交返回false

  • rootBounds 返回一个 DOMRectReadOnly 用来描述交叉区域观察者(intersection observer)中的根.

    • 根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
  • target 被检测的元素(目标元素),是一个 DOM 节点对象

  • time。返回一个记录从 IntersectionObserver 的时间原点(time origin)到交叉被触发的时间的时间戳

options对象

  • root

    • 指定根元素,必须是target元素的父级元素

    • 未指定或为null 则默认浏览器视窗

    • 顶级文档视窗 viewport

      ⚠️ 视口当前可见的部分叫做 可视视口 可视视口比布局视口更小,缩小缩放时,布局视口不变,可视视口变小

  • rootMargin

    • 根元素的外边距 ,可以是百分比
    • 用作 root元素和target元素发生交集的区域范围,使用可以控制root元素每一边的收缩和扩张
    • 默认0px
  • threshold

    • 可以是number。也可以是number Array

    • target 元素和 root 元素相交程度达到该值的时候 IntersectionObserver 注册的回调函数将会被执行。

    • 默认值是0 (意味着只要有一个 target 像素出现在 root 元素中,回调函数将会被执行)。

    • 该值为1.0含义是当 target 完全出现在 root 元素中时候 回调才会被执行。

![image-20220408180120281](/Users/wangyuan198/Library/Application Support/typora-user-images/image-20220408180120281.png)

方法

方法说明
observe()开始监听一个目标元素
unobserve()停止监听特定目标元素
takeRecords()返回所有观察目标的IntersectionObserverEntry对象数组
disconnect()使IntersectionObserver对象停止全部监听工作
// 开始观察
io.observe(document.getElementById('example'));

// 停止观察
io.unobserve(element);

// 关闭观察器
io.disconnect();

兼容性

总结

总体来说IntersectionObserver在检测元素进入视口区域来说提供了极大的便利性。但是在使用的时候需要考虑兼容性(可以使用intersection-observer-polyfill解决)

⚠️建议设置的观察视口是上一级的可滚动的元素,如果root是视口而且上一级有滚动元素的时候会有一些问题

引用