应广大读者建议,已经将该项目源码提交到地址:
https://github.com/devilyouwei/dashen
与本博客相关的多图压缩上传代码在dashen/service/ask.html,请解压项目并移动到hbuilder中打开。
欲实现效果图
提出需求点:
- 用户可*添加删除替换多张图片,并且显示相应缩略图,限制为8张
- 用户可选择压缩图或直接上传原图功能
- 返回提醒用户会丢失填写的信息
下面一个个实现上述需求,从简单到复杂:
需求3:
用户返回弹出提示框,使用mui.confirm如下:
var oldBack = mui.back;
mui.back=function(e){
mui.confirm("尚未提交,返回后将会丢失填写内容物,是否返回?","返回确认",['返回','取消'],function(e){
if(e.index==0)
oldBack();
})
}
(以上代码买应写在mui.plusReady()之中,因为里面需要用到html5+的方法,mui.back()就是5+方法)
第一步克隆了一个mui.back,因为下面他自己会被重写了,而真正返回的时候还是需要用到原来的mui.back()!
重写是为了再返回前执行一段逻辑,按照官方的说法,confirm弹出是异步执行(非阻塞)所以另外一种在mui.init({beforeBack:function})的方式是不合适,因为beforeBack选项要求的是阻塞的,可能会导致还没有按下confirm中的按钮,因为执行了return true而退出了,弹出窗口就显得没有意义了!只能使用重写back方法的办法了!
mui.confirm传入四个参数,提示主内容,标题,按钮数组,回调函数(对按钮数组的下表进行判断)
*增删改图片
qq空间发表说说可以携带图片,通过缩略图的形式让用户修改自己要上传的图片,不过腾讯做的那个高级多了,还可以拖拽图片打开大图编辑等等,在此只实现最简单的功能。
专门写一个函数来实现这个功能:init_image_add()
//初始化图片添加器
function init_image_add() {
//上传图片上限,超过不现实加号
if(question.files.length >= IMG_MAX_NUM)
return;
var placeholder = document.createElement('div');
placeholder.setAttribute('class', 'image-item space');
//删除图片
var closeButton = document.createElement('div');
closeButton.setAttribute('class', 'image-close');
closeButton.innerHTML = 'X';
//小X的点击事件
closeButton.addEventListener('tap', function(event) {
removeFile(getChildrenIndex(placeholder)); //删除实际图片数组元素,先删除数组中的
imageList.removeChild(placeholder); //删除ui,必须后删除,否则节点会找不到了
if(question.files.length >= IMG_MAX_NUM - 1)
init_image_add();
event.stopPropagation();
}, false);
placeholder.addEventListener('tap', function(event) {
var btnArray = [{
title: "相册"
}, {
title: "拍照"
}];
//actionsheet
plus.nativeUI.actionSheet({
title: "选择图片",
cancel: "取消",
buttons: btnArray
}, function(e) {
var i = e.index;
switch(i) {
case 0:
break;
case 1:
plus.gallery.pick(function(e) {
plus.io.resolveLocalFileSystemURL(e, function(entry) {
var url = entry.toLocalURL();
var name = url.substr(e.lastIndexOf('/') + 1);
//压缩取得缩略图
plus.zip.compressImage({
src: url,
dst: '_doc/' + name,
overwrite: true,
quality: 50,
height: '300px',
clip: {
top: "25%",
left: "25%",
width: "300px",
height: "300px"
}
}, function(zip) {
placeholder.style.backgroundImage = "url('" + zip.target + "')";
if(!placeholder.classList.contains('space')) { //已有图片
exFile(getChildrenIndex(placeholder), url);
} else { //加号
placeholder.classList.remove('space');
addFile(url);
init_image_add();
}
}, function(zip) {
mui.toast('压缩失败!');
});
}, function(e) {
console.log("读取相册文件错误:" + e.message);
});
}, function(e) {
console.log(e.message);
}, {});
break;
case 2:
plus.camera.getCamera().captureImage(function(e) {
plus.io.resolveLocalFileSystemURL(e, function(entry) {
var url = entry.toLocalURL();
var name = url.substr(e.lastIndexOf('/') + 1);
//压缩
plus.zip.compressImage({
src: url,
dst: '_doc/' + name,
overwrite: true,
quality: 50,
height: '300px',
clip: {
top: "25%",
left: "25%",
width: "300px",
height: "300px"
}
}, function(zip) {
placeholder.style.backgroundImage = "url('" + zip.target + "')";
if(!placeholder.classList.contains('space')) { //已有图片
exFile(getChildrenIndex(placeholder), url);
} else { //加号
placeholder.classList.remove('space');
addFile(url);
init_image_add();
}
}, function(zip) {
mui.toast('压缩失败!');
});
}, function(e) {
console.log("读取拍照文件错误:" + e.message);
});
}, function(s) {
console.log("error" + s);
}, {});
break;
}
});
}, false);
代码有点长,只能作为参考,讲一下其中的算法:
首先这个函数应该在plusReady()内调用,并且在载入页面的时候就要调用了!
这个函数作用:生成一个图片添加按钮:
如图所示的“+”号,并且为这个新增的加号添加监听事件
- 点击“+”号弹出actionsheet选择相册或者相机添加图片
- 点击右上角“x”号可以删除一张图片
添加的方式是使用js生成dom并且插入到相应的节点
var placeholder = document.createElement('div');
placeholder.setAttribute('class', 'image-item space');
//删除图片
var closeButton = document.createElement('div');
closeButton.setAttribute('class', 'image-close');
closeButton.innerHTML = 'X';
以下是将会生成的对应的html代码
<div class="image-item space" id="img1">
<div class="image-close">x</div>
</div>
可以想到这个函数应该要递归调用,这个递归是基于事件的,什么事件呢?
就是每一次添加完一张图片的事件,比如途中加号前面一张图被添加完成后立刻就会递归一次,调用init_image_add()自己
给个流程图:
压缩图片:
使用:plus.zip.compressImage
html5+官方文档:
例:
//压缩取得缩略图
plus.zip.compressImage({
src: url,
dst: '_doc/' + name,
overwrite: true,
quality: 50,
height: '300px',
clip: {
top: "25%",
left: "25%",
width: "300px",
height: "300px"
}
}, function(zip) {
placeholder.style.backgroundImage = "url('" + zip.target + "')";
if(!placeholder.classList.contains('space')) { //已有图片
exFile(getChildrenIndex(placeholder), url);
} else { //加号
placeholder.classList.remove('space');
addFile(url);
init_image_add();
}
}, function(zip) {
mui.toast('压缩失败!');
});
方法传入的参数:
- 压缩参数:
成功回调函数
失败回调函数
最终上传,多图压缩!
上传原图不在赘述,直接跳过此处,参考uploader上传即可_
这里是有一点小麻烦,个人折腾了一个小时才算弄完美了
千万注意,compressImage方法是异步执行的,也就是说,如果你打算将所有要上传的图片在for循环中遍历并且压缩是不妥当的,因为这些图片将会并行压缩,而由于是多图上传,你不知道所有图片压缩完成是什么时候,一张图的话可以直接在成功的回调函数中执行后面的逻辑
我采用了递归的方法解决了多图压缩并且压缩全部完成后再执行后面的逻辑,相当于强行把一个异步的函数写成了同步(阻塞)函数,需要结合“回调函数”+“递归调用”!
代码如下:
//用户未选取上传原图时上传前调用
function zip_upload_imgs(len = question.files.length - 1) {
//第一次递归显示等待
if(len == question.files.length - 1)
plus.nativeUI.showWaiting("正在压缩图片...", {
back: "none"
});
//当长度小于0时,结束递归
if(len < 0) {
//关闭等待
plus.nativeUI.closeWaiting();
return submitAsk();
}
//上传压缩图
var url = question.files[len];
plus.zip.compressImage({
src: url,
dst: '_doc/zip_' + url.substr(url.lastIndexOf('/') + 1),
overwrite: true,
quality: 50,
height: "90%",
}, function(zip) {
//压缩成功,替换原图路径
question.files[len] = zip.target;
zip_upload_imgs(--len);
}, function() {
//压缩失败
mui.toast('压缩失败!');
});
}
这个函数就有意思了,递归出现在当前图片压缩成功后调用(在回调函数中递归),这样就解决了异步的问题,等待压缩而不执行之后的逻辑!
当然这里因为压缩时间可能会长一点,需要用到等待窗口提供用户友好,以免用户不知道这段时间是在压缩!
这个递归函数默认传入的是需要压缩的图片的数组长度值,这个图片数组(question.files)是个全局或者说相对于函数来说是更加全局的,他不会因为函数结束而回收!处于函数作用域之外!
数组是反向递归的,下标从大到小,最大的时候第一次执行函数所以显示等待提示,最后一次是当下标小于0时(数组下标越界)结束递归,中间每一次执行完,递归前将下标-1,传入下一次递归!
在return后面紧跟着的是一个一直在等待着的sumitAsk()函数,这个函数是最终上传图片+提交表单的!之所以说是“一直等待着的提交函数”,因为他本来会因为异步执行的“plus.zip.compressImage”而先执行掉,导致图片没有压缩就上传了,我之前折腾了很久就是因为这个问题,如果不按照上述的回调+递归模式,图片还在压缩,sumit就执行了,那么图片数组没有变,依然上传了原图!
JavaScript中有的是异步函数,有的是同步函数,需要严格注意,异步函数会重新打开一个“时间线”去执行自己,忽略掉同步的函数,所以应该要做到等待异步函数执行完成后继续执行同步函数!
上传图片
使用函数:plus.uploader.createUoload()
官方说明
我需要上传
- 图片文件(根据上述的图片数组,question.files,其中保存的都是要上传的图片的绝对路径)
- 表单数据
示例如下:
function submitAsk() {
//建立连接
var url = HTTP_DOMAIN + "Service/ask";
var uploader = plus.uploader.createUpload(url, {
method: 'POST'
}, function(upload, status) {
plus.nativeUI.closeWaiting();
if(status == 200) {
var res = JSON.parse(upload.responseText);
//服务器方登陆失效
if(res.login == 0) {
plus.nativeUI.toast(res.info);
app.clearToken();
app.toLogin();
return false;
}
console.log(upload.responseText);
if(res.status == 1) {
mui.alert("您的问题已提交,等待附近的人解答", "发表成功", "确定", function() {
mui.back();
});
} else {
mui.toast(res.info);
}
} else {
mui.toast("网络服务器连接失败!稍后重试");
}
});
//添加上传数据
for(key in question) {
if(key == "files")
continue;
uploader.addData(key, question[key]);
}
//如果有礼物图片就上传
if(question.gift_img != "") {
uploader.addFile(question.gift_img, {
key: "gift_img"
});
}
//添加上传文件
for(var i = 0; i < question.files.length; i++) {
uploader.addFile(question.files[i], {
key: "img" + i
});
}
//开始上传任务
plus.nativeUI.showWaiting("正在提交...",{back:"none"});
uploader.start();
}
注意addData和addFile的使用:
其中addData是上传数据的键值对,就像表单name和value一样一一对应,在这之前已经放入了question对象之中:
document.getElementById("submit").addEventListener("tap", function() {
//获取表单数据
question.title = document.getElementById("title").value;
question.content = document.getElementById("content").value;
question.gift_img = document.getElementById("gift_img").getAttribute("src");
question.reward = document.getElementById("reward").value;
question.message = document.getElementById("message").value;
question.price = document.getElementById("price").value;
console.log(JSON.stringify(question));
/*
* 必填项目:标题,内容,难度
*/
if(plus.networkinfo.getCurrentType() == plus.networkinfo.CONNECTION_NONE)
return mui.toast("连接网络失败,请稍后再试");
if(trim(question.title) == "")
return mui.toast("请给出问题标题!");
if(trim(question.content) == "")
//判断网络连接
return mui.toast("无法提交,请详细填写以下问题内容!");
if(question.star <= 0 || question.star > 5)
return mui.toast("请给出问题难度!");
//用户未选择上传原图时,压缩所有上传图片
if(!document.getElementById("high_img").classList.contains("mui-active") && question.files.length > 0) {
zip_upload_imgs();
} else {
submitAsk();
}
}, false);
关于异步上传plus.uploader,详细请参阅:
http://www.html5plus.org/doc/zh_cn/uploader.html#plus.uploader.createUpload