base64和blob


base64是二進制數據的一個編碼格式,就像utf8一樣的東西,他跟json一樣,也是前后端交互能夠相互識別的數據,他更多的是用來傳遞文件數據,並且如果是圖片的base64,可以用來壓縮

獲取base64有幾個方式

  • 用input + filereader
  • 用url + canvas
  • 用url + filereader

在js里生成base64的API有兩個,一個是FileReader,一個是畫布canvas

input + filereader

<input type="file" onchange="change(this.files[0])">
function change(file){
   var fr = new FileReader()
   fr.onload = function(e) { 
      // 這個就是base64
      console.log( e.target.result );
   }
   // 這個方法傳參是一個Blob類型的格式
   fr.readAsDataURL(file)
}

url + canvas
我們使用畫布是為了獲取畫布上的內容
畫布的輸入是圖片,然后對這個圖片進行剪切,打水印什么的
畫布上的內容的輸出格式是base64

// 這個image就是輸入
// 除了new,也可以直接取頁面上的標簽
var image = new Image();

image.onload = function () {
   var w = image.width;
   var h = image.height;
   var canvas = document.createElement('canvas');
   var ctx = canvas.getContext("2d");
   canvas.width = w;
   canvas.height = h;
   ctx.drawImage(image, 0, 0, w, h);
   // 可以在這里添加水印或者合並圖片什么的    
   ...
   // 把畫布的內容轉成base64,這個就是輸出
   var base64 = canvas.toDataURL('image/jpeg');
   console.log(base64)
}
// 這個src可以是本地路徑,服務器圖片地址,也可以是上面fileReader的base64
image.src = "xxx.jpg";

url + filereader
解決方案是不通過畫布獲得base64,可以通過請求圖片地址,修改響應頭獲得blob 格式文件,然后讓fileReader把blob轉成base64,缺點是請求不允許跨域,上代碼

function getBase64(imgUrl) {
   window.URL = window.URL || window.webkitURL;
   var xhr = new XMLHttpRequest();
   xhr.open("get", imgUrl, true);
   // 至關重要
   xhr.responseType = "blob";
   xhr.onload = function () {
	if (this.status == 200) {
	     //得到一個blob對象
	     var blob = this.response;
	     console.log("blob", blob)
	     //  把blob轉成base64
	     let fr = new FileReader();
	     fr.onloadend = function (e) {
		  let base64 = e.target.result;
		  console.log(base64)
	     };
	     fr.readAsDataURL(blob);

             // 補充知識,把blob轉成內存地址
	     var img = document.createElement("img");
	     img.onload = function (e) {
		  window.URL.revokeObjectURL(img.src); // 清除釋放
	     };
             // 把blob轉化成當前頁面的一個內存地址
	     let src = window.URL.createObjectURL(blob); // 這個方法也可以傳一個file
	     console.log(src)
	     img.src = src;
	     document.getElementById("xxx").appendChild(img);
	}
  }
  xhr.send();
}
getBase64("http://www.xx.com/xx.png")

關於blob
上面使用到了blob的知識
從input的onchange中返回的圖片對象其實就是一個File對象。
而Blob對象是一個用來包裝二進制文件的容器,File繼承於Blob。
FileReader是用來讀取內存中的文件的API,支持File和Blob兩種格式。

FileReader和URL.createObjectURL的區別
資料來自掘金網友

區別一

  • 通過FileReader.readAsDataURL(file)可以獲取一段data:base64的字符串
  • 通過URL.createObjectURL(blob)可以獲取當前文件的一個內存URL

區別二

  • createObjectURL是同步執行(立即的)
  • FileReader.readAsDataURL是異步執行(過一段時間)

區別三

  • createObjectURL返回一段帶hash的url,並且一直存儲在內存中,直到document觸發了unload事件(例如:document close)或者執行revokeObjectURL來釋放。
  • FileReader.readAsDataURL則返回包含很多字符的base64,並會比blob url消耗更多內存,但是在不用的時候會自動從內存中清除(通過垃圾回收機制)

