IntersectionObserver API实现场景

一、背景

过去,要检测一个元素是否可见或者两个元素是否相交并不容易,很多解决办法不可靠或性能很差。比如,下面这些情况都需要用到相交检测:

  1. 图片懒加载——当图片滚动到可见时才进行加载
  2. 内容无限滚动——也就是用户滚动到接近内容底部时直接加载更多,而无需用户操作翻页,给用户一种网页可以无限滚动的错觉
  3. 检测广告的曝光情况——为了计算广告收益,需要知道广告元素的曝光情况
  4. 在用户看见某个区域时执行任务或播放动画
    过去,相交检测通常要用到事件监听,并且需要频繁调用 Element.getBoundingClientRect() 方法以获取相关元素的边界信息。事件监听和调用 Element.getBoundingClientRect() 都是在主线程上运行,因此频繁触发、调用可能会造成性能问题。这种检测方法极其怪异且不优雅。
二、概念

Intersection Observer API 允许你配置一个回调函数,当以下情况发生时会被调用

  • 每当目标 (target) 元素与设备视窗或者其他指定元素发生交集的时候执行。设备视窗或者其他元素我们称它为根元素或根 (root)。
    在这里插入图片描述

  • Observer 第一次监听目标元素的时候。

  • 每当被监视的元素进入或者退出另外一个元素时 (或者 viewport ),或者两个元素的相交部分大小发生变化时,该回调方法会被触发执行。
    Intersection Observer API 无法提供重叠的像素个数或者具体哪个像素重叠,他的更常见的使用方式是——当两个元素相交比例在 N% 左右时,触发回调,以执行某些逻辑。
    目标 (target) 元素与根 (root) 元素之间的交叉度是交叉比 (intersection ratio)。这是目标 (target) 元素相对于根 (root) 的交集百分比的表示,它的取值在 0.0 和 1.0 之间。

三、使用方法

创建一个 IntersectionObserver 对象,并传入相应参数和回调用函数,该回调函数将会在目标 (target) 元素和根 (root) 元素的交集大小超过阈值 (threshold) 规定的大小时候被执行。

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

let observer = new IntersectionObserver(callback, options);
  1. options
    options是一个对象,用来配置参数,也可以不填。共有三个属性,具体如下:

在这里插入图片描述

  1. callback
    callback是添加监听后,当监听目标发生滚动变化时触发的回调函数。接收一个参数entries,即IntersectionObserverEntry实例。描述了目标元素与root的交叉状态。具体参数如下:
    在这里插入图片描述

  2. 方法
    介绍了这么多配置项及参数,差点忘了最重要的,IntersectionObserver有哪些方法?
    如果要监听某些元素,则必须要对该元素执行一下observe
    在这里插入图片描述

四、案例
  1. 图片懒加载
<!DOCTYPE html>
<html>
        <head>
                <title>IntersectionObserver</title>
                <style>
                        .body {
                                width: 100%;
                                height: 2000px;
                        }
                        img {
                                width: 150px;
                        
                        }
                </style>
        </head>
        <body>
                <div class="body">
                       <img src="" alt="img" data-src="./img.jpg">
                </div>
        </body>
        <script>
                var imgs = [...document.querySelectorAll('img')]
                const io = new IntersectionObserver(entries=>{
                        console.log(entries,'触发回调函数')
                        entries.forEach(item=>{
                                if(item.isIntersecting) {
                                     item.target.src = item.target.dataset.src
                                     io.unobserve(item.target)
                                }
                        })
                })
                imgs.forEach(item=>io.observe(item))
                // observe是IntersectionObserver的方法,监听一个目标元素
        </script>
</html>

图片懒加载

在这里插入图片描述

  1. 吸顶、吸底
    在一些 APP 外的分享页中经常会看到头部或者底部会固定一个 banner 位,一开始 banner 可能处于正常位置,当即将离开可视区域的时候会固定在屏幕视口顶部或者底部,这种场景页非常适合用 IntersectionObserver 来处理。
    如果页面结构比较简单可以直接使用 css 粘性布局。
    IntersectionObserver 实现元素固定思路也很简单,首先需要给固定元素包一层父元素,父元素指定高度占位,防止固定元素吸附时页面抖动,然后观察父元素的可视状态变化,当父元素即将离开可视区域时改变固定元素的样式。
