zoukankan      html  css  js  c++  java
  • IntersectionObserver简介

    写在前面

    在移动端,有个很重要的概念,叫做懒加载,适用于一些图片资源特别多,ajax数据特别多的页面中,经常会有动态加载数据的场景中,这个时候,我们通常是使用监听scroll或者使用setInterval来判断,元素是否进入视图,其中scroll由于其特别大的计算量,会有性能问题,而setInterval由于其有间歇期,也会出现体验问题。

    1. 关于IntersectionObserver

    浏览器的开发商,估计也发现了这个问题,所以在2016年初,chrome51率先提供了一个新的API,就是IntersectionObserver,它可以用来监听元素是否进入了设备的可视区域之内,而不需要频繁的计算来做这个判断。

    毕竟是一个新兴的API,所以浏览器的支持性并不好,这里可以看看当前浏览器对于IntersectionObserver的支持性:IntersectionObserver-canuse Alt text

    虽然当前受限于浏览器的支持性,该方法还不能用于生产环境中,但却不影响我们去先了解一下这个令人兴奋的API,所以在接下来的文章中,就先来一步步的看看,为我们带来了哪些好处吧。

    2. API简介

    API的调用非常简单:

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    var observer = new IntersectionObserver(callback,options);
    

    IntersectionObserver支持两个参数:

      1. callback是当被监听元素的可见性变化时,触发的回调函数
    1. options是一个配置参数,可选,有默认的属性值

    接下来,就看一个官方的示例代码:

    • 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
    //初始化一个实例
    var observer = new IntersectionObserver(changes => {
        for (const change of changes) {
            console.log(change.time);
            // Timestamp when the change occurred
            // 当可视状态变化时,状态发送改变的时间戳
            // 对比时间为,实例化的时间,
            // 比如,值为1000时,表示在IntersectionObserver实例化的1秒钟之后,触发该元素的可视性变化
    
            console.log(change.rootBounds);
            // Unclipped area of root
            // 根元素的矩形区域信息,即为getBoundingClientRect方法返回的值
    
            console.log(change.boundingClientRect);
            // target.boundingClientRect()
            // 目标元素的矩形区域的信息
    
            console.log(change.intersectionRect);
            // boundingClientRect, clipped by its containing block ancestors,
            // and intersected with rootBounds
            // 目标元素与视口(或根元素)的交叉区域的信息
    
            console.log(change.intersectionRatio);
            // Ratio of intersectionRect area to boundingClientRect area
            // 目标元素的可见比例,即intersectionRect占boundingClientRect的比例,
            // 完全可见时为1,完全不可见时小于等于0
    
            console.log(change.target);
            // the Element target
            // 被观察的目标元素,是一个 DOM 节点对象
            // 当前可视区域正在变化的元素
    
        }
    }, {});
    
    // Watch for intersection events on a specific target Element.
    // 对元素target添加监听,当target元素变化时,就会触发上述的回调
    observer.observe(target);
    
    // Stop watching for intersection events on a specific target Element.
    // 移除一个监听,移除之后,target元素的可视区域变化,将不再触发前面的回调函数
    observer.unobserve(target);
    
    // Stop observing threshold events on all target elements.
    // 停止所有的监听
    observer.disconnect();
    

    详情请参考:IntersectionObserver

    俗话说,纸上得来终觉浅,还是看个小示例吧:第一场实战演练

    3. callback和options

    IntersectionObserver这个方法中,最重要的一个参数就是callback参数,如前面的示例中看到的,那么我们后面再来说另外一个参数,options吧。

    现在就先来看一下options的参数:

    options是可以选择设置的,即便我们不设置该参数,也会有一个默认的属性,这个默认的属性,就定义在了IntersectionObserver的原型链中,那么接下来看看有哪些默认的属性和方法:

    • 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
    disconnect()
    // function,没有参数。
    // 停止实例的继续监听,在前面的callback回调函数中,已经有看到了
    
    observe(target)
    // function,传入一个参数,原生的DOM对象,一个将要被监听的DOM元素
    // 添加一个监听元素,
    
    root
    // DOM元素,但默认值为null,也就是视口区域
    // 表示监听的可视区域为整个视口,也就是浏览器的可视区域
    // 如果设置了DOM元素,那么视口就变为该元素(即,就算元素在屏幕的可视区域,但是不在该DOM元素的可视区域,仍然不会触发可视性变化)
    
    rootMargin
    // 类似于css的margin属性,可以设置四个属性。
    // 该属性默认值为0px 0px 0px 0px,如果设置之后,会影响触发回调的时间
    
    takeRecords
    // 该属性,目前没有找到是干嘛的,返回一个数组,而且一直为空数组
    
    thresholds
    // Array
    // 默认值为[0],
    // 当元素的进入可视区域的百分比达到这个参数的设置值得时候
    // 就会触发IntersectionObserver实例中的callback的回调函数
    // 具体后面再看啦
    
    unobserve(target)
    // 解除target的元素的绑定
    

    其中,rootrootMarginthresholds这三者是属性,说到这里,就是想引入前面调用的时候的options的取值。在IntersectionObserver的第二个参数options中,可以通过设置这三个属性,来改变IntersectionObserver实例的一些表现。

    接下来我们就一个个的看一下它们的具体效果。

    3.1 root参数

    根元素,可视性以哪个root元素,作为参考系,如果不设置,那么可视区域,就是整个视图的区域,那么只要元素在该区域可视之后,就可以触发监听的回调函数。

    俗话再说,事实胜于雄辩,再来实战吧:

    第二场实战演练(root设置为一个DIV)

    第二场实战中,设置root为一个DIV元素,并添加了两个被监听的元素,其中,root内部的元素,可视性变化的时候,会触发callback,而root外部的元素,即便是一直在可视区域,也不会出现触发可视性的变化,这就是root的效果。

    再做一个对比,如果不设置的时候:

    第三场实战演练(root为默认值)

    root参与设置监听的区域,就比如上面的两个示例,如果给root设置了为一个DIV,那么所有被监听的目标元素,只有那些root元素的子元素,当其可见性变化时,才会触发该实例的回调函数。在该root元素之外的目标元素,可见性变化时,是无法被监听到的。

    并且,还有一个问题,如果root设置为一个DIV,而这个DIV的高度,是高于设备的可视区域的,那么当滚动这个区域时,元素在没有还没有进入设备的可视区域的时候,已经进入了root的可视区域了,那么依然也会触发可视性改变的回调函数,在本小节上面的两个示例中,都可以测试出该种状态。

    3.2 rootMargin属性

    关于这个属性,我们可以理解为更精确的配置参数,它是设置在root元素上的一个margin属性,其使用方法与CSS中的margin属性是完全相同的,虽然这个命名为rootMargin,也是设置在root属性上的,但真正起作用是作用在元素上的

    如下图所示: Alt text

    如图,root元素就是图中的黑色区域,给它设置一个rootMargin:10px的属性,就是如图中,黑色区域,和边框之间的间隔区域。

    假设现在监听元素ele(假设为上图中,红色元素区域)的可视性变化,那么如果我们不设置rootMargin的话,只有当ele元素,进入图中黑色区域的时候,其可视性才会改变,而这里,我们设置了rootMagrin的值,如图所示,当ele进入到边框内部,还没有进入到黑色区域时,就可以触发可视性变化的。

    或者也可以这样理解,root元素,多了一个margin属性,如果没有这个margin属性,ele元素只有与root元素开始交叉时才会触发可视性的变化,而这个rootMargin属性的话,就是当ele元素与root元素的外边距交叉时,就会触发ele元素的可视性变化。

    那么rootMagrin的好处在哪里呢?可以理解为,懒加载的预加载,比如图片的懒加载,不是等图片元素进入可视区域之后,才去加载,而是检测到,离进入可视区域,在一定范围内部的时候,就提前加载(叫做预加载吧),这样,当真的进入视图之内,说不定已经加载好了,可以大大的提升整个产品的体验。

    第四场实战演练

    虽然说,rootMargin的设置方法,与CSS中的margin属性设置方法完全相同,但是其设置的值,却不完全相同,比如,rootMargin属性就不能支持“em”,“rem”等单位的设置。所以设置时,尽量使用一些基本的单位,比如“px”,”%“等。

    3.3:threshold属性

    在前面的示例中,每个元素只有在刚出现和刚离开的时候(change.intersectionRatio=0的变化时),才会触发可视性的变化,如果只是这样的话,那该方法就显得过于死板了,一点都不灵活。

    所以,在初始化设置参数时,还可以传入一个threshold的参数,用来设置当change.intersectionRatio属性,达到指定值时,也会触发回调函数。

    threshold的默认值是:[0],即只有在开始进入,或者是完全离开视图区域时,才会触发。

    这里有个很疑问的地方,为何在实例属性中,是thresholds的属性,而在设置时,却是设置threshold的值呢,比如设置和读取:

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    var observer = new IntersectionObserver(_observer, {
        root : $(“#containter”)[0],
        threshold:[0,0.25,0.5,0.75]
        //设置时,不是个复数
    });
    
    //查看实例属性时,却是复数。
    observer.thresholds
    

    继续看DEMO吧:第五场实战演练(threshold属性)

    注意:虽然,上述的三个属性,都是属于实例中的属性,但是这三个属性,只是可读的,当实例化之后,这三个属性就是不可更改的。有兴趣的可以自己试试。

    3.4 回调函数

    前面把options的参数,介绍了一下,接下来再来看看callback能为我们提供些什么信息吧。

    可以看第二小节中的示例代码,可以看到,在callback中,是传入一个数组,数组中的每一个元素,都代表一个可视性变化的DOM元素,它包含该元素的一些基本信息和变化的信息。

    看如下的代码:

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    //初始化一个实例
    var observer = new IntersectionObserver(changes => {
        for (const change of changes) {
            console.log(change.time);
            console.log(change.rootBounds);
            console.log(change.boundingClientRect);
            console.log(change.intersectionRect);
            console.log(change.intersectionRatio);
            console.log(change.target);
        }
    }, {});
    

    参数的含义,在本篇开头的地方,示例也在前面引入过来,这里继续引入前面的一个示例吧:再看第一场实战演练

    其中,有三个参数是用来获取位置信息的,rootBoundsroot元素的位置信息),boundingClientRecttarget元素的位置信息),intersectionRect(进入交叉显示区域的位置信息,与intersectionRatio属性有关)。如果要更好的理解,需要看一下前面的示例,在控制台对比这三个数据,才能更好的理解。

    4. 其他

    前面我们说的都是一些基础的元素,基础的属性,那么如果设置了positionmargindisplayvisibilityclip等属性呢?对于元素的可视性,会有什么影响吗?

    这个希望有兴趣了解的,可以自己去尝试一把,来一次实战如何?

    IntersectionObserver实例是一个异步的实例,它只有在浏览器空闲的状态下才会触发,如果浏览器当前的事件队列中,有一系列的回调函数正在等待处理,该方法是不会被执行到的,只有当浏览器的事件队列为空,浏览器在空闲的时候,才会执行该方法。

    就异步的处理方式来看,该APIrequestIdleCallbackAPI,效果是一样的,都是在浏览器空闲的时候,才会被执行,他们与计时器的原理是不一样的,计时器的原理是在指定的时间之后,就把处理函数直接推入到执行队列中去,而这两个方法,就算已经被触发了(或者说,调用过来,依然不会出现在函数执行队列中)。

    这是浏览器的一个优化,考虑用户体验的同时,也在考虑浏览器的性能消耗。

    5. 应用

    1. 预加载(滚动加载,无限加载)
    2. 懒加载(图片后加载)
    3. TAB滚动跟随

    暂时能想到的应用场景,就是上面提到的这些个场景

    关于这些应用的实战,就靠你啦,去实战一下吧。

    6. 总结

    在当前判断可视性的方法,基本就是监听scroll事件,或者是计时器循环判断来做这个判断,但是由于其高频的计算频率,会导致浏览器性能的损失,尤其是,如果一个同一个页面中,有多个地方,需要这样的判断,那么就需要绑定多个scroll事件,或者有多个计时器在轮询的话,那么对性能的损失就更为客观了。

    虽然现在的浏览器性能一直在增强,但是也有更多的消耗性能的比较炫的技术在产生,它们依然在占据着浏览器的大量的计算内存,所以,尽量在可以节省性能的时候,就节省一下性能吧。

    而该方法给我们提供了一个更简单直接,性能更好的解决方案,希望以后的浏览器,可以越来越广泛的支持吧。

    本篇到此为止,感谢阅读。

    如果您发现文中有描述错误或者不当的地方,请留言指出,不胜感激,谢谢!

    本原文地址:http://www.zhangyunling.com/?p=811

  • 相关阅读:
    最近要看的项目
    Lavarel Route::resource
    架构,性能
    Unity ToLua & LuaFramework_UGUI学习笔记(zz)
    Unity UI 布局
    Introduction to Unity UI
    Unity more efficient find
    unity UI如何开启(显示)或者关闭(隐藏)Panel界面最好?
    Unity Canvas vs Panel
    Unity实现新手引导圆形遮罩
  • 原文地址:https://www.cnblogs.com/ckAng/p/9371435.html
Copyright © 2011-2022 走看看