• 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

  • 相关阅读:
    mysql总结
    JVM入门_笔记_狂神说
    spring-与事务管理相关的工具类
    spring-获取连接的工具类
    浏览器调试之 实时更新 browser-sync
    Git: 版本控件
    Visual Studio Code 自定义快捷键,自动生成.vue文件
    Markdown基本语法
    Node.js 平台-服务器 之 Express
    chrome插件之 vue devtools
  • 原文地址:https://www.cnblogs.com/sorrowx/p/6774720.html
Copyright © 2020-2023  润新知