IntersectionObserver是什么

50次阅读

共计 8248 个字符,预计需要花费 21 分钟才能阅读完成。

  • IntersectionObserver 概览
  • IntersectionObserver 构造器
  • IntersectionObserver 方法
  • IntersectionObserver 懒加载(vue 单文件组件版)
  • IntersectionObserver 吸顶(vue 单文件组件版)
  • IntersectionObserver 触底(vue 单文件组件版)
  • IntersectionObserver 懒加载、吸顶、触底综合(vue 单文件组件版)
  • 基于 Intersection 的开源工具 Scrollama
  • 总结
  • 参考资料

IntersectionObserver 概览

  • IntersectionObserver 提供了一个方式去异步观察 有一个祖先 element 或者 top-level document viewport 的目标 element 的交叉变化。
  • 祖先元素或者 viewport 被当做 root 节点。
  • 当 IntersectionObserver 创建出的时候,它会被配置到监听 root 内部的给定 visibility 的变化。
  • 一旦 IntersectionObserver 创建出来,它的配置是不能变的。 所以一个 observer object 只能用来监测一个指定 visibility 值的变化。
  • 虽然只能一对一去 watch ratio,但是 可以在同一个 observer 中 watch 多个 target elements。也就是说一个 visibility ratio 可以检测多个不同的 elements。

IntersectionObserver 构造器

var observer = new IntersectionObserver(callback[, options]);

  • 和其他构造器一样,创建并返回一个新的 Intersection 对象。
  • rootMargin 如果指定一个特殊值,是为了 确保语法是否正确
  • 阀值是为了确保值在 0.0 到 1.0 之间,threshold 会按照升序排列。若 threshold 是空,值为[0.0]。

参数

  • callback 当目标元素的透明度穿过设定的 threshold 值时,函数会被调用。callback 接受两个 参数。

    • entries 传入各个 threshold 值的数组,比该阀值指定的百分比更明显或者更不明显。
    • observer 调用 callback 的 observer 实例。
  • options 若 options 没设置。observer 使用 document 的 viewport 作为 root,没有 margin,0% 的 threshold(意味着即使有 1 px 的变化也会触发回调)

    • root 被当做 viewport 的元素。
    • rootMargin 语法是 ”0px 0px 0px 0px”
    • threshold 指明被监测目标总绑定盒模型的交叉区域 ratio,值在 0.0 到 1.0 之间;0.0 意味着即使是 1px 也会被当做可见的。1.0 意味着整个元素是可见的。默认 threshold 值是 0.0。

IntersectionObserver 方法

  • IntersectionObserver.disconnect() 停止 observe 一个目标。
  • IntersectionObserver.observe() 告诉 IntersectionObserver 一目标元素去 observe。
  • IntersectionObserver.takeRecords() 返回包含所有 observe 的对象一个数组。
  • IntersectionObserver.unobserve() 取消 observe 一个目标对象。

示例

下面的例子在 threshold 值变化在 10% 以上时触发 myObserverCallback。

let observer = new IntersectionObserver(myObserverCallback, { "threshold": 0.1});

IntersectionObserver 懒加载(vue 单文件组件版)

<template>
  <div>
    <img v-for="(image, i) in images" :key="i" src :data-img-url="image" />
  </div>
</template>

<script>
export default {data() {
    return {
      images: [
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247890587&di=88d4066be3d57ac962a6bec37e265d37&imgtype=0&src=http%3A%2F%2F01.imgmini.eastday.com%2Fmobile%2F20170810%2F20170810151144_d41d8cd98f00b204e9800998ecf8427e_3.jpeg',
        'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4054762707,1853885380&fm=26&gp=0.jpg',
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247912077&di=508a949e5e291875debf6ca844292cd4&imgtype=0&src=http%3A%2F%2F03imgmini.eastday.com%2Fmobile%2F20180827%2F20180827095359_6759372e9bd28026ee6f53b500fb4291_2.jpeg',
      ],
    };
  },
  mounted() {const images = document.querySelectorAll('img');

    const observerLazyLoad = new IntersectionObserver((entries) => {entries.forEach((item) => {if (item.isIntersecting) {item.target.src = item.target.dataset.imgUrl;}
      });
    });

    images.forEach((image) => {observerLazyLoad.observe(image);
    });
  },
};
</script>

