使用 JavaScript 壓縮和翻轉圖片

Sparkfly發表於2019-07-26

使用 JavaScript 壓縮圖片並上傳

☘️WEB前端上傳檔案到遠端時,我們都會遇到同樣一個問題,請求長度超出限制,是的,檔案大小超出限制該如何解決?

我這裡有兩種方案:

  • 第一種,前端壓縮後上傳圖片
  • 第二種,分塊上傳,後臺進行壓縮儲存
  • 其實有第三種,不允許上傳此檔案 ?

如果只考慮第一種方案,前端壓縮圖片後上傳,對於壓縮圖片的大小和質量無法把控,並且非圖片型別的檔案上傳又要考慮非圖片型別檔案的壓縮方法。
第二種方式,實現分塊上傳對編碼要求更高,後端和前端都需要實現可靠的元件。

本篇文章,我們只瞭解如何使用前端壓縮圖片

我畫了一張圖,大致描述前端壓縮圖片整個流程呼叫

Laravel

使用 JavaScript 壓縮圖片,首先要學習三個Web API: FileReaderCanvasBlob

FileReader

FileReader 物件允許Web應用程式非同步讀取儲存在使用者計算機上的檔案(或原始資料緩衝區)的內容,使用 File 或 Blob 物件指定要讀取的檔案或資料。

成員方法:

  • FileReader.abort()中止讀取操作。在返回時,readyState屬性為DONE
  • FileReader.readAsArrayBuffer()開始讀取指定的Blob中的內容, 一旦完成, result 屬性中儲存的將是被讀取檔案的 ArrayBuffer 資料物件.
  • FileReader.readAsBinaryString()開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含所讀取檔案的原始二進位制資料。
  • FileReader.readAsDataURL()開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含一個data: URL格式的字串以表示所讀取檔案的內容。
  • FileReader.readAsText()開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含一個字串以表示所讀取的檔案內容。

事件處理:

  • FileReader.onabort處理abort事件。該事件在讀取操作被中斷時觸發。
  • FileReader.onerror處理error事件。該事件在讀取操作發生錯誤時觸發。
  • FileReader.onload處理load事件。該事件在讀取操作完成時觸發。
  • FileReader.onloadstart處理loadstart事件。該事件在讀取操作開始時觸發。
  • FileReader.onloadend處理loadend事件。該事件在讀取操作結束時(要麼成功,要麼失敗)觸發。
  • FileReader.onprogress處理progress事件。該事件在讀取Blob時觸發

圖片壓縮過程中,我們需要使用到FileReader.readAsDataURL()將檔案轉換為DataURL,並使用FileReader.onload事件接收轉換結果。

程式碼實現:

var ready = new FileReader();
ready.readAsDataURL(file);
ready.onload = function(){
    var re = this.result;
    // re 為 Base64 DataURL
}

Canvas

HTML5 <canvas> 標籤用於繪製影象(通過指令碼,通常是 JavaScript)。不過,<canvas> 元素本身並沒有繪製能力(它僅僅是圖形的容器) - 您必須使用指令碼來完成實際的繪圖任務。

  • getContext() 方法可返回一個物件,該物件提供了用於在畫布上繪圖的方法和屬性
  • getContext("2d") 物件屬性和方法,可用於在畫布上繪製文字、線條、矩形、圓形等等

drawImage drawImage() 方法在畫布上繪製影象、畫布或視訊

三種過載方式:

  1. context.drawImage(img,x,y); // 在畫布上定點陣圖像

  2. context.drawImage(img,x,y,width,height); // 在畫布上定點陣圖像,並規定影象的寬度和高度

  3. context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height); // 剪下影象,並在畫布上定位被剪下的部分

圖片裁剪過程中,我們將會使用第二種方式,壓縮修改圖片的大小(實現等比例縮放)

程式碼實現

