图片懒加载 【JavaScript插件】

408 阅读4分钟

图片延迟加载

之所以对页面图片进行延迟加载,是由于浏览器加载线程有限造成的。 当页面首页加载过多图片时,会严重想其他必要文件的加载(如css,js等),这造成糟糕的用户体验,同时这也会是页面load事件处理逻辑推迟执行,因此我们可以使可视范围内的图片优先加载。

延迟加载图片插件

;
(function(undefined) {
    // "use strict"
    var _global;

    // 构造方法 初始化 setting
    function extend(o, n, override) {
        for (var key in n) {
            if (n.hasOwnProperty(key) && (!o.hasOwnProperty(key) || override)) {
                o[key] = n[key];
            }
        }
        return o;
    };

    // 插件构造函数 - 返回数组结构
    function Lazyload(id) {
        this.container = document.getElementById(id);
        // 缓存图片
        this.imgs = this.getImgs();
        // 执行逻辑
        this.init();
    };

    Lazyload.prototype = {
        constructor: this,
        // 起始执行逻辑
        init: function() {},
        // 延迟加载图片
        getImgs: function() {},
        // 加载图片
        update: function() {},
        //判断图片是否在可视范围内
        shouldShow: function(i) {},
        // 获取元素页面中纵坐标位置
        pageY: function(element) {},
        // 绑定事件
        on: function(element, type, fn) {},
        // 未窗口绑定resize事件与scroll事件
        bindEvent: function() {}

    }

    // 将插件对象暴露给全局对象(考虑兼容性)
    _global = (function() {
        return this || (0, eval)('this');
    }()); // //查找当前作用域,也就是this的指向,也就是找到顶层对象;
    if (typeof module !== "undefined" && module.exports) {
        module.exports = Lazyload;
    } else if (typeof define === "function" && define.amd) {
        define(function() {
            return Lazyload;
        });
    } else {
        !('Lazyload' in _global) && (_global.Lazyload = Lazyload);
    }
}());

图片延迟加载的框架有了,现在让我们一起来实现每一个方法

获取容器内的图片

对于获取容器内图片的getImgs方法需要注意的是,为了方便操作获取的图片集合(类数组),需要将其转化成数组

getImgs: function() {
    // 新数组容器
    var arr = [];
    // 获取所有img标签
    var imgs = this.container.getElementsByTagName('img');
    // 将获取的图片转换成数组(IE下Array.prototype.slice会报错) 
    for (var i = 0, len = imgs.length; i < len; i++) {
        arr.push(imgs[i]);
    }
    return arr;
}

加载图片

对于加载图片方法update,需要遍历每一个图片元素,如果处在可视区域内则加载并将其从图片缓存中清楚

update: function() {
    if (!this.imgs.length) return;
    var i = this.imgs.length;
    while (--i >= 0) {
        // 如果图片在可视区域
        if (this.shouldShow(i)) {
            this.imgs[i].src = this.imgs[i].getAttribute('data-src');
            // 清除图片缓存
            this.imgs.splice(i, 1);
        }
    }
}

筛选需要加载的图片

对于判断图片是否在可视范围内shouldShow方法,是判断图片的上下变坐标位置是否符合下列条件:图片底部高度大于可视视图的顶部高度并且图片底部高度小于可视视图的底部高度,或者图片顶部的高度大于可视视图的顶部的高度并且图片底部高度小于可视视图底部高度

shouldShow: function(i) {
    // 获取当前图片
    var img = this.imgs[i],
        //可视视图顶部高度(页面滚动条top值)
        scrollTop = document.documentElenment.scrollTop || document.body.scrollTop,
        // 可视视图底部高度
        scrollBottom = scrollTop + document.documentElement.clientHeight,
        // 图片顶部高度
        imgTop = this.pageY(img),
        // 图片底部高度
        imgBottom = imgTop + img.offsetHeight;
    if (imgBottom > scrollBottom && imgBottom < scrollTop || (imgTop > scrollTop && imgTop < scrollBottom)) {
        return true
    }
    return false
}

获取图片的纵坐标

对于获取图片元素纵坐标pageY方法,是通过一级一级遍历父元素,并累加每一级的offsetHeight得到的

pageY: function(element) {
    if (element.offsetParent) {
        return element.offsetHeight + this.pageY(element.offsetParent);
    } else {
        return element.offsetHeight
    }
}

绑定事件

// 绑定事件
on: function(element, type, fn) {
        if (element.addEventListener) {
            element.addEventListener(type, fn, false)
        } else if (element.attachEvent) {
            element.attachEvent('on' + type, fn, false)
        } else {
            element['on' + type] = fn
        }
    },
    // 未窗口绑定resize事件与scroll事件
    bindEvent: function() {
        var that = this;
        this.on(window, 'resize', function() {
            throttle(that.update, {
                context: that,
                time: 300
            })
        })
        this.on(window, 'scroll', function() {
            throttle(that.update, {
                context: that,
                time: 300
            })
        })
    }

最后一个方法bindEvent 绑定事件则是对页面的scroll与resize事件的监听,为检测每一次交互中事件的最后一次执行,故需要对事件的回调函数做节流处理

// 节流
var throttle = function() {
    // 获取第一个参数
    var isClear = arguments[0],
        fn;
    // 如果第一个参数是boolean类型那么第一个参数表示是否清除计时器
    if (typeof isClear === 'boolean') {
        // 第二个参数则为函数
        fn = arguments[1];
        // 函数计时器句柄存在,这清楚该计时器
        fn._throttleID && clearTimeout(fn._throttleID);
        // 通过计时器延迟函数的执行
    } else {
        //第一个参数是函数
        fn = isClear;
        // 第二个参数为函数执行是的参数
        var param = arguments[1];
        // 对执行时的参数适配默认值
        var p = extend({
            context: null,
            args: [],
            time: 300
        }, param, true);
        // 清除执行函数计时器句柄
        arguments.callee(true, fn)
        // throttle(true, fn);
        // 为函数绑定计时器句柄,延迟执行函数
        fn._throttleID = setTimeout(function() {
            // 执行函数
            fn.apply(p.context, p.args)
        }, p.time)
    }
}

使用

// 延迟加载 container容器内的图片
new LazyLoad('container');

这里是一个LazyLoad 例子(源码)