<style lang="scss" scoped>
img {
  display: block;
  height: 500px;
  margin: 30px;
}
</style>

IntersectionObserver 吸顶(vue 单文件组件版)

<template>
  <div>
    <p class="fixed-top-helper"></p>
    <p class="fixed-top-reference"></p>
    <header> 头部 </header>
    <main>
      <img v-for="(image, i) in images" :key="i" :src="image" />
    </main>
  </div>
</template>

<script>
export default {data() {
    return {
      images: [
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247890587&di=88d4066be3d57ac962a6bec37e265d37&imgtype=0&src=http%3A%2F%2F01.imgmini.eastday.com%2Fmobile%2F20170810%2F20170810151144_d41d8cd98f00b204e9800998ecf8427e_3.jpeg',
        'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4054762707,1853885380&fm=26&gp=0.jpg',
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247912077&di=508a949e5e291875debf6ca844292cd4&imgtype=0&src=http%3A%2F%2F03imgmini.eastday.com%2Fmobile%2F20180827%2F20180827095359_6759372e9bd28026ee6f53b500fb4291_2.jpeg',
      ],
    };
  },
  mounted() {const header = document.querySelector('header');
    const fixedTopReference = document.querySelector('.fixed-top-reference');
    fixedTopReference.style.top = `${header.offsetTop}px`;

    const observerFixedTop = new IntersectionObserver((entries) => {entries.forEach((item) => {if (item.boundingClientRect.top < 0) {header.classList.add('fixed');
        } else {header.classList.remove('fixed');
        }
      });
    });
    observerFixedTop.observe(fixedTopReference);
  },
};
</script>