壓縮圖片
三個獲取base64的方法說完了,但是base64的初衷是加工圖片或者壓縮圖片,否則我們獲取base64將毫無意義,加工圖片有擦除,打水印,剪切等等,這個可以查看下一篇筆記,這里先講壓縮,壓縮是使用canvas的API,但是有很多的兼容和限制

// 基礎使用
var bili = 0.7; // 壓縮比例
var base64 = canvas.toDataURL('image/jpeg',bili);

畫布的兼容和限制
畫布的api是非常不友好的

  1. 跨域問題(往下看有解決方案)
  2. ios系統兼容(所以有些需求要做手機網頁版的ps的千萬別接)
  3. 注意幾個細節
  4. 拍照還會旋轉(往下看有解決方案)

關於畫布跨域
張大神的跨域的解決方案

// 不管畫布的輸入,即圖片的來源是new Image() 還是 document.querySelector()
// 如果圖片是外鏈,產生了跨域,那畫布會報錯
// Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
// Tainted canvases 【被污染的畫布】
// 解決方案是先讓后端開啟圖片支持跨域CORS
// 然后在new Image() 或者 document.querySelector() 之后加上
img.crossOrigin=""

ios系統兼容
圖片壓縮上傳實踐

在IOS中,canvas繪制圖片是有兩個限制

  • 如果圖片的大小超過兩百萬像素,圖片也是無法繪制到canvas上的,調用drawImage的時候不會報錯,但是你用toDataURL獲取圖片數據的時候獲取到的是空的圖片數據,在安卓或者PC瀏覽器就只要比畫布小就行
  • canvas的大小有限制,如果canvas的大小大於大概五百萬像素(即寬高乘積)的時候,畫布是全黑的一片,而安卓和PC瀏覽器會大得多,測試的PChorme瀏覽器是16000*16000
var image = new Image();
// 在畫布大於 16000*16000 之后就畫不出來,這是谷歌78.0.3904.97
// 幾年前的資料顯示ios的畫布是 400w
var w = 16000;
image.onload = function () {
   var canvas = document.querySelector('#canvas');
   var ctx = canvas.getContext("2d");
   canvas.width = w;
   canvas.height = w;
   ctx.drawImage(image, 0, 0, w, w);
}
image.src = "./img/aa.jpg";

應對措施

  • 第一種限制,處理辦法就是瓦片繪制了,也就是將圖片分割成多塊繪制到canvas上,我代碼里的做法是把圖片分割成100萬像素一塊的大小,再繪制到canvas上。
  • 第二種限制,我的處理辦法是對圖片的寬高進行適當壓縮,我代碼里為了保險起見,設的上限是四百萬像素,如果圖片大於四百萬像素就壓縮到小於四百萬像素。四百萬像素的圖片應該夠了,算起來寬高都有2000X2000了
// 用於壓縮圖片的canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext('2d');
//  瓦片canvas
var tCanvas = document.createElement("canvas");
var tctx = tCanvas.getContext("2d");

// 把base64放到img里,再把圖片傳到方法里
function compress(img) {
    var initSize = img.src.length;  //base64的長度
    var width = img.width;
    var height = img.height;
    //如果圖片大於四百萬像素,計算壓縮比並將大小壓至400萬以下
    var ratio;
    if ((ratio = width * height / 4000000) > 1) {
      ratio = Math.sqrt(ratio);
      width /= ratio;
      height /= ratio;
    } else {
      ratio = 1;
    }
    canvas.width = width;
    canvas.height = height;
    //鋪底色,細節之一
    ctx.fillStyle = "#fff";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    //如果圖片像素大於100萬則使用瓦片繪制
    var count;
    if ((count = width * height / 1000000) > 1) {
      count = ~~(Math.sqrt(count) + 1); //計算要分成多少塊瓦片
      //計算每塊瓦片的寬和高
      var nw = ~~(width / count);
      var nh = ~~(height / count);
      tCanvas.width = nw;
      tCanvas.height = nh;
      for (var i = 0; i < count; i++) {
        for (var j = 0; j < count; j++) {
          tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh);
          ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
        }
      }
    } else {
      ctx.drawImage(img, 0, 0, width, height);
    }
    //進行最小壓縮
    var ndata = canvas.toDataURL('image/jpeg', 0.1);
    console.log('壓縮前:' + initSize);
    console.log('壓縮后:' + ndata.length);
    console.log('壓縮率:' + ~~(100 * (initSize - ndata.length) / initSize) + "%");
    tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
    return ndata;
}