var img = new Image();
img.src = path;
img.onload = function(){
    var that = this;
    // 等比例縮放
    var w = that.width,
        h = that.height,
        scale = w / h;
    w = obj.width || w;
    h = obj.height || (w / scale);
    var quality = 0.7;
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    var anw = document.createAttribute("width");
    anw.nodeValue = w;
    var anh = document.createAttribute("height");
    anh.nodeValue = h;
    canvas.setAttributeNode(anw);
    canvas.setAttributeNode(anh);
    ctx.drawImage(that, 0, 0, w, h);
    if(obj.quality && obj.quality <= 1 && obj.quality > 0){
        quality = obj.quality;
    }
    var base64 = canvas.toDataURL('image/jpeg', quality);
}

Blob

Blob物件表示一個不可變、原始資料的類檔案物件。Blob表示的不一定是JavaScript原生格式的資料。File介面基於Blob,繼承了blob的功能並將其擴充套件使其支援使用者系統上的檔案。

要從其他非blob物件和資料構造一個Blob,請使用Blob()建構函式。要建立包含另一個blob資料的子集blob,請使用slice()方法。

此處 slice() 也是用於分塊上傳的呼叫方法

本文前端壓縮圖片流程,我們只需要用到Blob()建構函式

下面程式碼實現:DataURL 建立 Blob 物件,提供上傳表單form-data使用:

function dataURLtoFile(dataurl, filename) {
  var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
  while(n--){
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, {type:mime});
}

圖片拍攝角度問題

首先來了解一個概念 EXIF 是什麼?

簡單來說,Exif資訊就是由數碼相機在拍攝過程中採集一系列的資訊,然後把資訊放置在我們熟知的JPEG/TIFF檔案的頭部,也就是說Exif資訊是鑲嵌在 JPEG/TIFF影象檔案格式內的一組拍攝引數。主要包含了以下幾類資訊:

  • 拍攝日期
  • 攝器材,機身、鏡頭、閃光燈等
  • 拍攝引數,快門速度、光圈F值、ISO速度、焦距、測光模式等
  • 影象處理引數,銳化、對比度、飽和度、白平衡等
  • 影象描述及版權資訊
  • GPS定位資料
    ...

其中orientation記錄著拍攝後圖片旋轉的角度資訊:https://www.impulseadventure.com/photo/exi...

orientation 旋轉角度
1
3 180°
6 順時針90°
8 逆時針90°

所以,上傳圖片時,需要將圖片進行反向旋轉,才能得到一致的展示效果。

開始旋轉圖片之前,我們需要獲取圖片旋轉資訊,需要引入擴充套件:https://github.com/exif-js/exif-js

然後,就可以編寫程式碼實現:(所有的旋轉都是以原點為中心的)

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const anw = document.createAttribute('width');
const anh = document.createAttribute('height');
anw.nodeValue = w;
anh.nodeValue = h;
switch (orientation) {
  case 6: // 90度
    anw.nodeValue = h;
    anh.nodeValue = w;
    canvas.setAttributeNode(anh);
    canvas.setAttributeNode(anw);
    ctx.rotate(Math.PI / 2);
    ctx.drawImage(this, 0, -h, w, h);
    break;
  case 3: // 180度
    canvas.setAttributeNode(anh);
    canvas.setAttributeNode(anw);
    ctx.rotate(Math.PI);
    ctx.drawImage(this, -w, -h, w, h);
    break;
  case 8: // -90度
    anw.nodeValue = h;
    anh.nodeValue = w;
    canvas.setAttributeNode(anh);
    canvas.setAttributeNode(anw);
    ctx.rotate(3 * Math.PI / 2);
    ctx.drawImage(this, -w, 0, w, h);
    break;
  default: // 0度
    canvas.setAttributeNode(anh);
    canvas.setAttributeNode(anw);
    ctx.drawImage(this, 0, 0, w, h);
}

原文來自我的部落格:https://www.helingfeng.com/2019-06-26/use-...

相關文章