前言: 前陣子做中再保險(xiǎn)的項(xiàng)目,客戶提到一個(gè)需求需要開發(fā)一個(gè)在線頁(yè)面嵌套到別的供應(yīng)商產(chǎn)品中,其中有上傳報(bào)銷單和裁剪的功能(雖然裁剪最后pass掉了)罐呼,這里說(shuō)一說(shuō)關(guān)于圖片裁剪遇到的一些坑罩锐。
首先由于是嵌套在別的系統(tǒng)中求泰,一些原生的插件就不可能用了芝加。于是本圖片裁剪功能就基于canvas自己用畫布裁出來(lái)硅卢,并上傳。
1 要裁剪首先肯定就是讀取圖片文件藏杖。由于沒有cordova插件可用将塑,用一個(gè)input指定為file類型就可以了。
<input type="file" class="file" accept="image/*;capture=camera" name="img" @change="clipImg($event)">
// Vue里面的methods
clipImg(event){
this.clip = new Clip('container',this);
this.clip.init(event.target.files[0]);
this.isClip = true;
document.body.addEventListener('touchmove',this.noScoll,false);
},
接著蝌麸,處理所獲取的文件:
根據(jù)FileReader創(chuàng)建一個(gè)實(shí)例点寥,然后把讀得的文件用readAsDataURL轉(zhuǎn)換為base64編碼,再通過paintImg方法進(jìn)行繪制處理来吩。需要注意的是文件讀取是異步的敢辩,所以讀取后的操作需要在onload函數(shù)中進(jìn)行蔽莱。
//這些方法是寫在Clip類里面的
handleFiles(file){
let t = this;
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
t.imgUrl = this.result;
t.paintImg(this.result);
}
}
2 繪制裁剪界面
這一步的繪制的大小尺寸可根據(jù)個(gè)人需求而定,首先我們初始化一些基本參數(shù)戚长,這里我們暫且設(shè)定比例為233:175盗冷。
//初始化
init(file){
this.sx = 0; //裁剪框的初始x
this.sy = 0; //裁剪框的初始y
this.sWidth = 233; //裁剪框的寬
this.sHeight = 175; //裁剪框的高
this.chooseBoxScale = 233/175;//裁剪框的寬高比
this.handleFiles(file);
}
下面這一段代碼稍微有點(diǎn)長(zhǎng),我們先確認(rèn)三個(gè)元素:
1 裁剪界面中同廉,首先有一個(gè)最外層的容器仪糖,裝著圖片,即id為container的div
2 然后是圖片容器迫肖,即id為image-box的canvas (裁剪選中的部分)
3 被裁剪掏空的部分锅劝,即id為cover-box的canvas(被裁剪舍棄的部分)
這幾個(gè)容器中,1的寬高是固定的蟆湖。而2則在保證比例不變的情況下有一邊占滿整個(gè)1故爵。所以可以看到上面那段判斷盒子與圖片比列的代碼是在實(shí)現(xiàn)這個(gè)顯示方式。
同時(shí)可以看到3的裁剪區(qū)域也是同理的隅津,在保證比例不變的情況下有一邊占滿整個(gè)2稠集。亦即裁剪區(qū)域的比例是在事先就設(shè)計(jì)好的,在這里是4:3饥瓷。
所以我在效果圖中將高填滿容器和寬填滿容器的圖片都演示了一遍剥纷。
paintImg(picUrl){
//other code
//.....
img.onload = function() {
let imgScale = img.width / img.height;
let boxScale = t.regional.offsetWidth / t.regional.offsetHeight;
//判斷盒子與圖片的比列
if (imgScale < boxScale) {
//設(shè)置圖片的像素
t.imgWidth = t.regional.offsetHeight * imgScale;
t.imgHeight = t.regional.offsetHeight;
} else {
//設(shè)置圖片的像素
t.imgWidth = t.regional.offsetWidth;
t.imgHeight = t.regional.offsetWidth / imgScale;
}
//判斷圖片與選擇框的比例大小,作出裁剪
if (imgScale < t.chooseBoxScale) {
//設(shè)置選擇框的像素
t.sWidth = t.imgWidth;
t.sHeight = t.imgWidth / t.chooseBoxScale;
//設(shè)置初始框的位置
t.sx = 0;
t.sy = (t.imgHeight - t.sHeight) / 2;
} else {
//設(shè)置選擇框的像素
t.sWidth = t.imgHeight * t.chooseBoxScale;
t.sHeight = t.imgHeight;
t.sx = (t.imgWidth - t.sWidth) / 2;
t.sy = 0;
}
//(1)
}
3 防止高分屏下出現(xiàn)圖片模糊的情況呢铆,做以下處理
第一個(gè)是高分屏下圖片模糊的問題晦鞋,在高分屏下用canvas繪制某些圖片是會(huì)出現(xiàn)模糊,估計(jì)是和canvas的繪制機(jī)制有關(guān)棺克,具體原因戳這里悠垛。解決辦法也比較簡(jiǎn)單,將canvas的css寬高固定娜谊,容器寬高擴(kuò)大兩倍确买。(我的理解,css寬高就是canvas標(biāo)簽style樣式設(shè)置的寬高纱皆,容器寬高就是里面那個(gè)畫板的寬高湾趾,不是同一個(gè)東西)經(jīng)過這樣的處理后,絕大多數(shù)圖片的模糊問題解決了派草。
第二個(gè)是圖片繪制壓縮問題搀缠,在低版本的ios機(jī)型下繪制1m多以上的圖片時(shí)會(huì)出現(xiàn)壓縮,翻轉(zhuǎn)等問題近迁,詳情及解決辦法戳這里艺普。我上面就是用了一個(gè)stackflow里面的fix方法。
//續(xù)上面(1)
//高分屏下圖片模糊,需要2倍處理
t.getImage.height = 2 * t.imgHeight;
t.getImage.width = 2 * t.imgWidth;
t.getImage.style.width = t.imgWidth + 'px';
t.getImage.style.height = t.imgHeight + 'px';
let vertSquashRatio = t.detectVerticalSquash(img);
cxt.drawImage(img, 0, 0,2 * t.imgWidth * vertSquashRatio, 2 * t.imgHeight * vertSquashRatio)
t.cutImage();
t.drag();
4 移動(dòng)的時(shí)候繪制
這里主要記錄裁剪的區(qū)域歧譬,首先確定起始位置岸浑,然后判斷左右或者上下移動(dòng),對(duì)應(yīng)的高度填滿容器高度和寬度瑰步,在根據(jù)pageX和pageY判斷位置即可助琐。
drag(){
let t = this;
let draging = false;
//記錄初始點(diǎn)擊的pageX,pageY面氓。用于記錄位移
let pageX = 0;
let pageY = 0;
//初始位移
let startX = 0;
let startY = 0;
t.editBox.addEventListener('touchmove', function(ev) {
let e = ev.touches[0];
let offsetX = e.pageX - pageX;
let offsetY = e.pageY - pageY;
if (draging) {
if (t.imgHeight == t.sHeight) {
t.sx = startX + offsetX;
if (t.sx <= 0) {
t.sx = 0;
} else if (t.sx >= t.imgWidth - t.sWidth) {
t.sx = t.imgWidth - t.sWidth;
}
} else {
t.sy = startY + offsetY;
if (t.sy <= 0) {
t.sy = 0;
} else if (t.sy >= t.imgHeight - t.sHeight) {
t.sy = t.imgHeight - t.sHeight;
}
}
t.cutImage();
}
});
t.editBox.addEventListener('touchstart', function(ev) {
let e = ev.touches[0];
draging = true;
pageX = e.pageX;
pageY = e.pageY;
startX = t.sx;
startY = t.sy;
})
t.editBox.addEventListener('touchend', function() {
draging = false;
})
}
5 將裁剪的區(qū)域保存起來(lái)
裁剪框那里記錄了裁剪開始及結(jié)束的坐標(biāo)兵钮,然后新建一個(gè)canvas裁出來(lái),并用toDataURL方法轉(zhuǎn)換為base64編碼舌界,就可以傳輸?shù)胶笈_(tái)了掘譬。我這里裁剪后的尺寸是固定的,這個(gè)是根據(jù)當(dāng)初的初定業(yè)務(wù)需求做的呻拌,有需要的可以根據(jù)需要修改葱轩。
save(){
let t = this;
let saveCanvas = document.createElement('canvas');
let ctx = saveCanvas.getContext('2d');
//圖片裁剪后的尺寸
saveCanvas.width = 466;
saveCanvas.height = 350;
let images = new Image();
images.src = t.imgUrl;
images.onload = function(){
//計(jì)算裁剪尺寸比例,用于裁剪圖片
let cropWidthScale = images.width/t.imgWidth;
let cropHeightScale = images.height/t.imgHeight;
t.drawImageIOSFix(ctx, images,cropWidthScale * t.sx , cropHeightScale* t.sy,
t.sWidth * cropWidthScale, t.sHeight * cropHeightScale, 0, 0, 466, 350);
// ctx.drawImage(images,2 * t.sx, 2 * t.sy, t.sWidth * 2, t.sHeight * 2, 0, 0, 466, 350);
t.$vm.clipUrl = saveCanvas.toDataURL();
t.regional.removeChild(t.getImage);
t.regional.removeChild(t.editBox);
}
}
最后可將保存的裁剪部分通過base64活其他格式傳輸?shù)胶笈_(tái)藐握!