注意幾個細節

  • 一個就是canvas的toDataURL是只能壓縮jpg的,當用戶上傳的圖片是png的話,就需要轉成jpg,也就是統一用canvas.toDataURL('image/jpeg') , 類型統一設成jpeg,而壓縮比就自己控制了
  • 另一個就是如果是png轉jpg,繪制到canvas上的時候,canvas存在透明區域的話,當轉成jpg的時候透明區域會變成黑色,因為canvas的透明像素默認為rgba(0,0,0,0),所以轉成jpg就變成rgba(0,0,0,1)了,也就是透明背景會變成了黑色。解決辦法就是繪制之前在canvas上鋪一層白色的底色

拍照旋轉的原因和解決方案
資料來自掘金網友

為什么從相機拍照獲取的圖片會旋轉呢?
是因為從相機拍照獲取的圖片的EXIF(Exchangeable image file format)會默認設置一個orientation tag
目前只有jpeg格式的圖片會有

image.png

解決方案有
用EXIF.js插件獲取圖片的orientation進行判斷
簡易版EXIF.js用DataViewAPI 代碼來自stackoverflow

function getOrientation(file, callback) {
    var reader = new window.FileReader();
    reader.onload = function (e) {

        var view = new window.DataView(e.target.result);
        if (view.getUint16(0, false) != 0xFFD8) {
            return callback(-2);
        }
        var length = view.byteLength, offset = 2;
        while (offset < length) {
            var marker = view.getUint16(offset, false);
            offset += 2;
            if (marker == 0xFFE1) {
                if (view.getUint32(offset += 2, false) != 0x45786966) {
                    return callback(-1);
                }
                var little = view.getUint16(offset += 6, false) == 0x4949;
                offset += view.getUint32(offset + 4, little);
                var tags = view.getUint16(offset, little);
                offset += 2;
                for (var i = 0; i < tags; i++) {
                    if (view.getUint16(offset + (i * 12), little) == 0x0112) {
                        return callback(view.getUint16(offset + (i * 12) + 8, little));
                    }

                }
            } else if ((marker & 0xFF00) != 0xFF00) {
                break;
            } else {
                offset += view.getUint16(offset, false);
            }
        }
        return callback(-1);
    };
    reader.readAsArrayBuffer(file);
}

// 將圖片旋轉到正確的角度
function resetOrientation(srcBase64, srcOrientation, callback) {
    var img = new Image();
    img.onload = function() {
        var width = img.width,
            height = img.height,
            canvas = document.createElement('canvas'),
            ctx = canvas.getContext("2d");
        // set proper canvas dimensions before transform & export
        if ([5,6,7,8].indexOf(srcOrientation) > -1) {
            canvas.width = height;
            canvas.height = width;
        } else {
            canvas.width = width;
            canvas.height = height;
        }
        // transform context before drawing image
        // -2: not jpeg
        // -1: not defined
        switch (srcOrientation) {
            case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
            case 3: ctx.transform(-1, 0, 0, -1, width, height ); break;
            case 4: ctx.transform(1, 0, 0, -1, 0, height ); break;
            case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
            case 6: ctx.transform(0, 1, -1, 0, height , 0); break;
            case 7: ctx.transform(0, -1, -1, 0, height , width); break;
            case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
            default: ctx.transform(1, 0, 0, 1, 0, 0);
        }
        // draw image
        ctx.drawImage(img, 0, 0);
        // export base64,然后用畫布旋轉正確后再生成正確的base64
        callback(canvas.toDataURL('image/jpeg'));
    };
    img.src = srcBase64;
};

如果有關於手機畫布的需要能不做盡量不做,pc端可以做一做,微信的相冊自帶的畫布是手機端最好使的,微信把手機的兼容都大概搞定了,這也是為什么說再別人的平台上做網頁好的地方,API眾多,兼容性也很好,微信的代碼查看《微信公眾號》篇


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM