lazy-load-img.js 源码 学习笔记及原理说明

lazy-load-img.js?

1. 什么鬼?

     一个轻量级的图片懒加载,我个人很是喜欢。

2. 有什么优势?

     1.原生js开发,不依赖任何框架或库

     2.支持将各种宽高不一致的图片,自动剪切成默认图片的宽高

       比如说你的默认图片是一张正方形的图片,则各种宽度高度不一样的图片,自动剪切成正方形

   完美解决移动端开发中,用户上传图片宽高不一致而导致的图片变形的问题。

3. 使用姿势,如下:

 

            // 生成li
            var ul = document.querySelector('#list');
            for (var i = 1; i <= 21; i++) {
                var li = document.createElement('li');
                li.innerHTML = `<img src="./images/default.png" data-src="./images/${i}.jpg">`;
                ul.appendChild(li);
            };

            var lazyLoadImg = new LazyLoadImg({
                el: ul,    // dom元素下的图片
                mode: 'diy',    // 模式: 默认/自定义
                time: 300,    // 多长时间重新监听一次
                complete: true,    // 完成后自己销毁程序
                position: {    // 只要其中一个位置符合条件,都会触发加载机制
                    top: 0,    // 元素距离顶部
                    left: 0,    // 元素距离右边
                    right: 0,    // 元素距离下面
                    bottom: 0    // 元素距离左边
                },
                diy: { //设置图片剪切规则,diy模式时才有效果
                    backgroundSize: 'cover',
                    backgroundRepeat: 'no-repeat',
                    backgroundPosition: 'center center'
                },
                before: function() {

                },
                success: function(el) {
                    el.classList.add('success');
                },
                error: function(el) {
                    el.src = './images/error.png';
                }
            });

            // lazyLoadImg.start() // 开启懒加载程序
            // lazyLoadImg.destroy() // 销毁图片懒加载程序
            // lazyLoadImg.restart() // 销毁后又可以重新开启懒加载程序

 

源码笔记(以下是我改版过的,因为原作者的代码兼容性做的很好,然而现在都使用es6+语法了,所以就...)

/**
 * Created by Sorrow.X on 2017/4/27.
 */

;(function(exports) {
    class LazyLoadImg {
        constructor() {
            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
            this.options = {    // 实例的option属性(默认)
                el: document.querySelector('body'), // 选择的元素
                mode: 'default', // 默认模式,将显示原图,diy模式,将自定义剪切,默认剪切居中部分
                time: 300, // 设置一个检测时间间隔
                done: true, // 页面内所有数据图片加载完成后,是否自己销毁程序,true默认销毁,false不销毁:FALSE应用场景:页面异步不断获取数据的情况下 需要实时监听则不销毁
                diy: { // 此属性,只有在设置diy 模式时才生效
                    backgroundSize: 'cover',
                    backgroundRepeat: 'no-repeat',
                    backgroundPosition: 'center center'
                },
                position: { // 只要其中一个位置符合条件,都会触发加载机制
                    top: 0, // 元素距离顶部
                    right: 0, // 元素距离右边
                    bottom: 0, // 元素距离下面
                    left: 0 // 元素距离左边
                },
                before: function before(el) {// 图片加载之前,执行钩子函数

                },
                success: function success(el) {// 图片加载成功,执行钩子函数

                },
                error: function error(el) {// 图片加载失败,执行的钩子函数

                }
            };
            Object.assign({}, this.options, options);
            Object.assign({}, this.options.diy, options.diy);
            Object.assign(this.options, options);

            // 裁切图片用的
            this.canvas = document.createElement('canvas');
            this.canvas.getContext('2d').globalAlpha = 0.0;
            this.images = {};

            this._timer = true;    // 给实例添加一个_timer属性(定时器)
            this.start();    // 开启懒加载程序
        }

        _testMeet(el) {    // 每个dom元素,一般img元素
            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};    // position对象

            // 取得元素在可视区的位置(相对浏览器视窗)左右上下
            var bcr = el.getBoundingClientRect();
            // padding+border+width
            var mw = el.offsetWidth; // 元素自身宽度
            var mh = el.offsetHeight; // 元素自身的高度
            // 包含了导航栏
            var w = window.innerWidth; // 视窗的宽度
            var h = window.innerHeight; // 视窗的高度

            var boolX = !(bcr.right - options.left <= 0 && bcr.left + mw - options.left <= 0) && !(bcr.left + options.right >= w && bcr.right + options.right >= mw + w); // 左右符合条件
            var boolY = !(bcr.bottom - options.top <= 0 && bcr.top + mh - options.top <= 0) && !(bcr.top + options.bottom >= h && bcr.bottom + options.bottom >= mh + h); // 上下符合条件
            return el.width !== 0 && el.height !== 0 && boolX && boolY;
        }

        _getTransparent(src, w, h) {
            if (this.images[src]) return this.images[src];
            this.canvas.width = w;
            this.canvas.height = h;
            var data = this.canvas.toDataURL('image/png');
            this.images[src] = data;
            return data;
        }

        start() {
            var self = this;    // LazyLoadImg实例存一下

            var options = this.options;    // 配置存一下

            clearTimeout(this._timer); // 清除定时器
            if (!this._timer) return;
            // this._timer 是setTimeout的return flag 推荐采用settimeout的方法,而不是setinterval
            this._timer = setTimeout(function () {
                var list = Array.prototype.slice.apply(options.el.querySelectorAll('[data-src]'));    // 获取el下所有含有data-src属性的标签,且转成数组
                // 如果list.length为0 且页面内图片已经加载完毕 清空setTimeout循环
                if (!list.length && options.done) {    // list有数据就不关闭定时器
                    clearTimeout(self._timer); // 有页面内的图片加载完成了,自己销毁程序
                } else {
                    list.forEach(function (el) {    // 遍历dom
                        // 如果该元素状态为空(dataset HTML5方法 设置、获取属性);并且检测该元素的位置
                        if (!el.dataset.LazyLoadImgState && self._testMeet(el, options.position)) {
                            self.loadImg(el);    // 加载图片
                        };
                    });
                };
                // call it
                self.start();
            }, options.time);
        }

        loadImg(el) {
            var self = this;

            // 加载图片
            var options = this.options;

            el.dataset.LazyLoadImgState = 'start';
            // 执行加载之前做的事情
            options.before.call(this, el);
            var img = new window.Image();
            // 这里是一个坑 dataset.src 实际取的值是 属性data-src data- 是HTML5 DOMStringMap对象
            img.src = el.dataset.src;

            // 图片加载成功
            img.addEventListener('load', function () {
                if (options.mode === 'diy') {
                    el.src = self._getTransparent(el.src, el.width, el.height);
                    options.diy.backgroundImage = 'url(' + img.src + ')';
                    Object.assign(el.style, options.diy);
                } else {
                    el.src = img.src;
                };
                delete el.dataset.src;
                el.dataset.LazyLoadImgState = 'success';
                return options.success.call(self, el);
            }, false);

            // 图片加载失败
            img.addEventListener('error', function () {
                delete el.dataset.src;
                el.dataset.LazyLoadImgState = 'error';
                options.error.call(self, el);
            }, false);
        }

        destroy() {
            // 解除事件绑定,return掉,不会自调用
            delete this._timer;
        }

        restart() {
            this._timer = true;
            this.start();
        }
    };

    exports.LazyLoadImg = LazyLoadImg;
})(window);

