Intersection Observer实现滚动到相应区域执行动画及图片懒加载

2019-04-042904次阅读javascript

Intersection observer API提供了一种方法,可以异步观察目标元素与祖先元素或相对于浏览器视口(root)的交集变化。

很多种情况下都需要用到元素交集变化的信息,例如:

  • 当页面滚动时,懒加载图片或其他内容。
  • 实现“可无限滚动”网站,也就是当用户滚动网页时直接加载更多内容,无需翻页。
  • 为计算广告收益,检测其广告元素的曝光情况。
  • 根据用户是否已滚动到相应区域来灵活开始执行任务或动画。

过去,交集检测通常需要涉及到事件监听,以及对每个目标元素执行Element.getBoundingClientRect()方法以获取所需信息。可是这些代码都在主线程上运行,所以任何一点都可能造成性能问题。

一睹为快:

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

var callback = function(entries, observer) { 
    entries.forEach(entry => {
        //以下都是只读属性
        entry.time;               // 返回一个记录从 IntersectionObserver 的时间原点(time origin)到交叉被触发的时间的时间戳(DOMHighResTimeStamp).
        entry.rootBounds;         // 返回一个 DOMRectReadOnly 用来描述交叉区域观察者(intersection observer)中的根.
        entry.boundingClientRect; // 返回包含目标元素的边界信息的DOMRectReadOnly. 边界的计算方式与  Element.getBoundingClientRect() 相同..
        entry.intersectionRect;   // 返回一个 DOMRectReadOnly 用来描述根和目标元素的相交区域.
        entry.intersectionRatio;  // 返回intersectionRect 与 boundingClientRect 的比例值.
        entry.target;             // 与根出现相交区域改变的元素 (Element).
    });
};

//通过new IntersectionObserver()构造器创建并返回一个IntersectionObserver实例对象。
var observer = new IntersectionObserver(callback, options);
var target = document.querySelector('#listItem');
//为观察者对象添加一个观察元素
observer.observe(target);

callback

当元素可见比例超过指定阈值后,会调用一个回调函数,此回调函数接受两个参数:

  • entries:一个IntersectionObserverEntry对象列表(list),每个被触发的阈值,都会较指定阈值有偏差(或超出或少于指定阈值)。
  • observer:被调用的IntersectionObserver实例对象。

options可选

