让前端飞前端框架Layui我爱编程

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

2018-07-07  本文已影响6人  哪种生活可以永远很轻松

1. 问题

1.1. 问题描述

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

1.2. 代码

<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>
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">')
        });
}
});
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回调函数里执行循环的函数function(index, file, result),其都是非同步执行的。但我们需要达到的效果是,在循环完全执行完毕之后再去执行layerphotos();方法。

2.2.0.2. 实现

    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
        }
    },
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。

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

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. 现在的问题

3.7.0.4. 问题本质

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

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

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

我把我在源码上做的改动和一些注释,以及控制台输出语句处理一下,注意注释里加了------的和星号*的是我修改了的地方,粘贴到这里:(为了不影响其他操作,本着防止牵一发而动全身的基本理念,我把修改后的方法命名为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()方法本身的功能的基础上,修改了一下源码。对源码的修改主要有两处:

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

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

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

上一篇 下一篇

猜你喜欢

热点阅读