当然,原作者狼族小狈的源码如下,棒极了,很是喜欢

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) : (global.LazyLoadImg = factory());
}(this, function() {
    'use strict';

    // 只要其中一个位置符合条件,都会触发加载机制
    var testMeet = function (el) {    // 每个dom元素,一般img元素
        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};    // position对象

        // 取得元素在可视区的位置(相对浏览器视窗)左右上下
        var bcr = el.getBoundingClientRect();
        // padding+border+width
        var mw = el.offsetWidth; // 元素自身宽度
        var mh = el.offsetHeight; // 元素自身的高度
        // 包含了导航栏
        var w = window.innerWidth; // 视窗的宽度
        var h = window.innerHeight; // 视窗的高度

        var boolX = !(bcr.right - options.left <= 0 && bcr.left + mw - options.left <= 0) && !(bcr.left + options.right >= w && bcr.right + options.right >= mw + w); // 左右符合条件
        var boolY = !(bcr.bottom - options.top <= 0 && bcr.top + mh - options.top <= 0) && !(bcr.top + options.bottom >= h && bcr.bottom + options.bottom >= mh + h); // 上下符合条件
        return el.width !== 0 && el.height !== 0 && boolX && boolY;
    };

    var canvas = document.createElement('canvas');
    canvas.getContext('2d').globalAlpha = 0.0;
    var images = {};

    var getTransparent = function (src, w, h) {
        if (images[src]) return images[src];
        canvas.width = w;
        canvas.height = h;
        var data = canvas.toDataURL('image/png');
        images[src] = data;
        return data;
    };

    // 检查instance是否是Constructor的实例
    var classCallCheck = function (instance, Constructor) {
        if (!(instance instanceof Constructor)) {
            throw new TypeError("Cannot call a class as a function");
        };
    };

    // 给构造函数的原型添加方法
    var createClass = function () {
        function defineProperties(target, props) {
            for (var i = 0; i < props.length; i++) {    // 遍历数组
                var descriptor = props[i];    // 描述对象
                descriptor.enumerable = descriptor.enumerable || false;    // 可枚举, 默认不可枚举
                descriptor.configurable = true;    // 可配置
                if ("value" in descriptor) descriptor.writable = true;    // 如果有value, 则可写
                Object.defineProperty(target, descriptor.key, descriptor);    // 给原型添加属性, 有描述符
            }
        }

        return function (Constructor, protoProps, staticProps) {
            if (protoProps) defineProperties(Constructor.prototype, protoProps);    // 原型添加方法
            if (staticProps) defineProperties(Constructor, staticProps);    // 原型添加属性
            return Constructor;    // 返回构造函数
        };
    }();

    // 一级浅拷贝
    var _extends = Object.assign || function (target) {
            for (var i = 1; i < arguments.length; i++) {    // 从第二个参数开始
                var source = arguments[i];    //

                for (var key in source) {
                    if (Object.prototype.hasOwnProperty.call(source, key)) {    // 一级浅拷贝
                        target[key] = source[key];    // 覆盖target的值
                    };
                };
            };

            return target;
        };

    var _win = window;

    var LazyLoadImg = function () {
        // 构造函数 初始化参数
        function LazyLoadImg() {
            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};    //参数个数不为0, 就把第一个参数赋给option
            classCallCheck(this, LazyLoadImg);    // this: LazyLoadImg的实例, LazyLoadImg: 构造函数

            this.options = {    // 实例的option属性(默认)
                el: document.querySelector('body'), // 选择的元素
                mode: 'default', // 默认模式,将显示原图,diy模式,将自定义剪切,默认剪切居中部分
                time: 300, // 设置一个检测时间间隔
                done: true, // 页面内所有数据图片加载完成后,是否自己销毁程序,true默认销毁,false不销毁:FALSE应用场景:页面异步不断获取数据的情况下 需要实时监听则不销毁
                diy: { // 此属性,只有在设置diy 模式时才生效
                    backgroundSize: 'cover',
                    backgroundRepeat: 'no-repeat',
                    backgroundPosition: 'center center'
                },
                position: { // 只要其中一个位置符合条件,都会触发加载机制
                    top: 0, // 元素距离顶部
                    right: 0, // 元素距离右边
                    bottom: 0, // 元素距离下面
                    left: 0 // 元素距离左边
                },
                before: function before(el) {// 图片加载之前,执行钩子函数

                },
                success: function success(el) {// 图片加载成功,执行钩子函数

                },
                error: function error(el) {// 图片加载失败,执行的钩子函数

                }
            };
            // 一级浅拷贝 如果都有 则右面的值 option.position会覆盖this.options.position
            options.position = _extends({}, this.options.position, options.position);    // 设置position值
            options.diy = _extends({}, this.options.diy, options.diy);    // 设置diy值
            _extends(this.options, options);    // 组合一下options数据
            this._timer = true;    // 给实例添加一个_timer属性(定时器)
            this.start();    // 开启懒加载程序
        };

        createClass(LazyLoadImg, [{
                key: 'start',
                value: function start() {
                    var _this = this;    // LazyLoadImg实例存一下

                    var options = this.options;    // 配置存一下

                    clearTimeout(this._timer); // 清除定时器
                    if (!this._timer) return;
                    // this._timer 是setTimeout的return flag 推荐采用settimeout的方法,而不是setinterval
                    this._timer = setTimeout(function () {
                        var list = Array.prototype.slice.apply(options.el.querySelectorAll('[data-src]'));    // 获取el下所有含有data-src属性的标签,且转成数组
                        // 如果list.length为0 且页面内图片已经加载完毕 清空setTimeout循环
                        if (!list.length && options.done) {    // list有数据就不关闭定时器
                            clearTimeout(_this._timer); // 有页面内的图片加载完成了,自己销毁程序
                        } else {
                            list.forEach(function (el) {    // 遍历dom
                                // 如果该元素状态为空(dataset HTML5方法 设置、获取属性);并且检测该元素的位置
                                if (!el.dataset.LazyLoadImgState && testMeet(el, options.position)) {
                                    _this.loadImg(el);    // 加载图片
                                };
                            });
                        };
                        // call it
                        _this.start();
                    }, options.time);
                }
            }, {
                key: 'loadImg',
                value: function loadImg(el) {
                    var _this2 = this;

                    // 加载图片
                    var options = this.options;

                    el.dataset.LazyLoadImgState = 'start';
                    // 执行加载之前做的事情
                    options.before.call(this, el);
                    var img = new _win.Image();
                    // 这里是一个坑 dataset.src 实际取的值是 属性data-src data- 是HTML5 DOMStringMap对象
                    img.src = el.dataset.src;

                    // 图片加载成功
                    img.addEventListener('load', function () {
                        if (options.mode === 'diy') {
                            el.src = getTransparent(el.src, el.width, el.height);
                            options.diy.backgroundImage = 'url(' + img.src + ')';
                            _extends(el.style, options.diy);
                        } else {
                            el.src = img.src;
                        };
                        delete el.dataset.src;
                        el.dataset.LazyLoadImgState = 'success';
                        return options.success.call(_this2, el);
                    }, false);

                    // 图片加载失败
                    img.addEventListener('error', function () {
                        delete el.dataset.src;
                        el.dataset.LazyLoadImgState = 'error';
                        options.error.call(_this2, el);
                    }, false);
                }
            }, {
                key: 'destroy',
                value: function destroy() {
                    // 解除事件绑定
                    delete this._timer;
                }
            },{
                key: 'restart',
                value: function restart() {
                    // 重新开始自调用
                    this._timer = true;
                    this.start();
                }
            }]
        );
        return LazyLoadImg;
    }();

    return LazyLoadImg;
}));

屁话放到现在,还不如看demo来的直接,走你,

移动手机端点我demo

 

ps: 很不错的一个轻量级图片懒加载的库。

     github: https://github.com/lzxb/lazy-load-img

posted @ 2017-04-27 16:04  Sorrow.X  阅读(922)  评论(0编辑  收藏  举报