<style lang="scss" scoped>
.fixed-top-helper {
  height: 1px;
  background: #ccc;
}
header {
  background: #ccc;
  &.fixed {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
  }
}
main {
  img {
    display: block;
    height: 500px;
    margin: 30px;
  }
}
footer {background: #ccc;}
</style>

注意事项:

  • fixedTopReference 是为了避免缓慢移动时 add remove .fixed 死循环,死循环的结果是抖动
  • fixedTopHelper 是为了避免被吸顶元素没有上一个 sibling 元素(也就是说被吸顶元素是最上层元素)时,避免缓缓移动时 add remove .fixed 死循环抖动,特殊引入的标签,需要设置 1 个 px 的 height
  • fixedTopHelper 需要与被吸顶元素保持样式一致,以确保好的用户体验。例如在本例中将其 background 设置为 #ccc,很好的做到了隐藏

吸顶抖动

IntersectionObserver 触底(vue 单文件组件版)

<template>
  <div>
    <main>
      <img v-for="(image, i) in images" :key="i" src="image" />
    </main>
    <footer> 底部 </footer>
  </div>
</template>

<script>
export default {data() {
    return {
      images: [
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247890587&di=88d4066be3d57ac962a6bec37e265d37&imgtype=0&src=http%3A%2F%2F01.imgmini.eastday.com%2Fmobile%2F20170810%2F20170810151144_d41d8cd98f00b204e9800998ecf8427e_3.jpeg',
        'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4054762707,1853885380&fm=26&gp=0.jpg',
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247912077&di=508a949e5e291875debf6ca844292cd4&imgtype=0&src=http%3A%2F%2F03imgmini.eastday.com%2Fmobile%2F20180827%2F20180827095359_6759372e9bd28026ee6f53b500fb4291_2.jpeg',
      ],
    };
  },
  mounted() {const footer = document.querySelector('footer');

    const observerTouchBottom = new IntersectionObserver((entries) => {entries.forEach((item) => {if (item.isIntersecting) {setTimeout(() => {console.log('滚动到了底部,可以发 request 请求数据了');
          }, 2000);
        }
      });
    });

    observerTouchBottom.observe(footer);
  },
};
</script>

<style lang="scss" scoped>
main {
  img {
    display: block;
    height: 500px;
    margin: 30px;
  }
}
footer {background: #ccc;}
</style>

IntersectionObserver 懒加载、吸顶、触底综合(vue 单文件组件版)

<template>
  <div>
    <p class="fixed-top-helper"></p>
    <p class="fixed-top-reference"></p>

    <header> 头部 </header>
    <main>
      <img v-for="(image, i) in images" :key="i" src :data-img-url="image" />
    </main>
    <footer> 底部 </footer>
  </div>
</template>

<script>
export default {data() {
    return {
      images: [
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247890587&di=88d4066be3d57ac962a6bec37e265d37&imgtype=0&src=http%3A%2F%2F01.imgmini.eastday.com%2Fmobile%2F20170810%2F20170810151144_d41d8cd98f00b204e9800998ecf8427e_3.jpeg',
        'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4054762707,1853885380&fm=26&gp=0.jpg',
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1574247912077&di=508a949e5e291875debf6ca844292cd4&imgtype=0&src=http%3A%2F%2F03imgmini.eastday.com%2Fmobile%2F20180827%2F20180827095359_6759372e9bd28026ee6f53b500fb4291_2.jpeg',
      ],
    };
  },
  mounted() {const images = document.querySelectorAll('img');

    const observerLazyLoad = new IntersectionObserver((entries) => {entries.forEach((item) => {if (item.isIntersecting) {item.target.src = item.target.dataset.imgUrl;}
      });
    });

    images.forEach((image) => {observerLazyLoad.observe(image);
    });

    const footer = document.querySelector('footer');

    const observerTouchBottom = new IntersectionObserver((entries) => {entries.forEach((item) => {if (item.isIntersecting) {setTimeout(() => {console.log('滚动到了底部,可以发 request 请求数据了');
          }, 2000);
        }
      });
    });

    observerTouchBottom.observe(footer);

    const header = document.querySelector('header');
    const fixedTopReference = document.querySelector('.fixed-top-reference');
    fixedTopReference.style.top = `${header.offsetTop}px`;

    const observerFixedTop = new IntersectionObserver((entries) => {entries.forEach((item) => {if (item.boundingClientRect.top < 0) {header.classList.add('fixed');
        } else {header.classList.remove('fixed');
        }
      });
    });

    observerFixedTop.observe(fixedTopReference);
  },
};
</script>

<style lang="scss" scoped>
.fixed-top-helper {
  height: 1px;
  background: #ccc;
}
header {
  background: #ccc;
  &.fixed {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
  }
}
main {
  img {
    display: block;
    height: 500px;
    margin: 30px;
  }
}
footer {background: #ccc;}
</style>

基于 Intersection 的开源工具 Scrollama

官方提供了 Basic Process,Progress,Sticky Side,Sticky Overlay 几种示例。

Basic Process

Progress

Sticky Side

Sticky Overlay

在项目中可以当做适当引用。

项目地址:https://github.com/russellgol…
demo 地址:https://russellgoldenberg.git…

总结

  • 主要使用 IntersectionObserver 实现懒加载图片,触底,吸顶
  • 虽然 vue 单文件组件版本,但是我相信聪明的你知道核心部分在哪里
  • IntersectionObserver 可能还会有其他的用处,来日方长,慢慢探索

参考资料

https://developer.mozilla.org…
https://developer.mozilla.org…
https://developer.mozilla.org…
https://juejin.im/post/5ca15c…
https://medium.com/walmartlab…
https://juejin.im/post/5d6651…
https://github.com/russellgol…

原文地址:IntersectionObserver 是什么?

正文完
 0