Skip to content

interSectionObserver实践 #34

Open
@keep-run

Description

@keep-run

简介

interSection Observer 提供了一种异步检测目标元素与指定祖先元素相交情况变化的方法;基于这个API,最常见的两个使用场景是:

  • 移动端无限滚动;
  • 图片懒加载;
    下边具体看看这两个场景的具体例子;

图片懒加载

传统的做法都是绑定scroller事件,滚动过程通过Element.getBoundingClientRect()计算是否出现在可视区,然后加载图片,然而这些都是在主线程频繁触发,会影响性能;

import React, { useState, useEffect, useRef } from 'react';
import { imgs } from './imgs.ts'  //这个文件会返回一个很长的图片列表

export default () => {

  useEffect(() => {
    const interSectionObserver = new IntersectionObserver(entries => {
      // 当目标元素与视窗(默认的祖先元素)交叉超过阈值(默认为0)时的回调,entries是所有观测的目标元素
      entries.forEach(item => {
        if (item.isIntersecting) {  // true表示超过交叉阈值,取dataset.src复制给src,实现图片加载
          const imgDom = item.target
          const imgSrc = imgDom.dataset.src
          imgDom.src = imgSrc
          interSectionObserver.unobserve(imgDom) // 图片加载完毕之后,取消观测该元素,否则来回滚动会继续触发;
        }
      })
    }, {
      rootMargin: "350px 0px"  // 这个会使得根元素的矩形大小上下各增加350px; 实现图片没有出现在屏幕可视区,但是开始加载;
    })

    Array.from(document.querySelectorAll('img')).forEach(dom => {
      interSectionObserver.observe(dom)  // 找到所有的img元素,并观测;
    })

  }, [])

  return (<div style={{ height: "600px", overflow: "scroll" }}>
    {
      imgs.map((item, index) => {
        return <div style={{ 'justifyContent': "center" }} key={item}>
          {/** 先把图片资源存储在data-src中 */}
          <img data-src={item} style={{ width: "300px", height: "165px", margin: "20px" }} />
          <span>{index}</span>
        </div>
      })
    }
  </div>)
}

无限滚动

这个场景在移动端会比较常见,滚定到底部实现自动分页,一般的做法也是绑定scoller事件,判断距离底部一定的值时发接口。问题和图片懒加载是一样的,利用IntersectionObserver就可以改善很多,在最底部放一个元素,观测该元素的交叉状况即可实现分页;

import React, { useState, useEffect, useRef } from 'react';

const bgColors = ['#4e61d4', '#ccc', '#999', 'pink', 'yellow']
const generateBlock = (counts: Number) => {
  return new Array(counts).fill(0).map(() => {
    const backgroundColor = bgColors[Math.floor(Math.random() * bgColors.length)]
    return <div style={{ height: "50px", margin: "20px 0", backgroundColor }} />
  })
}

export default function () {
  const [blocks, setBlocks] = useState(generateBlock(10));
  const footerRef = useRef('')

  useEffect(() => {
    const interSectionObserver = new IntersectionObserver((enteries) => {
      // 可见
      if (enteries[0].isIntersecting) {
        addblocks(10)  //增加10个背景色随机的块(模拟分页)
      }
    },{
      rootMargin:"50px 0px"
    });
    if (footerRef.current) {
      interSectionObserver.observe(footerRef.current)
    }
  }, [])

  const addblocks = (counts: Number) => {
    setBlocks((prevBlocks) => ([...prevBlocks, ...generateBlock(counts)]))
  }

  return (
    <>
      <div>无线向下滚动</div>
      {[...blocks]}
      <div ref={footerRef} style={{ height: "1px" }}>footer</div>
    </>
  );

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions