• DOM – IntersectionObserver


    介绍

    IntersectionObserver 的作用是监听某个元素是否出现在框内 (比如 viewport).

    它可以实现 lazy load image, 一开始图片是没有加载的, 当图片出现在 viewport 时才去加载.

    也可以用来做 tracking, 比如某个商品 (card 元素), 是否出现在 viewport, 这样就可以检测用户是否看了某个商品.

    效果

    参考:

    IntersectionObserver’s Coming into View

    Medium – IntersectionObserver’s coming into view

    Trust is good, observation is better: Intersection Observer v2 (v2, 但是目前只有 chrome 99 支持, 所以这篇先不介绍了)

    getBoundingClientRect

    参考: getBoundingClientRect() 详解

    先了解一下 rect, 通过 bounding client rect 可以得知一个元素, 对标 viewport 的位置坐标. 它不管 scroll bar 有点像 position fixed 对标 viewport.

    最常使用的就是 x, y, width, height, 其它算是冗余吧. 

    在 IntersectionObserver 诞生以前, 要模拟它的效果就得监听 scroll 然后通过调用 bounding client 来判断 2 个元素是否交会.

    场景

    先搭建一个简单的场景. 方便解释

    <div class="container">
      <div class="box">box1</div>
      <div class="box">box2</div>
      <div class="box">box3</div>
      <div class="box">box4</div>
      <div class="box">box5</div>
      <div class="box">box6</div>
      <div class="box">box7</div>
      <div class="box">box8</div>
      <div class="box">box9</div>
      <div class="box">box10</div>
    </div>

    1 个 container 包着 10 个 box

    CSS Style

    .container {
      margin-top: 100px;
      margin-inline: auto;
      width: fit-content;
      .box {
        border: 1px solid red;
        width: 100px;
        height: 100px;
      }
    }

    效果

    new IntersectionObserver()

    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          console.log(entry);
        });
      },
      {
        root: document,
        rootMargin: "0px",
        threshold: [0],
      }
    );

    首先看看它的调用方式.

    new 一个 IntersectionObserver 实例.

    第 1 个参数是触发时的回调. 不关心这个先.

    第 2 个参数是一个 config.

    root 指的是框, 一旦观察元素出现在框内就会触发回调. 默认是 viewport 也就是 document, 也可以设置成任何一个 element (通常是 scrollable 的).

    rootMargin 指的是 extra 的范围, 如同框变大了. 比如说, 不想等到 element 出现在框内了才触发, 想提早, 那可以写 rootMargin: 50% (也可以写 px, percentage 对应框的大小)

    所以它会提早半个屏幕就触发. 一般上 lazy load 图片都会提早, 而不是等到用户已经看见 img element 了才去 load, 这样就慢掉了.

    threshold 是一个元素显示多少 % 时要触发, 比如

    threshold: [0.1, 0.5, 1], 观察元素是 box4

    分别会在这 3 个阶段触发回调. 比较常用的方式是 [0, 1], 刚出现时触发一次, 完整出现时触发一次.

    注意:

    1. 如果被观察的元素 height 超过框的 height, 那意味着永远不会出现 100% 显示. 那么 1 就不出触发了.

    2. 只要元素出现在框内就会触发 (哪怕 viewport 看不见), 看下面的例子:

    root 是 container 框 (不是 viewport 哦), click button 会 scroll container. 当把 viewport 移开以后, 点击 button 依然触发了. 因为元素显示在 container 框了. 

    它不需要出现在 viewport 的框.

    3. 不仅仅是 scroll 无论以何种方式出现在框内都会触发, 比如 transform translate 也会触发的.

    observe, unobserve

    observe

    把框定义好以后, 就开始放入要观察的元素.

    const box9 = document.querySelectorAll(".box")[8];
    io.observe(box9);

    注意:

    1. 触发频率, 它不是立马触发的 (性能考量), 可能会有几毫秒的微差, 比如监听的是 0.5 (50% 显示), 但触发的时候是 0.52 (intersectionRatio).

    2. observe 调用后, 不管元素是否出现在框内, 它都会触发第一次.

    unobserve, disconnect

    io.unobserve(box9)
    io.disconnect()

    不想监听了, 可以调用 unobserve, 或者把整个 io disconnect 掉 (没有 reconnect 功能的哦)

    Callback Info

    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          console.log(entry);
          // entry.target;
          // entry.boundingClientRect;
          // entry.intersectionRect;
          // entry.isIntersecting;
          // entry.time;
          // entry.intersectionRatio;
          // entry.rootBounds
        });
      }
    );

    当元素显示时, 它就会触发 callback, entries 就是这一轮所以触发的 element (不是所有监听的 element 哦, 不关事的不会在 entries 里)

    出于性能原因, 即使 element 是先后出现在框的, 但也可能被放到同一轮触发列表内.

    target = 被观察的元素.

    boundingClientRect = 被观察元素的 getBoundingClientRect()

    intersectionRatio = 多少 percentage 显示, 0.52 = 52% 显示在框内

    isIntersecting = 元素是否显示

    当 ratio = 0 的时候, 有可能是刚出现, 也可能是刚离开. 通过 isIntersecting 来判断.

    rootBounds = 框 element 的 getBoundingClientRect()

    intersectionRect 

    它和 boundingClientRect 差不多, 微差体现在:

    区别在于它的 height 和 bottom, bottom 是冗余, 我们看 height 就好了.

    100px 是整个 box9 的高, intersectionRect 是 41px, 因为 threshold 是 0.4, 40% 显示时触发. 

    所以 intersection 的 height 拿的不是完整的 box9 height, 而是已经显示的 box9 height. 所以就是大约 40px 了.

    height 不同, bottom 自然也就不同了. 因为 bottom = y + height.

    time = IO 有一个计时器, 从 new IO 后开始跑 start from 0ms.

    每当触发的时候它就去截取当前的 time, 比如 5000 表示从 new IO 到这个元素触发已经过了 5 秒了. 它会一直跑, 不会 reset 的.

    通过时间差就可以判断用户是否停留看着某个元素多久了. 具体的需求可能是, 想 tracking 某个 page section 用户是否游览. 但是那种一秒涮过的不算.

    这时候就可以看 intersect in/out 的时间差, 来判断是否用户是慢慢划过的.

  • 相关阅读:
    Flutter 步骤进度组件
    程序员到底要不要学习框架、库和工具
    Flutter 吐血整理组件继承关系图
    超过百万的StackOverflow Flutter 问题
    Flutter 实现网易云音乐字幕
    Flutter 实现虎牙/斗鱼 弹幕效果
    Flutter AbsorbPointer 与 IgnorePointer的区别
    《Flutter 小技巧》一行禁用App,一行置灰App,致敬
    Python 任务自动化工具:nox 的配置与 API
    Flask 作者 Armin Ronacher:我不觉得有异步压力
  • 原文地址:https://www.cnblogs.com/keatkeat/p/15993834.html
Copyright © 2020-2023  润新知