zoukankan      html  css  js  c++  java
  • Intersection Observer API

    Intersection Observer API

    随着 Web 应用的丰富和成熟,检测元素是否可见的需求增多。之前一般是通过三方库来实现,各自有各自的实现方式,性能也有差异。Intersection Observer API 便是这种功能的一个原生支持。

    适用场景

    • 页面滚动过程中的懒加载。
    • 长页面无限内容加载。
    • 页面中广告查看量的计算。
    • 以及其他需要用户可见才进行的操作,动画等。

    用法及参数

    初始化监视器

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

    使用该 API,有两个角色会参与进来,

    • 容器(root or root element):缺省或传递 null 时为文档视窗 viewport,否则是指定的元素。
    • 目标元素(target):需要监视(observe)的目标元素。

    API 会在目标元素可见,即出现在容器中时执行指定的回调。

    但回调也不是都执行,它受参数 threshold 的控制。

    所以还需要理解一个概念,Intersection Ratio,可理解成目标元素的身体出现在容器中多少时,才触发回调,进一步理解请看下图:

    Intersection Ratio 示例

    Intersection Ratio 示例 -- 图片来自 Arnelle Balane 的文章 The Intersection Observer API

    上面 threshold 就是指定 Intersection Ratio 的。它是一个介于 0~1 的小数,默认为 0 ,即一旦可见就执行,1 则表示元素全部可见才执行。

    除了将 threshold 指定为数字,还可指定为一个数字的数组,比如 [0, 0.25, 0.5, 0.75, 1],这样在元素出现过程中,其可见范围分别为 0, 25%,50%,75%, 100% 时都会触发一次回调。

    rootMargin 则用于指定容器的边距,接收的值和 CSS margin 一样。这个边距会在计算目标可见范围时被考虑进去,即在容器原有的内容范围内,减去 margin 的这部分。

    执行监视

    利用构造器 IntersectionObserver 创建好一个监视器后,便可以对容器内的元素进行监视了。

    let target = document.querySelector('#listItem');
    observer.observe(target);

    回调

    回调的结构如下:

    let callback = (entries, observer) => { 
      entries.forEach(entry => {
        // Each entry describes an intersection change for one observed
        // target element:
        //   entry.boundingClientRect
        //   entry.intersectionRatio
        //   entry.intersectionRect
        //   entry.isIntersecting
        //   entry.rootBounds
        //   entry.target
        //   entry.time
      });
    };

    回调的入参为一个 IntersectionObserverEntry 数组,同时还有监视器本身 observer

    注意:虽然监测器的监听是异步的,但回调的执行是在主线程,所以回调中不宜进行耗时任务,如果确实需要应该使用 Window.requestIdleCallback() 来执行。

    一个完整的示例

    实现一个页面无限滚动及图片懒加载。完整代码可在 wayou/intersection-observer-api 查看。

    App.tsx
    import React, { useState, useEffect, useCallback } from "react";
    import "./App.css";
    
    const IMAGE_API = "https://picsum.photos/200";
    const PAGE_SIZE = 10;
    const IMAGES_PAGED = new Array(PAGE_SIZE).fill(IMAGE_API);
    
    let loaderObserver: IntersectionObserver;
    let lazyLoadObserver: IntersectionObserver;
    let options = {
      rootMargin: "0px",
      threshold: 0
    };
    let page = 0;
    
    const App = () => {
      const [images, setImages] = useState<string[]>(IMAGES_PAGED);
    
      const infiniteCallback = useCallback<IntersectionObserverCallback>(
        (_entries, _observer) => {
          console.info(`loading page ${++page}`);
          setImages(prev => [...prev, ...IMAGES_PAGED]);
        },
        []
      );
    
      const lazyLoadCallback = useCallback<IntersectionObserverCallback>(
        (entries, observer) => {
          entries.forEach(entry => {
            if (entry.isIntersecting) {
              // stop observer and load the image
              const lazyImage = entry.target as HTMLImageElement;
              console.log("lazy loading ", lazyImage);
              lazyImage.classList.remove("empty");
              lazyImage.src = lazyImage.dataset.src!;
              observer.unobserve(entry.target);
            }
          });
        },
        []
      );
    
      //setup  infinite loading
      useEffect(() => {
        if (!loaderObserver) {
          loaderObserver = new IntersectionObserver(infiniteCallback, options);
        }
        const target = document.querySelector("footer");
        if (target) {
          loaderObserver.observe(target);
        }
        return () => {
          if (target) {
            loaderObserver.unobserve(target);
          }
        };
      }, [infiniteCallback]);
    
      // setup lazy loading
      useEffect(() => {
        if (!lazyLoadObserver) {
          lazyLoadObserver = new IntersectionObserver(lazyLoadCallback, options);
        }
        const imgs = document.querySelectorAll("img.empty");
        if (imgs) {
          imgs.forEach(img => {
            console.count("setup img observer");
            lazyLoadObserver.observe(img);
          });
        }
        return () => {
          if (imgs) {
            imgs.forEach(img => {
              lazyLoadObserver.unobserve(img);
            });
          }
        };
      }, [lazyLoadCallback, images]);
    
      return (
        <div className="App">
          <div className="content-wrap">
            {images.map((image, index) => (
              <img className="empty" key={index} data-src={image + `?f=${index}`} />
            ))}
          </div>
          <footer>loading more ....</footer>
        </div>
      );
    };
    
    export default App;

    运行效果:

    利用 Intersection Observer API 实现的无限图片懒加载运行效果

    利用 Intersection Observer API 实现的无限图片懒加载运行效果

    兼容性

    虽然是实验性 API,但支持情况还可以。根据 Can I Use #intersectionobserver 的数据,PC 端除 IE 外主流浏览器均已支持,移动端 UC 部分支持,其余浏览器支持良好。

    相关资源

    The text was updated successfully, but these errors were encountered:

    CC BY-NC-SA 署名-非商业性使用-相同方式共享
  • 相关阅读:
    缩放图片
    Volley下载图片存放在data/data下 networkImageView lrucache
    类实现Parcelable接口在Intent中传递
    基本控件设置边角图片 drawableleft
    屏幕全屏之类的问题
    关于点击按钮分享
    万能适配器的一些问题
    自定义控件高级
    Fragment 生命周期 全局变量的声明位置
    GridView
  • 原文地址:https://www.cnblogs.com/Wayou/p/14870820.html
Copyright © 2011-2022 走看看