用Canvas實現h5移動端圖片裁剪

前陣子七夕的時候搞了一個h5活動頁,須要作一個本地圖片的裁剪上傳功能,用於生成一些特定的表白圖片。主要是用到了H5的FileApiCanvas。我的感受圖片裁剪功能仍是很實用的,故寫篇文章分享一下。javascript

demo地址:https://github.com/Bless-L/im...
效果gif:
效果圖democss

(PS:這個demo原本是移動端的,但了爲了方便錄製效果,因此我用chrome模擬地跑了一下。
還有就是demo是配合Vue實現的,由於我是直接在項目中複製過來改改的,就懶得轉換了)html

圖片讀取

要裁剪首先確定就是讀取圖片文件。其實也很簡單,用一個input指定爲file類型就能夠了。vue

<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);
},

而後就是處理得到的文件html5

//這些方法是寫在Clip類裏面的

handleFiles(file){
    let t = this;
    let reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function() {
        t.imgUrl = this.result;
        t.paintImg(this.result);
    }
}

先新建一個FileReader類,而後把讀得的文件用readAsDataURL轉換爲base64編碼,再經過paintImg方法進行繪製處理。java

須要注意的是文件讀取是異步的,因此讀取後的操做須要在onload函數中進行。ios

繪製裁剪界面

初始化

先是初始化一些基本參數git

//初始化
init(file){
    this.sx = 0; //裁剪框的初始x
    this.sy = 0; //裁剪框的初始y
    this.sWidth = 233; //裁剪框的寬
    this.sHeight = 175; //裁剪框的高 
    this.chooseBoxScale = 233/175;//裁剪框的寬高比

    this.handleFiles(file);
}

在這裏裁剪框的比例是固定的4:3,有須要的同窗能夠自行更改。至於寬高那個是先照着設計稿設一個數值,後面會根據不一樣的圖片而從新設置。github

繪製裁剪界面

paintImg(picUrl){
    //other code
    //.....
    img.onload = function() {

        let imgScale = img.width / img.height;
        let boxScale = t.regional.offsetWidth / t.regional.offsetHeight;

        //判斷盒子與圖片的比列
        if (imgScale < boxScale) {
            //設置圖片的像素
            t.imgWidth = t.regional.offsetHeight * imgScale;
            t.imgHeight = t.regional.offsetHeight;
        } else {
            //設置圖片的像素
            t.imgWidth = t.regional.offsetWidth;
            t.imgHeight = t.regional.offsetWidth / imgScale;
        }

        //判斷圖片與選擇框的比例大小,做出裁剪
        if (imgScale < t.chooseBoxScale) {
            //設置選擇框的像素
            t.sWidth = t.imgWidth;
            t.sHeight = t.imgWidth / t.chooseBoxScale;

            //設置初始框的位置
            t.sx = 0;
            t.sy = (t.imgHeight - t.sHeight) / 2;
        } else {
            //設置選擇框的像素
            t.sWidth = t.imgHeight * t.chooseBoxScale;
            t.sHeight = t.imgHeight;

            t.sx = (t.imgWidth - t.sWidth) / 2;
            t.sy = 0;
        }
        //(1)
    }

這段代碼比較長。chrome

先說一下裁剪界面的結構。看那demo及動圖能夠知道,裁剪界面中,首先有一個最外層的容器,裝着圖片,即id爲container的div,稱爲1;
而後是圖片容器,即id爲image-box的canvas,稱爲2;
最後是最外面被掏空裁剪區域的模糊層,即id爲cover-box的canvas,稱爲3。

這幾個容器中,1的寬高是固定的。而2則在保證比例不變的狀況下有一邊佔滿整個1。因此能夠看到上面那段判斷盒子與圖片比列的代碼是在實現這個顯示方式。
同時能夠看到3的裁剪區域也是同理的,在保證比例不變的狀況下有一邊佔滿整個2。亦即裁剪區域的比例是在事先就設計好的,在這裏是4:3。

因此我在效果圖中將高填滿容器寬填滿容器的圖片都演示了一遍。

一些處理

//續上面(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();

第一個是高分屏下圖片模糊的問題,在高分屏下用canvas繪製某些圖片是會出現模糊,估計是和canvas的繪製機制有關,具體緣由戳這裏。解決辦法也比較簡單,將canvas的css寬高固定,容器寬高擴大兩倍。(個人理解,css寬高就是canvas標籤style樣式設置的寬高,容器寬高就是裏面那個畫板的寬高,不是同一個東西)通過這樣的處理後,絕大多數圖片的模糊問題解決了。

第二個是圖片繪製壓縮問題,在低版本的ios機型下繪製1m多以上的圖片時會出現壓縮,翻轉等問題,詳情及解決辦法戳這裏。我上面就是用了一個stackflow裏面的fix方法。

從這兩個問題能夠見到,canvas繪製還不是很是成熟,使用時要注意一些bug及相關修復辦法。

移動時繪製

drag(){
    let t = this;
    let draging = false;

    //記錄初始點擊的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;
    })

這裏邏輯不算太複雜,先是記錄初始位置,而後判斷是左右移動仍是上下移動(對應高填滿容器和寬填滿容器),再根據pageX,pageY這些判斷位置量便可。

保存圖片

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(){

        //計算裁剪尺寸比例,用於裁剪圖片
        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);
    }
}

這部分也挺簡單的,裁剪框那裏記錄了裁剪開始及結束的座標,而後新建一個canvas裁出來,並用toDataURL方法轉換爲base64編碼,就能夠傳輸到後臺了。我這裏裁剪後的尺寸是固定的,這是業務需求,有須要的能夠更改

總結

大體的流程就是這樣,感謝你們的閱讀,若有錯誤,多多包涵。

相關文章
相關標籤/搜索