應廣大讀者建議,已經將該項目源碼提交到地址:
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