- [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模块暴露出来的接口只有四个函数:
图片里的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. 初始详细描述
-
初始状态:div为空
-
上传一张图片
-
再上传两张图片
再上传一张点击第二张仍然是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,只是不断更新其内的内容。
- 打开页面,也是会不经过我的调用就执行这个方法:
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
-
然后加载第二张图片。同样的,加载时候首先输出:
可以看出,遍历正常。
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显示正常,控制台输出:
比较可疑的点:为什么会调用两次。
-
进行了一些小操作
3.1 刷新页面
3.2 上传一张图片,再上传一张图片。这个期间不点击图片。控制台输出就是:
和预想的一致:
首先加载了photos()函数,什么操作都没完成。
然后加载出第一张图片之后,调用了这个方法。循环遍历发现img只有一个。
然后加载出第二张图片,调用了这个方法,循环遍历发现img有两个。
一切正常。
3.3 点击第一张图片,控制台输出两组记录:
第一组记录:
第二组记录:
此时,点开的图片显示1/1,是不正常的。但因为我看到了第二组记录其实更新正常了,所以在想,此时重新点开第一张图片,显示应该是正常的1/2。果真如此。
也就是说,我在点击第一张图片的时候,其会执行两遍photos函数,因为有两个img,而当且仅当都执行完的时候,显示才是正确的。
同样的,再新增一张图片,控制台有三组输出,前两组输出中展示的photos都是两张,最后一组输出展示的是三张。
现在的问题变成:为什么我点击第一张图片,click事件注册的函数为什么会多次执行?在一个正常的操作了,控制台输出很简洁:
现在明白了报错src undefined的问题。因为只有在我们点击了已经存在着的照片的时候,新加进去的照片才会被被被发现?被photos.data这个数组所容纳。所以如果你直接点击新加进去的图片,自然是undefined。
哈哈哈我现在的状态是,知道什么时候肯定会出什么错。
- 比如我一次性加载三张图片,先点击第一张图片,此时后台调用三次click事件绑定的方法(click事件作用的元素是同一个元素),而在第一次完成后图片就正常显示了,此时显示的序号是1/1。然后再点击后边任意的图片,由于之前三次click已经完成了,现在无论怎么点击,显示的都是正常的。
- 如果我一次性加载三张图片,直接点击第三张图片,此时后台直接调用第三张图片的点击事件,这时候必然报错,因为没被发现。这个时候被发现的图片是第一张图片,所以第三张图片的信息是undefined。
3.6. 步驟五 打开谷歌浏览器的调试工具,在js设置断点
- 同时上传两张图片。此时,上传图片的before(){}方法内部,遍历div中的所有图片,在每一个图片时候调用一次photos()方法。解决这个问题的办法是将这个函数的调用语句放在done和error回调函数中,控制在图片都加载完成之后调用。
- 之前说,点击一张图片之后,后台会调用若干次click事件注册的方法。现在发现,这个次数和photos()函数的执行次数有关系。我现在先后两次分别上传了5张图片,之后第三次上传图片,点击第一张图片,后台执行三次click事件绑定的函数:第一次显示photos.data只有10张,第二次显示10张,这都是第三次上传的图片还没有被发现的时候的数据,第三次执行,就显示11张,就正常了。
3.7. 总结一下到目前的进度
3.7.0.3. 我的进度
- 我做了什么? --把相对本质的问题暴露出来了,其他问题都得到了解决。
比如页面加载就调用方法。 - 现在的问题
- 展现上:上传一张图片,再上传一张图片,点击第一张图片查看,序号是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框架给了我很多帮助成长,期待更新!