美文网首页让前端飞前端框架Layui我爱编程
Layui框架】layer.photos()遇到动态加载图片的时

Layui框架】layer.photos()遇到动态加载图片的时

作者: 哪种生活可以永远很轻松 | 来源:发表于2018-07-07 14:50 被阅读6次
    • [1. 问题]
      • [1.1 问题描述]
      • [1.2 代码]
    • [2. 第一个问题的解决]
      • [2.1 一开始的解决:]
      • [2.2 最没有错误的解决办法]
        - [2.2.1 解决办法的思路:]
        - [2.2.2 实现]
    • [3. 第二个问题的解决思路过程]
      • [3.1 初始详细描述]
      • [3.2 找错步骤一:看源码]
      • [3.3 步骤二:从浏览器控制台定位错误出现的地方]
      • [3.4 步骤三:根据正常的流程走一遍]
      • [3.5 步骤四:新开一个页面,对比,继续寻找错误:]
      • [3.6 步驟五 打开谷歌浏览器的调试工具,在js设置断点]
      • [3.7 总结一下到目前的进度]
        - [3.7.1 我的进度]
        - [3.7.2 问题本质]
      • [3.8 这个问题的终极解决步骤]
    • [4. 总结]

    1. 问题

    1.1. 问题描述

    当layer.photo()作用的区域内的图片是动态加载产生的的时候,出现两个问题。

    • 一次性加载一个图片之后,方法不生效;第二次加载图片,方法方才生效;且常出现最后一张图片点击之后一直转圈,加载不出来的情况。
    • 点击图片显示图片总共多少张,即i/n中n显示异常。继上一个问题,点击放大查看第一张图片图片,显示1/1张,再点击第二张图片,显示2/2;在加载正常的情况下,若直接点击最后一张图片,则显示2/2张。

    1.2. 代码

    • HTML代码
    <div class="layui-upload">
        <button type="button" class="layui-btn layui-btn-primary" id="uploadpics">选择图片</button>
        <blockquote id="blockquote_pics" class="layui-elem-quote layui-quote-nm" style="display: none; margin-top: 10px;">
            预览图:
            <div class="layui-upload-list" id="pics" lay-filter="pics"></div>
        </blockquote>
    </div>
    
    • 图片上传的js代码
    upload.render({
        elem: '#uploadpics',
        url: '/upload/',
        multiple: true,
        size: 1000 //限制文件大小,单位 KB
            ,
        number: 9,
        before: function(obj) {
            $('#blockquote_pics').show();
            //预读本地文件示例,不支持ie8
            obj.preview(function(index, file, result) {
                $('#pics').append('<img src="' + result + '" alt="' + file.name + '" class="layui-upload-img">')
            });
    }
    });
    
    • 这里定义了一个方法,写在fun.js里。就是封装了的layer.photos()
    function layerphotos() {
            console.log("layer.photos() start.");
            layer.photos({
                photos: '#pics',
                anim: 5 //0-6的选择,指定弹出图片动画类型,默认随机(请注意,3.0之前的版本用shift参数)
            });
        }; 
    

    2. 第一个问题的解决

    2.1. 一开始的解决:

    把图片预览显示的代码放在动态生成代码的一起,紧跟着动态生成图片的代码。

    obj.preview(function(index, file, result) {
        $('#pics').append('<img src="' + result + '" alt="' + file.name + '" class="layui-upload-img">')
        layerphotos();
    }
    

    这个问题,一开始的想法是:在页面加载完成之后执行layerphotos();方法,但根据控制台调试输出的结果发现,页面加载完成的时候方法起作用的div里没有图片,后续加载图片也不会起作用的。

    这个值得注意,用控制台输出console.log()的方法判断js代码的执行顺序。

    但是,这个问题解决了的原因,还是没有想明白。

    目前这个解决方案的不合理之处在于:我如果在图片都加载完成之后,即在preview()函数执行完之后使用photos函数,理论上来说其效果一致,实则不然。考虑这个方法可能是非同步执行。

    2.2. 最没有错误的解决办法

    2.2.0.1. 解决办法的思路:

    layerphotos();方法调用放在图片都加载到页面完成之后,而不是在每一张图片加载之后。

    放在每一张图片加载之后并不合理,我们需要在图片全部加载之后才调用layerphotos();去渲染这个div。要使得图片全部加载完成之后再调用这个方法,我们可以在photos()的三个回调函数中解决。

    • before回调函数里,获取这次加载的图片数量,在循环执行完毕(加载完毕)之后执行layerphotos();方法。
    • error回调函数里,判断一共有多少张照片,确保加载完毕执行layerphotos();方法。
    • done回调函数里,执行layerphotos();方法。

    这里,三个回调函数,以及before回调函数里执行循环的函数function(index, file, result),其都是非同步执行的。但我们需要达到的效果是,在循环完全执行完毕之后再去执行layerphotos();方法。

    2.2.0.2. 实现

    • 修改upload模块源码upload.js,暴露出before回调中获取文件数量的接口
      因为我要在before中动态加载上传的图片,所以要在before回调中获取文件数量。然后在我使用浏览器调试工具仔细分析发现,layui2.3.0版本中,upload模块暴露出来的接口只有四个函数:


      upload模块before回调暴露出的接口

      图片里的fileLength_()是我在源码中找到暴露接口的位置加进去的:

        g = {
            preview: function(e) {
                o.preview(e)
            },
            upload: function(e, i) {
                var t = {};
                t[e] = i, o.upload(t)
            },
            pushFile: function() {
                return o.files = o.files || {}, layui.each(o.chooseFiles, function(e, i) {
                    o.files[e] = i
                }), o.files
            },
            resetFile: function(e, i, t) {
                var n = new File([i], t);
                o.files = o.files || {}, o.files[e] = n
            }
            //--------------------------add------------------------
            ,
            fileLength_: function() {
                return o.fileLength
            }
        },
    
    • 获取此次加载的图片数量fileLength
    • 获取目前一共加载了的图片的数量pics_num_amount
    • 在before的循环中计数,或者error中计数,得到已经加载完毕的图片数量 pics_current
    • pics_current === pics_num的时候,调用layerphotos();方法。因为此时,所有图片在div中的加载渲染都已经确保完成。
    before: function(obj) {
            var fileLength = obj.fileLength_();     //获取本次上传的图片数量
            pics_num_amount = pics_num_load + fileLength;   //目前一共加载了的图片的数量
            console.log("pics_num_amount = " + pics_num_amount + ", fileLength = " + fileLength);
            $('#blockquote_pics').show();           //显示这div
            
            //预读本地文件示例,不支持ie8
            obj.preview(function(index, file, result) {
                $('#pics').append('<img id="img_o_' + index + '" src="' + result + '" alt="' + file.name + '" class="layui-upload-img">')
                pics_cur ++;                        //加载中  实时更新的已经加载了的图片总数
                console.log("pics_num_amount = " + pics_num_amount + " pics_cur = " + pics_cur);
                if(pics_cur == pics_num_amount) {       //都加载完毕了
                    layerphotos('pics');                    //调用layer.photos() 此时调用能保证div中的图片加载完全了
                }
            });
            pics_num_load += fileLength;
        },
    

    3. 第二个问题的解决思路过程

    3.1. 初始详细描述

    1. 初始状态:div为空


      初始div不可见
    2. 上传一张图片


      上传一张图片
      点击图片正常显示
    3. 再上传两张图片


      现在是三张
      点击第一张仍然显示1/1
      点击第二张显示2/3正常

      再上传一张点击第二张仍然是2/3,不正确。点击第四张后恢复正确。


      再上传第五张,直接点第五张图片,显示加载中
    • 出现控制台报错:
    Uncaught TypeError: Cannot read property 'src' of undefined
        at Object.r.photos (layer.js:2)
        at HTMLImageElement.<anonymous> (layer.js:2)
        at HTMLDivElement.dispatch (jquery-3.2.1.min.js:3)
        at HTMLDivElement.q.handle (jquery-3.2.1.min.js:3)
    

    就这样,卡在了这个地方。

    3.2. 找错步骤一:看源码

    layer.js中有photos()方法

    3.3. 步骤二:从浏览器控制台定位错误出现的地方

    s.loadi = r.load(1, {
            shade: !("shade"in t) && .9,
            scrollbar: !1
        }),
        o(u[d].src, function(n) {
            r.close(s.loadi),
            s.index = r.open(i.extend({
                type: 1,
                id: "layui-layer-photos",
                area: function() {
                    var a = [n.width, n.height]
                        , o = [i(e).width() - 100, i(e).height() - 100];
                    if (!t.full && (a[0] > o[0] || a[1] > o[1])) {
                        var r = [a[0] / o[0], a[1] / o[1]];
                        r[0] > r[1] ? (a[0] = a[0] / r[0],
                        a[1] = a[1] / r[0]) : r[0] < r[1] && (a[0] = a[0] / r[1],
                        a[1] = a[1] / r[1])
                    }
                    return [a[0] + "px", a[1] + "px"]
                }(),
                ...
    },
    

    看目前的状况,似乎是这样的:

    当页面动态加载出图片,此时

    photos() 这个方法似乎是异步方法,不是同步方法。其传入的参数t,也就是图片数据,可以用这行代码打印出:

            console.log(t.photos.data);
    

    不对不对,不是异步方法。

    这个方法是在点击图片这个事件被触发的时候调用的方法 。!

    if(n || p.on("click", t.img, function() {
        var e = i(this),
            n = e.attr("layer-index");
        r.photos(i.extend(t, {    //就是在这里调用的
            photos: {
                start: n,
                data: u,
                tab: t.tab
            },
            full: t.full
        }), !0), h()
    }), !n) return
    

    3.4. 步骤三:根据正常的流程走一遍

    我写了四处注释,加载一个在页面加载完毕就能把所有图片都加载好的页面,其效果是这样的:

    首先,会进入这个方法。但并不是通过我调用方法的入口进入方法的。(就是说我写的console.log("layer.photos() start.");这一句代码并没有执行;我写在layui.use(layer)中的console.log(" * * * * * * layui.use('layer') * * *");

    001 -- photos 方法调用--r.photos = function(t, n, a)   &  t.photos.data:
    undefined
    002 ---图片数据u=f.data: u.length = 0 startIndex:d = 0 ---s.imgIndex = 1
    

    紧接着,由于加载出来了四组图片(4个div),会调用四次layer.photos()方法
    此时加载了layer模块:

    * * * * * *  layui.use('layer') * * *
    

    然后,四个div的执行结果大同小异,以第一个div为例:

    photos 方法启动 & id = #friendpics0
    layer.js:531  001 -- photos 方法调用--r.photos = function(t, n, a)   &  t.photos.data:
    layer.js:532 undefined
    layer.js:548 002 ---图片数据u=f.data: u.length = 0 startIndex:d = 0 ---s.imgIndex = 1
    layer.js:557 003 -- 方法h()调用  & e = 0
    layer.js:557 003 -- 方法h()调用  & e = 1
    layer.js:557 003 -- 方法h()调用  & e = 2
    VM10684:122 photos 方法执行完毕 & id = #friendpics0
    

    可以看出,注册这个事件的主要逻辑应该在h()这个方法内部

    h = function() {
                            
            u = [], p.find(t.img).each(function(e) {
                console.log("003 -- 方法h()调用  & e = " + e);
                var t = i(this);
                t.attr("layer-index", e), u.push({
                    alt: t.attr("alt"),
                    pid: t.attr("layer-pid"),
                    src: t.attr("layer-src") || t.attr("src"),
                    thumb: t.attr("src")
                })
            })
        };
    

    这个方法内部,通过循环遍历这个div内的所有img,其中e是index。
    然后点击第一张图:

    图片显示、序号都正常
    控制台消息:
    这是点击第一张图片后的控制台消息
    点击另两张图片的效果也是一样的。首先004调用了点击事件所注册的函数,在这个函数内部,调用了photos()方法。

    至此,一次完整的,正确的操作完毕。问题就出在动态加载。

    3.5. 步骤四:新开一个页面,对比,继续寻找错误:

    依旧,一步步记录。值得注意的是,这也页面,我只有一个div,只是不断更新其内的内容。

    1. 打开页面,也是会不经过我的调用就执行这个方法:
     001 -- photos 方法调用--r.photos = function(t, n, a)   &  t.photos.data:
    layer.js:532 undefined
    layer.js:548 002 ---图片数据u=f.data: u.length = 0 startIndex:d = 0 ---s.imgIndex = 1
    

    1.1. 动态加载出一张图片,控制台输出:

    VM372:4 element.render()
    VM372:5 layer.photos() start.
    layer.js:531  001 -- photos 方法调用--r.photos = function(t, n, a)   &  t.photos.data:
    layer.js:532 undefined
    layer.js:548 002 ---图片数据u=f.data: u.length = 0 startIndex:d = 0 ---s.imgIndex = 1
    layer.js:557 003 -- 方法h()调用  & e = 0
    VM372:14 layer.photos() end.
    

    首先更新渲染(不知道有没有用),然后通过我的调用启动了方法。因为只有一张图片,e = 0说明此时,这个用来遍历div的循环正常执行。

    1.2. 然后点击这张图片,显示正常,此时控制台输出是:

    layer.js:571 004 -- p.on(click, t.img, function() {}  &  e = [object Object]  n = e.attr(layer-index) = 0
    layer.js:531  001 -- photos 方法调用--r.photos = function(t, n, a)   &  t.photos.data:
    layer.js:532 [{…}]
    layer.js:548 002 ---图片数据u=f.data: u.length = 1 startIndex:d = 0 ---s.imgIndex = 1
    layer.js:608 005 -- 这里是几个函数定义之后 -- u = : 
    layer.js:609 [{…}]
    layer.js:610 --u.length = 1 d = 0src: data:image/jpeg;base64,.../这里的记录是一段乱码,超长,疑点1/...
    layer.js:557 003 -- 方法h()调用  & e = 0
    
    控制台输出
    1. 然后加载第二张图片。同样的,加载时候首先输出:


      加载第二张图片之后的输出

      可以看出,遍历正常。

    2.1. 然后点击第一张图片,控制台输出:

    第一张图片点击之后的控制台输出
    004点击了index为0的图片,也就是第一张图片
    001方法调用,显示photos.data的数据有两条,正常
    002 ---图片数据u=f.data: u.length = 2 startIndex:d = 0 ---s.imgIndex = 1长度为2说明有两张图片,显示正常;当前图片的编号index为1,这也正常。
    003循环正常
    但点击显示不正常,显示的是 1/1 .
    这个点击事件有一个疑点,就是它会执行两次,控制台上会有两条一模一样的输出,我这里只截取了一个输出。

    2.2. 然后点击第二张图片,控制台输出:

    第二张图片点击之后的控制台输出
    004点击index为1的图片,也就是第二张图片
    001方法调用,显示photos.data的数据有两条,正常
    002 ---图片数据u=f.data: u.length = 2 startIndex:d = 1 ---s.imgIndex = 2当前图片编号2,正常。
    003循环正常
    点击图片显示正常2/2

    2.3. 然后再点击第一张图片,1/2显示正常,控制台输出:


    再点击第一张图片之后的显示

    比较可疑的点:为什么会调用两次。

    1. 进行了一些小操作
      3.1 刷新页面
      3.2 上传一张图片,再上传一张图片。这个期间不点击图片。控制台输出就是:


      先后上传两张图片时候的控制台输出

      和预想的一致:
      首先加载了photos()函数,什么操作都没完成。
      然后加载出第一张图片之后,调用了这个方法。循环遍历发现img只有一个。
      然后加载出第二张图片,调用了这个方法,循环遍历发现img有两个。
      一切正常。
      3.3 点击第一张图片,控制台输出两组记录:
      第一组记录:


      注意中间部分获取到的photos.data只有一条记录
      第二组记录:
      注意中间部分获取到的photos.data两条记录
      此时,点开的图片显示1/1,是不正常的。但因为我看到了第二组记录其实更新正常了,所以在想,此时重新点开第一张图片,显示应该是正常的1/2。果真如此。

    也就是说,我在点击第一张图片的时候,其会执行两遍photos函数,因为有两个img,而当且仅当都执行完的时候,显示才是正确的。

    同样的,再新增一张图片,控制台有三组输出,前两组输出中展示的photos都是两张,最后一组输出展示的是三张。

    现在的问题变成:为什么我点击第一张图片,click事件注册的函数为什么会多次执行?在一个正常的操作了,控制台输出很简洁:


    正常操作

    现在明白了报错src undefined的问题。因为只有在我们点击了已经存在着的照片的时候,新加进去的照片才会被被被发现?被photos.data这个数组所容纳。所以如果你直接点击新加进去的图片,自然是undefined。

    哈哈哈我现在的状态是,知道什么时候肯定会出什么错。

    • 比如我一次性加载三张图片,先点击第一张图片,此时后台调用三次click事件绑定的方法(click事件作用的元素是同一个元素),而在第一次完成后图片就正常显示了,此时显示的序号是1/1。然后再点击后边任意的图片,由于之前三次click已经完成了,现在无论怎么点击,显示的都是正常的。
    • 如果我一次性加载三张图片,直接点击第三张图片,此时后台直接调用第三张图片的点击事件,这时候必然报错,因为没被发现。这个时候被发现的图片是第一张图片,所以第三张图片的信息是undefined。

    3.6. 步驟五 打开谷歌浏览器的调试工具,在js设置断点

    1. 同时上传两张图片。此时,上传图片的before(){}方法内部,遍历div中的所有图片,在每一个图片时候调用一次photos()方法。解决这个问题的办法是将这个函数的调用语句放在done和error回调函数中,控制在图片都加载完成之后调用。
    2. 之前说,点击一张图片之后,后台会调用若干次click事件注册的方法。现在发现,这个次数和photos()函数的执行次数有关系。我现在先后两次分别上传了5张图片,之后第三次上传图片,点击第一张图片,后台执行三次click事件绑定的函数:第一次显示photos.data只有10张,第二次显示10张,这都是第三次上传的图片还没有被发现的时候的数据,第三次执行,就显示11张,就正常了。

    3.7. 总结一下到目前的进度

    3.7.0.3. 我的进度

    1. 我做了什么? --把相对本质的问题暴露出来了,其他问题都得到了解决。
      比如页面加载就调用方法。
    2. 现在的问题
    • 展现上:上传一张图片,再上传一张图片,点击第一张图片查看,序号是1/1,此时第二张图片尚未被发现,重新打开第一张图片,序号是1/2。
    • 后台执行上:上传一张图片,再上传一张图片,点击第一张图片查看,此时会调用两次点击事件绑定的方法。第一次调用的时候显示只有一张图片,此时图片显示出来;第二次调用的时候显示有两张图片,此时图片因为已经显示出来了,所以正确的没法显示。

    3.7.0.4. 问题本质

    分析到现在,问题很明确了。在我多次加载图片之后,点击第一张图片,此时后台会依次“发现”所有被加载的图片,然而第一次过程之后就展示了图片,所以导致序号显示不正确,以及加载完毕就点击后加载的图片会显示不出来。

    解决思路是,控制图片显示的时机。

    3.8. 这个问题的终极解决步骤

    • 原本的做法 ( layer.js )
      点击图片,依次“发现”被加载的图片。
    • 现在的做法,点击图片,判断所有被加载的图片是不是都被“发现”了,如果是,再执行图片显示的操作。

    我把我在源码上做的改动和一些注释,以及控制台输出语句处理一下,注意注释里加了------的和星号*的是我修改了的地方,粘贴到这里:(为了不影响其他操作,本着防止牵一发而动全身的基本理念,我把修改后的方法命名为photos_dynamic() (layer.js))

    var pic_amount = 0;                     //------ 变量定义  图片计数器
    var pic_acount_valid = false;           //------ 变量定义  图片计数器有效检验
    r.photos_dynamic= function(t, n, a) {
        ...
        if(t = t || {}, t.photos) {          //t就是传入的photos数据
            if(delete t.success, l) {} else {
                var p = i(t.photos),
                h = function() {                //h函数的定义 遍历所有图片 设置属性 因为要遍历所有图片,所以在这里计数
                    pic_acount_valid = false;           //* 计数器无效
                    u = [], p.find(t.img).each(function(e) {    //循环
                        pic_amount = e+1;               //* 计数器计数
                        ...};
                pic_acount_valid = true;     //* ------ 当h()遍历完所有的图片,也就是图片计数器数值有效,置为true
                ...
                if(n || p.on("click", t.img, function() {        //图片被点击时候调用的函数 
                    ...                                         //这里调用了photos函数 用来“发现所有的图片” 也就是说点击图片之后执行语句会在这个方法内部再次调用photos_dynamic()
                    r.photos_dynamic(i.extend(t, { ... },}),!0), h()}), !n) return
        }
        ... //一些函数的定义 前一张后一张等
        if(pic_acount_valid === true, u.length === pic_amount) {        //------ 判断计数器有效,且当前被发现的数据u的差哪个都和计数器计数数量一致 说明所有图片都已经被发现
            console.log("006 --成功 -- 自己加的判断:pic_acount_valid === " + pic_acount_valid + " " + "u.length = " + u.length + " " + "pic_amount = " + pic_amount);
            o(u[d].src, function(n) {
                r.close(s.loadi), s.index = r.open(i.extend({ ... }(),               //设置区域大小等  用layer.open()打开弹出窗
                }, t))                  //设置open()的一些属性
            },
                ...     //设置关闭弹窗的一下属性 
        }
        else {
            console.log("006 --失败 -- 自己加的判断:pic_acount_valid === " + pic_acount_valid + " " + "u.length = " + u.length + " " + "pic_amount = " + pic_amount);
        }}}, ... //其余的一些函数定义等
    }()
    

    就是在这里判断一下计数器和图片数量是不是一致的,只有是一致的的时候才执行open()方法。

    4. 总结

    截至目前,问题都得到了解决。我在确保不影响模块本身的功能,以及layer.photos()方法本身的功能的基础上,修改了一下源码。对源码的修改主要有两处:

    • upload.js模块 暴露出获取文件数量的接口;
    • layer.js模块,新增了layer在处理页面动态加载出的数据的时候的合理处理。

    这次遇到的这个问题,对于我的项目来说并不是关键的东西,但我认为这是比较基本的应该实现的效果,所以我查了源码,用了浏览器调试,甚至修改了框架代码。

    尽管如此,我并不认为我这是最好的解决。

    • 我不知道为什么框架没有暴露出获取文件数量的接口,也许是出于自身考虑。我们的确可以在循环中设置计数器,这样虽然能获取到文件数量,但由于这个回调函数是非同步执行,我如果将这个数据运用到其他回调中,必然会出现问题。
    • 如果是在一个div中动态加载出来的图片,框架原本的做法,会多次调用图片的点击事件。
      • 合理操作中,当页面加载图片完毕,点击某一张图片,触发这个图片的点击事件,此时点击事件内就应该已经捕获到这个div最新的所有图片。
      • 但实际中,点击图片,会多次触发这个图片的点击事件(见文中的截图),并且,图片加载操作有几次,此时就触发了几次点击事件。并且前几次触发点击事件时候事件所捕获的div内的内容是上一次更新的内容,直到最后一次点击事件被触发。这是为什么呢?

    layui框架给了我很多帮助成长,期待更新!ayui框架给了我很多帮助成长,期待更新!

    相关文章

      网友评论

      本文标题:Layui框架】layer.photos()遇到动态加载图片的时

      本文链接:https://www.haomeiwen.com/subject/hdbwuftx.html