一个可以用来配置observer实例的对象。如果options未指定,observer实例默认使用viewport作为根(root),并且没有margin, 阈值为0% (意味着即使1像素的改变都会触发回调函数)。你可以指定以下配置:

  • root:监听元素的祖先元素Element对象,其边界盒将被视作viewport。目标在根的可见区域的的任何不可见部分都会被视为不可见。如果设置root参数为null或者不设置则root默认为设备视口)
  • rootMargin:一个在计算交叉值时添加至根的边界盒(bounding_box)中的一组偏移量,类型为字符串(string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和CSS 中的margin属性等同:上、右、下、左。默认值是"0px 0px 0px 0px".
  • threshold:规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组0.0到1.0之间的数组。若指定值为0.0,则意味着监听元素即使与根有1像素交叉,此元素也会被视为可见. 若指定值为1.0,则意味着整个元素都是可见的。阈值的默认值为0.0.

IntersectionObserver实例对象方法

  • IntersectionObserver.disconnect:使IntersectionObserver对象停止监听工作。
  • IntersectionObserver.observe:使IntersectionObserver开始监听一个目标元素。
  • IntersectionObserver.takeRecords:为所有监听目标返回一个IntersectionObserverEntry对象数组并且停止监听这些目标。
  • IntersectionObserver.unobserve:使IntersectionObserver停止监听特定目标元素。

滚动到相应区域执行css动画

<div class="b-block">
....
</div>
<div class="b-block">
....
</div>
const addAnimation = function(){

    const observerEle = function(){
            //选取元素
            const elements = document.querySelectorAll('.b-block');

            const options = {};

            let imageCount = elements.length;

            const observer = new IntersectionObserver(onIntersection,options);

            Array.prototype.slice.call(elements).forEach(target=>{
                //给选取的元素添加监听目标
                observer.observe(target);

            });
        
            function onIntersection(entries,observer){
                //终止对所有目标的监听观察
                if (imageCount === 0) {
                    observer.disconnect();
                }

                entries.forEach(entry=>{
                    //entry.intersectionRatio = 1在视口内,0为不在视口内
                    //也可以试一试entry.isIntersecting为true在视口内,为false为不在视口内
                    if (entry.intersectionRatio > 0) {
                        imageCount--;
                        //停止监听当前目标元素
                        observer.unobserve(entry.target);
                        //给目标元素添加css动画类名
                        entry.target.classList.add('transition');                        
                    }

                });

            }

        };

    if('IntersectionObserver' in window ) observerEle();

}();

浏览器兼容的图片懒加载

<img a="https://static.xinran001.com/frontend/website/build/images/footerlogo.png">
var lazyLoad = function() {
    var IeModule = function(){
                var map_element = {},
                    element_obj = [],
                    download_count = 0,
                    last_offset = -1,
                    doc = document,
                    doc_body = doc.body,
                    doc_element = doc.documentElement,
                    lazy_load_tag = "img",
                    imagesrcname = 'a',
                    thissrolltop = 400;
                
                function initElementMap(){

                        var el = doc.getElementsByTagName(lazy_load_tag); 

                        for (var j = 0, len2 = el.length; j < len2; j++) { 

                            if (typeof el[j] == "object" && el[j].getAttribute(imagesrcname)){

                                element_obj.push(el[j]); 

                            } 
                        } 

                    for (var i = 0, len = element_obj.length; i < len; i++) {

                        var o_img = element_obj[i],

                            t_index = getAbsoluteTop(o_img);

                        if (map_element[t_index]){

                            map_element[t_index].push(i);

                        }else{

                            var t_array = []; 

                            t_array[0] = i;

                            map_element[t_index] = t_array; 

                            download_count++;

                        } 
                    } 
                }


                function initDownloadListen(){

                    if (!download_count) return;

                    var getscrolltop = doc_body.scrollTop;

                    if(getscrolltop == 0){

                        getscrolltop = doc.documentElement.scrollTop;

                    }

                    var visio_offset = getscrolltop + doc_element.clientHeight; 

                    if (last_offset == visio_offset){

                        setTimeout(initDownloadListen,200);

                        return; 
                    }

                    last_offset = visio_offset;

                    var visio_height = doc_element.clientHeight + thissrolltop,

                        img_show_height = visio_height + getscrolltop; 
                    // var allurlnum = parseInt(imageurlarray.length);
                    // if(allurlnum <= 0)
                    // {
                    //  allurlnum = 1;
                    // }
                    var j = 0;

                    for (var key in map_element) {

                            if (img_show_height > key){

                                var t_o = map_element[key],

                                    img_vl = t_o.length; 

                                for (var l = 0; l < img_vl; l++) { 
                                
                                    element_obj[t_o[l]].src = element_obj[t_o[l]].getAttribute(imagesrcname);

                                    element_obj[t_o[l]].removeAttribute(imagesrcname);

                                    j++; 
                                }

                                delete map_element[key];

                                download_count--;

                            }
                    }
                    
                    setTimeout(initDownloadListen, 200); 
                }


                function getAbsoluteTop(element) {

                    if (arguments.length != 1 || element == null){ 

                        return null; 

                    }


                    var offsetTop = element.offsetTop,

                        current = element.offsetParent;

                    while(current !== null){

                        offsetTop += current.offsetTop;

                        current = current.offsetParent;

                    }
                    return offsetTop; 
                }

                function init(){

                    initElementMap(); 

                    initDownloadListen(); 
                }

                init();
        },
        observerImg = function(){
                // 获取所有标记为懒惰加载的图像。
                var images = document.querySelectorAll('img[a]'),

                    config = {//如果图像在y轴内达到上下50px,则开始下载。
                        rootMargin: '50px 0px',
                        threshold: 0.01
                    },

                    imageCount = images.length,

                    observer = new IntersectionObserver(onIntersection, config);

                Array.prototype.slice.call(images).forEach(function(target){
                    observer.observe(target);
                });
            
                function onIntersection(entries) {
                    //终止对所有目标的观察
                    if (imageCount === 0) {
                        observer.disconnect();
                    }

                    entries.forEach(function(entry){

                        //1在视口内,0为不在视口内
                        if (entry.intersectionRatio > 0) {
                            imageCount--;
                            //停止观察并载入图像
                            observer.unobserve(entry.target);
                            preloadImage(entry.target);
                        }

                    });

                }

        
                function fetchImage(url) {
                    return new Promise(function(resolve, reject){
                        var image = new Image();
                        image.src = url;
                        image.onload = resolve;
                        image.onerror = reject;
                    });
                }

    
                function preloadImage(image) {
                    //const src = image.dataset.src;
                    var src = image.getAttribute("a");

                    if(!src) return;

                    return fetchImage(src).then(function(){ applyImage(image, src); });
                }

                function applyImage(img, src) {
                    // Prevent this from being lazy loaded a second time.
                    // img.classList.add('js-lazy-image--handled');
                    img.src = src;
                    img.classList.add('fade-in');
                    img.removeAttribute("a");
                }
        };

    //如果我们没有支持交叉路口观察员,立即加载图像
    if('IntersectionObserver' in window ){
        observerImg();
        return;
    }

    IeModule();

}()

 

上一篇: insertAdjacentHTML与appendChild方法区别  下一篇: npm中--save-dev与--save的区别  

Intersection Observer实现滚动到相应区域执行动画及图片懒加载相关文章