IntersectionObserve初試

IntersectionObserve這個API,可能知道的人並很少(我也是最近才知道...),這個API能夠很方便的監聽元素是否進入了可視區域。javascript

<style>
* {
  margin: 0;
  padding: 0;
}

.test {
  width: 200px;
  height: 1000px;
  background: orange;
}

.box {
  width: 150px;
  height: 150px;
  margin: 50px;
  background: red;
}
</style>
<div class="test">test</div>
<div class="box">box</div>

上圖代碼中,.box元素目前並不在可視區域(viewport)內,如何監聽它進入可視區域內?css

傳統作法是:監聽scroll事件,實時計算.box距離viewport的top值:html

const box = document.querySelector('.box');
const viewHeight = document.documentElement.clientHeight;

window.addEventListener('scroll', () => {
  const top = box.getBoundingClientRect().top;
  
  if (top < viewHeight) {
    console.log('進入可視區域');
  }
});

然而scroll事件會頻繁觸發,所以咱們還須要手動節流。前端

使用IntersectionObserve就很是方便了:java

const box = document.querySelector('.box');
const intersectionObserver = new IntersectionObserver((entries) => {
  entries.forEach((item) => {
    if (item.isIntersecting) {
      console.log('進入可視區域');
    }
  })
});
intersectionObserver.observe(box);

IntersectionObserver API是異步的,不隨着目標元素的滾動同步觸發,因此不會有性能問題。react

IntersectionObserver構造函數

const io = new IntersectionObserver((entries) => {
  console.log(entries);
});

io.observe(dom);

調用IntersectionObserver時,須要給它傳一個回調函數。當監聽的dom元素進入可視區域或者從可視區域離開時,回調函數就會被調用。npm

注意: 第一次調用new IntersectionObserver時,callback函數會先調用一次,即便元素未進入可視區域。數組

構造函數的返回值是一個觀察器實例。實例的observe方法能夠指定觀察哪一個 DOM 節點。瀏覽器

// 開始觀察
io.observe(document.getElementById('example'));

// 中止觀察
io.unobserve(element);

// 關閉觀察器
io.disconnect();

IntersectionObserverEntry對象

callback函數被調用時,會傳給它一個數組,這個數組裏的每一個對象就是當前進入可視區域或者離開可視區域的對象(IntersectionObserverEntry對象)app

這個對象有不少屬性,其中最經常使用的屬性是:

  • target: 被觀察的目標元素,是一個 DOM 節點對象
  • isIntersecting: 是否進入可視區域
  • intersectionRatio: 相交區域和目標元素的比例值,進入可視區域,值大於0,不然等於0

options

調用IntersectionObserver時,除了傳一個回調函數,還能夠傳入一個option對象,配置以下屬性:

  • threshold: 決定了何時觸發回調函數。它是一個數組,每一個成員都是一個門檻值,默認爲[0],即交叉比例(intersectionRatio)達到0時觸發回調函數。用戶能夠自定義這個數組。好比,[0, 0.25, 0.5, 0.75, 1]就表示當目標元素 0%、25%、50%、75%、100% 可見時,會觸發回調函數。
  • root: 用於觀察的根元素,默認是瀏覽器的視口,也能夠指定具體元素,指定元素的時候用於觀察的元素必須是指定元素的子元素
  • rootMargin: 用來擴大或者縮小視窗的的大小,使用css的定義方法,10px 10px 30px 20px表示top、right、bottom 和 left的值
const io = new IntersectionObserver((entries) => {
  console.log(entries);
}, {
  threshold: [0, 0.5],
  root: document.querySelector('.container'),
  rootMargin: "10px 10px 30px 20px",
});

懶加載

圖片懶加載的原理就是:給img標籤一個自定義屬性,用來記錄真正的圖片地址。默認img標籤只加載一個佔位符。當圖片進入可視區域時,再把img的src屬性更換成真正的圖片地址。

<div>
  <img src="/empty.jpg" data-src="/img/1.jpg" />
  <img src="/empty.jpg" data-src="/img/2.jpg" />
</div>
const intersectionObserver = new IntersectionObserver((entries) => {
    entries.forEach((item) => {
        if (item.isIntersecting) {
            item.target.src = item.target.dataset.src;
            // 替換成功後,中止觀察該dom
            intersectionObserver.unobserve(item.target);
        }
    })
  }, {
      rootMargin: "150px 0px" // 提早150px進入可視區域時就加載圖片,提升用戶體驗
    });

const imgs = document.querySelectorAll('[data-src]');
imgs.forEach((item) => {
    intersectionObserver.observe(item)
});

打點上報

前端頁面常常有上報數據的需求,好比統計頁面上的某些元素是否被用戶查看,點擊。這時,咱們就能夠封裝一個Log組件,用於當元素進入可視區域時,就上報數據。
以React爲例,咱們但願:被Log組件包裹的元素,進入可視區域後,就向後臺發送{ appid: 1234, type: 'news'}數據

<Log
  className="container"
  log={{ appid: 1234, type: 'news'}}
  style={{ marginTop: '1400px' }}
  onClick={() => console.log('log')}
>
  <div className="news" onClick={() => console.log('news')}>
    <p>其餘內容</p>
  </div>
</Log>
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Log extends Component {
  static propTypes = {
    log: PropTypes.object
  };

  constructor(props) {
    super(props);
    this.io = null;
    this.ref = React.createRef();
  }

  componentDidMount() {
    this.io = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          console.log('進入可視區域,將要發送log數據', this.props.log);
          sendLog(this.props.log);
          this.io.disconnect();
        }
      }
    );

    this.io.observe(this.ref.current);
  }

  componentWillUnmount() {
    this.io && this.io.disconnect();
  }

  render() {
    // 把log屬性去掉,不然log屬性也會渲染在div上 
    // <div log="[object Object]"></div>
    const { log, ...props } = this.props;
    return (
      <div ref={this.ref} {...props}>
        {this.props.children}
      </div>
    )
  }
}

export default Log

兼容性

safari並不支持該API,所以爲了兼容主流瀏覽器,咱們須要引入polyfill
intersection-observer

相關文章
相關標籤/搜索