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
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();
callback函數被調用時,會傳給它一個數組,這個數組裏的每一個對象就是當前進入可視區域或者離開可視區域的對象(IntersectionObserverEntry對象)app
這個對象有不少屬性,其中最經常使用的屬性是:
調用IntersectionObserver時,除了傳一個回調函數,還能夠傳入一個option對象,配置以下屬性:
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