useInViewport

2022-02-25  本文已影响0人  skoll

简介

1 .观察元素是否在可见区域
2 .使用场景:

1 .图片懒加载-当图片滚动到可见位置的时候才加载
2 .内容无限滚动-用户滚动到接近内容底部时直接加载更多
3 .检测广告的曝光率,广告是否被看到了
4 .用户看到某个区域时执行任务或播放动画

3 .传统实现:监听scroll事件,调用目标元素的getBoundingClientRect().得到相对于视口左上角的坐标,在判断是否在视口之内.但是触发非常频繁,这样计算就会有性能问题

4 .最简单例子

import React,{useRef} from 'react'
import {useInViewport} from 'ahooks'
export default function Drag(props){
    const ref=useRef()
    const [inViewport,ratio]=useInViewport(ref,{
        threshold:0.1,
    })
    console.log(inViewport,ratio)
    return (
        <div>
            <div style={{ width: 300, height: 300, overflow: 'scroll', border: '1px solid',position:'absolute',top:'100px' }}>
                scroll here
                <div style={{ height: 800 }}>
                <div
                    ref={ref}
                    style={{
                    border: '1px solid',
                    height: 100,
                    width: 100,
                    textAlign: 'center',
                    marginTop: 80,
                    }}
                >
                    observer dom
                </div>
                </div>
            </div>
            {/* <div style={{ marginTop: 16, color: inViewport ? '#87d068' : '#f50' }}>
                inViewport: {inViewport ? 'visible' : 'hidden'}
            </div> */}
        </div>
    )
}

原生实现

1 .IntersectionObserver的实现,应该采用requestIdleCallback(),即只有线程空闲下来,才会执行观察器。这意味着,这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行

import React,{useEffect,useState} from 'react'
export default function Drag(props){
    const [url,setUrl]=useState('222')
    useEffect(()=>{
        const io=new IntersectionObserver((entries)=>{
            entries.forEach((change)=>{
                // let [container,visible]=change
                // 这样操作还不行

                if(change.intersectionRatio<=0)return
                // 消失
                
                let container=change.target
                let visible=change.isVisible
                console.log('被观察的目标出现在了视口里面')

                // 可以加载更多的数据,实现无限滚动的操作.这样做的虚拟列表,我们连右边的滚动条也可以自己实现,不用浏览器自带的版本,虚拟列表也可以这么做.因为浏览器的长度列表是完全靠高度撑起来的,最高是3千万的px.有点离谱.感觉创建这么高的div应该也会耗时间吧.

                // 可以更换图片

                // 注意这里是每次出现都触发,但其实我们应该是这个机制只触发一次
            })
        },{})
        io.observe(document.getElementById('target'),{
            threshold:[0.5,1],
            // 只有出现的时候才触发函数,也就是目标元素出现了0.5的时候就会触发函数u
            root:document.getElementById('parent'),
            // 触发检测的根元素
        })
        
    },[])
    
    return (
        <div>
            <div style={{ width: 300, height: 300, overflow: 'scroll', border: '1px solid',position:'absolute',top:'100px'}}
            id="parent"
            >
                <div style={{ height: 800 }}>
                    这是一个很长的占位操作
                </div>
                <div
                    style={{
                    border: '1px solid',
                    height: 100,
                    width: 100,
                    textAlign: 'center',
                    marginTop: 80,
                    }}
                    id='target'
                >
                    observer dom{url}
                </div>
            </div>
            {/* <div style={{ marginTop: 16, color: inViewport ? '#87d068' : '#f50' }}>
                inViewport: {inViewport ? 'visible' : 'hidden'}
            </div> */}
        </div>
    )
}

图片懒加载的实现效果

1 .img不是先来默认图片,而是一开始都没有地址

img{visibility:hidden}
img[src]{
  visibility:visible
}
<img />

2 .注意这是没有src="",有的浏览器空值都会发出请求

上一篇 下一篇

猜你喜欢

热点阅读