• 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    图片懒加载

    在这里插入图片描述

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    吸顶

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    五、浏览器兼容性

    在这里插入图片描述

  • 相关阅读:
    软考高级系统架构设计师系列之:系统开发基础知识
    信息学奥赛一本通:1159:斐波那契数列
    使用Hadoop MapReduce分析邮件日志提取 id、状态 和 目标邮箱
    Vue3.3 新特性 - 初体验
    一文彻底理解synchronized(通俗易懂的synchronized)
    ffmpeg sdk 视频合成
    Adersoft VbsEdit 9.9X Crack
    微信公众号之底部菜单
    工业自动化应用智能制造技术有哪些作用?
    Auracast 广播音频知识点
  • 原文地址:https://blog.csdn.net/qq_37241934/article/details/126124691