<!DOCTYPE html>
<html>
        <head>
                <meta charset="utf-8">
                <title>吸顶</title>
                <style>
                        body {
                                margin: 0;
                        }
                        .display {
                                width: 100%;
                                height: 3000px;
                                background-color: #d0d0d0;
                        }

                        .back {
                                display: flex;
                        }

                        .btn {
                                background: #999997;
                                color: #757575;
                                text-align: center;
                                line-height: 40px;
                                width: 100px;
                                height: 40px;
                                margin-right: 14px;
                                border-radius: 20px;
                                cursor: pointer;
                        }

                        .cur {
                                width: 130px;
                                background-color: #fff;
                        }
                </style>
        </head>
        <body>
                <div class="display ">
                        <div class="hold">
                                <div class="back">
                                        <div class="cur btn">露营</div>
                                        <div class=" btn">飞盘</div>
                                        <div class=" btn">吃饭</div>
                                        <div class=" btn">划水</div>
                                </div>
                        </div>
                        <img src="./img.jpg" alt="" style="width: 100%;">

                </div>
        </body>
        <script>
        const cur = document.querySelector('.hold')
        const back = document.querySelector('.back')
        const io = new IntersectionObserver(entries => {
            entries.forEach(item => {
                if (!item.isIntersecting) {
                   back.style.cssText = 'position: fixed; top: 0; left: 0;'
                } else {
                        back.style.cssText = ''
                }
            })
        },{threshold: 1,})
        
        io.observe(cur) // 监听对象
        </script>

</html>

吸顶

sticky粘性定位

单词sticky的中文意思是“粘性的”,position:sticky表现也符合这个粘性的表现。基本上,可以看出是position:relative和position:fixed的结合体——当元素在屏幕内,表现为relative,就要滚出显示器屏幕的时候,表现为fixed。
注意点:

  1. 父级元素不能有任何overflow:visible以外的overflow设置,否则没有粘滞效果。因为改变了滚动容器(即使没有出现滚动条)。因此,如果你的position:sticky无效,看看是不是某一个祖先元素设置了overflow:hidden,移除之即可。
  2. 父级元素设置和粘性定位元素等高的固定的height高度值,或者高度计算值和粘性定位元素高度一样,也没有粘滞效果。
  3. sticky定位,不仅可以设置top,基于滚动容器上边缘定位;还可以设置bottom,也就是相对底部粘滞。如果是水平滚动,也可以设置left和right值。
  4. 由于滚动的时候,流盒不变,而粘性定位元素的包含块跟着滚动,因此粘性约束矩形随着滚动的进行是实时变化的。假设我们的粘性定位元素只设置了top属性值,则粘贴定位元素碰到粘性约束矩形的顶部的时候开始向下移动,直到它完全包含在粘贴约束矩形中。当粘贴约束矩形滚动到页面不可见的时候,粘性也会消失

sticky消失

案例实现

<!DOCTYPE html>
<html>
        <head>
                <meta charset="utf-8">
                <title>吸顶</title>
                <style>
                        body {
                                margin: 0;
                        }

                        .display {
                                width: 100%;
                                height: 3000px;
                                background-color: #d0d0d0;
                        }

                        .back {
                                display: flex;
                                position: -webkit-sticky;
                                position: sticky;
                                top: 10px; // 相对最近的父元素高度而言
                                margin: 50px 0 0;
                        }

                        .btn {
                                background: #999997;
                                color: #757575;
                                text-align: center;
                                line-height: 40px;
                                width: 100px;
                                height: 40px;
                                margin-right: 14px;
                                border-radius: 20px;
                                cursor: pointer;
                        }

                        .cur {
                                width: 130px;
                                background-color: #fff;
                        }
                </style>
        </head>
        <body>
                <div class="display ">
                        <div class="back">
                                <div class="cur btn">露营</div>
                                <div class=" btn">飞盘</div>
                                <div class=" btn">吃饭</div>
                                <div class=" btn">划水</div>
                        </div>
                        <img src="./img.jpg" alt="" style="width: 100%;">
                </div>
                
        </body>

</html>

sticky

  1. 加载更多
    当尾部元素进入页面的时候,就往列表后面添加数据。添加一个加载更多的动画即可
    保证尾部元素mod_loading不在视野,一直在列表的最后面,当尾部元素进入视野再加载数据。
function loadMore() {
  const observer = new IntersectionObserver(
    (entries) => {
      const loadingEntry = entries[0]

      if (loadingEntry.isIntersecting) {
        // 请求数据并插入列表
      }
    },
    {
      rootMargin: '0px 0px 600px 0px', // 提前加载高度
    },
  )

  observer.observe(document.querySelector('.mod_loading')) // 观察尾部元素
}
五、浏览器兼容性

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那就可爱多一点点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值