• [jQuery插件]手写一个图片懒加载实现


    教你做图片懒加载插件

    那一年

    那一年,我还年轻 刚接手一个ASP.NET MVC 的 web 项目,
    (C#/jQuery/Bootstrap)
    并没有做 web 的经验,没有预留学习时间,
    (作为项目组长的我,主要C#客户端经验)
    项目来了只能硬上,把JS比作C#来写(哭

    当时接到请求协助解决一个图片显示慢的问题时,给出了一个后来看来不好的解决方案。

    那个项目结束一段时间之后,内心愧疚的我把这个图片懒加载的jQuery插件发了过去。(现在我JS已经搞很溜了你信不信,哇哈哈~)


    jQuery 的辉煌时代已成过去,
    但不能否定这是个非常优秀的 js 库,
    而且仍有大量旧网站在用它。

    今天聊一下如何开发一个基于 jQuery 的图片懒加载插件(当然只要你想也可以脱离jQuery用纯原生js)。

    工作原理

    如何才能懒加载呢?

    • 页面初始加载时统一设置一个fake图片
    • 在data属性上记录真实图片的url地址
    • 在预期的时机请求相应的图片资源

    以上,就是一个图片懒加载插件的大致工作原理。

    准备

    滚动加载

    假设一个这样的场景:

    • 页面有大量图片
    • 出现有滚动条
    • 可视范围内的图片要加载
    • 可视范围外的图片仍为占位图
    • 当滚动时,可视范围外的图片进入(或即将进入)可视范围则发起请求获取图片并显示

    带滚动条的图片展示页面

    先创建一个demo-lazyload-window-scroll.html的图片展示页面,效果如下:

    demo-page-no-image

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>demo-lazyload</title>
        
        <script src="https://cdn.bootcss.com/jquery/2.0.3/jquery.min.js"></script>
    
        <style>
            .container{
                padding:10px 50px;
                display: flex;
                flex-direction: column;
                align-items: center;
                height: 300px;
                overflow-y: auto;
                border: 1px solid gray;
                background-color: lightskyblue;
            }
    
            .img{
                flex: 1;
                margin: 10px 0;
                 100%;
                height: 100%;
            }
        </style>
    </head>
    <body>
        <h2>用于展示图片的页面,检验我们的懒加载插件</h2>
        
        <div id="container" class="container">
            <img src="./blank_img.png" alt="" class="img" data-lazysrc="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1575255351&di=f793ab57355f48f8dac4dd9c84df3f79&imgtype=jpg&er=1&src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2Fa%2F591580f307d75.jpg">
            <img src="./blank_img.png" alt="" class="img" data-lazysrc="http://pic41.nipic.com/20140514/13102479_214919550103_2.jpg">
            <img src="./blank_img.png" alt="" class="img" data-lazysrc="http://pic41.nipic.com/20140514/13102479_215137440121_2.jpg">
            <img src="./blank_img.png" alt="" class="img" data-lazysrc="http://pic38.nipic.com/20140213/9422601_094926378000_2.jpg">
            <img src="./blank_img.png" alt="" class="img" data-lazysrc="http://pic41.nipic.com/20140505/9448607_213048662000_2.jpg">
            <img src="./blank_img.png" alt="" class="img" data-lazysrc="https://setouchifinder.com/ja/wp-content/uploads/sites/2/2018/03/01onokorojima.jpg">
            <img src="./blank_img.png" alt="" class="img" data-lazysrc="http://img.pconline.com.cn/images/upload/upc/tx/photoblog/1703/23/c15/40225820_1490275789503.jpg">
        </div>
    
    </body>
    </html>
    
    • 通过CDN引入了jQuery
    • container里有7张图片,src都是一个占位用空图片
    • data-lazysrc的值才是真正要显示的图片,我们插件里就取这个值来用

    预想的用法

    在上面的html页面,

    • 引入我们将要完成的插件js
    • 调用混入进jQuery的lazyLoad方法

    就是这么简单

    <script src="./jquery.lazyloader.js"></script>
    
    <script>
      window.onload = function() {
        $('.img').lazyLoad()
      }
    </script>
    

    IIFE

    在没有js模块化的年代,人们通过IIFE来解决作用域问题。

    IIFE全称Immediately Invoked Function Expression
    即立即执行函数表达式,通过用一个立即执行的函数的形式,
    包裹起这个函数的作用域。

    (function() {
        // coding here
        root.xx = function(){}
    })(root)
    

    然后通过参数传入全局对象等参数,
    将插件对象挂载到全局对象上,
    以实现在页面上直接使用。

    那时的jQuery等库也都是这种风格形式的,插件也不例外。

    开发jQuery的插件,要用到jQuery的extend方法,传入一个对象,就可以将对象的属性方法给扩展到现有jQuery对象上。

    (function() {
        // coding here
        $.fn.extend({
          lazyLoad: function() {}
        })
    })()
    

    剩下的就是去实现我们的lazyLoad方法了

    具体实现

    lazyLoad

    创建js文件:jquery.lazyloader.js

    这时我们要考虑如下几点:

    • 页面加载时要显示可视区域的图片
    • 其它等滚动时再获取并显示(如果有的话)

    这样,任务可以分解为:

    • 工具函数:判断是否进入可视区域
    • 公用函数:获取未显示img元素的真实图片地址,并显示(设到src属性)
    • 监听滚动:调用显示图片的函数逻辑即可

    同时还要注意对已通过懒加载显示完的图片不在进行处理

    (function() {
        // coding here
        $.fn.extend({
          lazyLoad: function() {
            var that = this
            
            // 初始化时加载一次进入可视区域的图片
            showRealImage(this)
            
            // 滚动条拖动时显示未加载且进入(即将进入)可视区域的图片
            $(window).scroll(function(){
                showRealImage(that)
            })
          }
        })
    
        /**
         * 修改进入可视区域的图像元素的真实图像地址使之显示
         *
         * @param {*} $eleList
         */
        function showRealImage($eleList){
            if(!$eleList || !$eleList.length){
                return
            }
    
            // 先过滤,后处理
            $eleList.filter((i, ele)=>{
                var $img = $(ele)
                // 作为处理对象的条件:
                //    1.未加载
                //    2.进入可视区域
                //    3.DOM标签为img
                return !$img.attr('loaded') && isInSight($img) && ele.nodeName.toLowerCase() === 'img'
            }).each(function(i,ele){
                var $img = $(ele)
                var source = $img.data('lazysrc')
                if(source){
                    // 显示真实图片
                    $img.attr('src', source)
                }
                // 设为已加载,之后不再需要处理
                $img.attr('loaded', true)
            })
            
            /**
             *判断图像元素是否进入可视区域(包含预加载Offset的距离)
            *
            * @param {*} $node
            */
            function isInSight($node){
                return ($node.offset().top - 50) <= $(window).height()+$(window).scrollTop();
            }
        }
        
    })()
    

    现在就可以打开demo-lazyload-window-scroll.html试一下效果了。

    为了效果明显,可以将网络调为3G。

    验证方法

    F12打开页面调试工具,通过watch查看图片的src,可以看到初始状态下只有第一张是显示了真是图片的;
    一边拖动滚动条,一边刷新watch的结果,可以看到在距离马上就要显示下一张的时候,就会加载出它的真实图片。

    存在的问题

    虽然能够看到效果了,但是:

    • 这里我们用了固定的offset值
    • 还用了默认的图片父元素/容器:window

    实际上的情况肯定要复杂,这样可不是个合格的插件。

    比如当显示图片的区域只是一部分而不是整个window的时候,这里就会失效了。

    改进

    针对上面提到的情况,我们创建另外一个html文件:
    demo-lazyload-container-scroll.html

    在这个html文件里,图片显示区域作为子元素被一个div包裹。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>demo-lazyload</title>
        
        <script src="https://cdn.bootcss.com/jquery/2.0.3/jquery.min.js"></script>
    
        <style>
            .container{
                padding:10px 50px;
                display: flex;
                flex-direction: column;
                align-items: center;
                height: 300px;
                overflow-y: auto;
                border: 1px solid gray;
                background-color: lightskyblue;
            }
    
            .img{
                flex: 1;
                margin: 10px 0;
                 100%;
            }
        </style>
    </head>
    <body>
        <h2>用于展示图片的页面,检验我们的懒加载插件(非window滚动)</h2>
        
        <div id="container" class="container">
            <img src="https://s2.ax1x.com/2019/11/25/MXLO6f.png" alt="" class="img" data-lazysrc="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1575255351&di=f793ab57355f48f8dac4dd9c84df3f79&imgtype=jpg&er=1&src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2Fa%2F591580f307d75.jpg">
            <img src="https://s2.ax1x.com/2019/11/25/MXLO6f.png" alt="" class="img" data-lazysrc="http://pic41.nipic.com/20140514/13102479_214919550103_2.jpg">
            <img src="https://s2.ax1x.com/2019/11/25/MXLO6f.png" alt="" class="img" data-lazysrc="http://pic41.nipic.com/20140514/13102479_215137440121_2.jpg">
            <img src="https://s2.ax1x.com/2019/11/25/MXLO6f.png" alt="" class="img" data-lazysrc="http://pic38.nipic.com/20140213/9422601_094926378000_2.jpg">
            <img src="https://s2.ax1x.com/2019/11/25/MXLO6f.png" alt="" class="img" data-lazysrc="http://pic41.nipic.com/20140505/9448607_213048662000_2.jpg">
            <img src="https://s2.ax1x.com/2019/11/25/MXLO6f.png" alt="" class="img" data-lazysrc="https://setouchifinder.com/ja/wp-content/uploads/sites/2/2018/03/01onokorojima.jpg">
            <img src="https://s2.ax1x.com/2019/11/25/MXLO6f.png" alt="" class="img" data-lazysrc="http://img.pconline.com.cn/images/upload/upc/tx/photoblog/1703/23/c15/40225820_1490275789503.jpg">
        </div>
    
        <script src="./jquery.lazyloader.js"></script>
    
        <script>
    
            window.onload = function() {
                $('.img').lazyLoad({
                    preOffset: 80,
                    container: document.getElementById('container')
                })
            }
    
        </script>
    </body>
    </html>
    

    页面效果图:

    • 上面将占位图片由本地文件换为了线上图片,对于验证没有影响。
    • 这次在调用懒加载方法时,传入了自定义内容作参数:
      • 预加载Offset的距离:offset
        当拖动滚动条,下一张图片距离要显示还有这些像素的时候开始加载
      • container元素:container
        图片区域的父元素,滚动对象

    页面中script调用处:

    <script>
    ​
        window.onload = function() {
            $('.img').lazyLoad({
                preOffset: 80,
                container: document.getElementById('container')
            })
        }
    </script>
    

    详情参照注释文字,完整的实现如下:

    /**
     * 图片懒加载jQuery插件
     *
     * 2019-07-25
     * CoderMonkie
     */
    
    (function(){
    
        // 配置信息
        var options = {}
    
        /**
         * 默认配置
         */
        function defaultOptions(){
            return {
                preOffset: 100,
                container: window
            }
        }
        
        // 扩展 lazyLoad 方法
        $.fn.extend({
            lazyLoad: function(setting){
                var that = this
    
                options = defaultOptions()
    
                // 确保参数合法的情况下
                if(setting && toString.call(setting) === '[object Object]') {
    
                    // 确保参数里设定项目的值的类型正确
                    //   preOffset:数值(负数没有意义,NaN更不符合要求: >= 0)
                    //   container:DOM元素
    
                    if(setting.preOffset && typeof(setting.preOffset) === 'number' && setting.preOffset >= 0){
                        options.preOffset = setting.preOffset;
                    }
    
                    if(setting.container && setting.container.nodeType){
                        options.container = setting.container
                    }
                }
    
                // 初始化时加载一次进入可视区域的图片
                showRealImage(this)
    
                // 滚动条拖动时显示未加载且进入(即将进入)可视区域的图片
                $(options.container).scroll(()=>{
                    showRealImage(that)
                })
            }
        })
    
        /**
         *修改进入可视区域的图像元素的真实图像地址使之显示
         *
         * @param {*} $eleList
         */
        function showRealImage($eleList){
            if(!$eleList || !$eleList.length){
                return
            }
    
            // 先过滤,后处理
            $eleList.filter((i, ele)=>{
                var $img = $(ele)
                // 作为处理对象的条件:
                //    1.未加载
                //    2.进入可视区域
                //    3.DOM标签为img
                return !$img.attr('loaded') && isInSight($img) && ele.nodeName.toLowerCase() === 'img'
            }).each(function(i,ele){
                var $img = $(ele)
                var source = $img.data('lazysrc')
                if(source){
                    // 显示真实图片
                    $img.attr('src', source)
                }
                // 设为已加载,之后不再需要处理
                $img.attr('loaded', true)
            })
            
            /**
             *判断图像元素是否进入可视区域(包含预加载Offset的距离)
            *
            * @param {*} $node
            */
            function isInSight($node){
                return ($node.offset().top - options.preOffset) <= $(options.container).height() + $(options.container).scrollTop();
            }
        }
    })()
    

  • 相关阅读:
    socketserver源码简介
    Python中实现switchcase
    maven知识整理
    架构师成长之路(5)--如何获取知识(方法)
    架构师成长之路(4)--知识体系(方法)
    python 定义函数
    Git 使用规范流程
    Mybatis内置的日志工厂提供日志功能
    Log4j配置详解
    【log4j2 加载配置文件】 加载配置文件的三种方法
  • 原文地址:https://www.cnblogs.com/CoderMonkie/p/image-lazyload-jquery-plugin.html
Copyright © 2020-2023  润新知