在一個移動端的項目中,圖片上傳是一個比較經常使用的功能。javascript
可是,目前手機的隨便拍的照片一張都要好幾 M , 直接上傳的話特別耗費流量,並且所需時間也比較長。html
因此須要前端在上傳以前先對圖片進行壓縮。前端
要使用 js 實現圖片壓縮效果, 原理其實很簡單,主要是:java
canvas
的 drawImage
將目標圖片畫到畫布上canvas
的 toDataURL
或者 toBlob
能夠將畫布上的內容導出成 base64 格式的數據。這個須要 import EXIF from 'exif-js';
來獲取到手機的方向,而後對 canvas 的寬高進行處理git
let imgDataLength = dataUrl.length;
獲取到數據後,判斷壓縮後的圖片大小是否知足需求,不然就下降尺寸以及質量,再次壓縮github
// 填充白色背景 ctx.fillStyle = fillBgColor; ctx.fillRect(0, 0, size.w, size.h);
/** * 文件讀取並經過canvas壓縮轉成base64 * @param files * @param callback */ //EXIF js 能夠讀取圖片的元信息 https://github.com/exif-js/exif-js import EXIF from 'exif-js'; // 壓縮圖片時 質量減小的值 const COMPRESS_QUALITY_STEP = 0.03; const COMPRESS_QUALITY_STEP_BIG = 0.06; // 壓縮圖片時,圖片尺寸縮放的比例,eg:0.9, 等比例縮放爲0.9 const COMPRESS_SIZE_RATE = 0.9; let defaultOptions = { removeBase64Header: true, // 壓縮後容許的最大值,默認:300kb maxSize: 200 * 1024, fillBgColor: '#ffffff' }; /** * 將待上傳文件列表壓縮並轉換base64 * !!!! 注意 : 圖片會默認被轉爲 jpeg , 透明底會加白色背景 * files : 文件列表 ,必須是數組 * callback : 回調,每一個文件壓縮成功後都會回調, * options :配置 * options.removeBase64Header : 是否須要刪除 'data:image/jpeg;base64,'這段前綴,默認true * @return { base64Data: '',fileType: '' }, //fileType強制改成jpeg */ export function imageListConvert(files, callback, options = {}) { if (!files.length) { console.warn('files is null'); return; } options = { ...defaultOptions, ...options }; // 獲取圖片方向--iOS拍照下有值 EXIF.getData(files[0], function() { let orientation = EXIF.getTag(this, 'Orientation'); for (let i = 0, len = files.length; i < len; i++) { let file = files[i]; let fileType = getFileType(file.name); //強制改成jpeg fileType = 'jpeg'; let reader = new FileReader(); reader.onload = (function() { return function(e) { let image = new Image(); image.onload = function() { let data = convertImage( image, orientation, fileType, options.maxSize, options.fillBgColor ); if (options.removeBase64Header) { data = removeBase64Header(data); } callback({ base64Data: data, fileType: fileType }); }; image.src = e.target.result; }; })(file); reader.readAsDataURL(file); } }); } /** * 將 image 對象 畫入畫布並導出base64數據 */ export function convertImage( image, orientation, fileType = 'jpeg', maxSize = 200 * 1024, fillBgColor = '#ffffff' ) { let maxWidth = 1280, maxHeight = 1280, cvs = document.createElement('canvas'), w = image.width, h = image.height, quality = 0.9; /** * 這裏用於計算畫布的寬高 */ if (w > 0 && h > 0) { if (w / h >= maxWidth / maxHeight) { if (w > maxWidth) { h = (h * maxWidth) / w; w = maxWidth; } } else { if (h > maxHeight) { w = (w * maxHeight) / h; h = maxHeight; } } } let ctx = cvs.getContext('2d'); let size = prepareCanvas(cvs, ctx, w, h, orientation); // 填充白色背景 ctx.fillStyle = fillBgColor; ctx.fillRect(0, 0, size.w, size.h); //將圖片繪製到Canvas上,從原點0,0繪製到w,h ctx.drawImage(image, 0, 0, size.w, size.h); let dataUrl = cvs.toDataURL(`image/${fileType}`, quality); //當圖片大小 > maxSize 時,循環壓縮,而且循環不超過5次 let count = 0; while (dataUrl.length > maxSize && count < 10) { let imgDataLength = dataUrl.length; let isDoubleSize = imgDataLength / maxSize > 2; // 質量一次降低 quality -= isDoubleSize ? COMPRESS_QUALITY_STEP_BIG : COMPRESS_QUALITY_STEP; quality = parseFloat(quality.toFixed(2)); // 圖片還太大的狀況下,繼續壓縮 。 按比例縮放尺寸 let scaleStrength = COMPRESS_SIZE_RATE; w = w * scaleStrength; h = h * scaleStrength; size = prepareCanvas(cvs, ctx, w, h, orientation); //將圖片繪製到Canvas上,從原點0,0繪製到w,h ctx.drawImage(image, 0, 0, size.w, size.h); console.log(`imgDataLength:${imgDataLength} , maxSize --> ${maxSize}`); console.log(`size.w:${size.w}, size.h:${size.h}, quality:${quality}`); dataUrl = cvs.toDataURL(`image/jpeg`, quality); count++; } console.log(`imgDataLength:${dataUrl.length} , maxSize --> ${maxSize}`); console.log(`size.w:${size.w}, size.h:${size.h}, quality:${quality}`); cvs = ctx = null; return dataUrl; } /** * 準備畫布 * cvs 畫布 * ctx 上下文 * w : 想要畫的寬度 * h : 想要畫的高度 * orientation : 屏幕方向 */ function prepareCanvas(cvs, ctx, w, h, orientation) { cvs.width = w; cvs.height = h; //判斷圖片方向,重置canvas大小,肯定旋轉角度,iphone默認的是home鍵在右方的橫屏拍攝方式 let degree = 0; switch (orientation) { case 3: //iphone橫屏拍攝,此時home鍵在左側 degree = 180; w = -w; h = -h; break; case 6: //iphone豎屏拍攝,此時home鍵在下方(正常拿手機的方向) cvs.width = h; cvs.height = w; degree = 90; // w = w; h = -h; break; case 8: //iphone豎屏拍攝,此時home鍵在上方 cvs.width = h; cvs.height = w; degree = 270; w = -w; // h = h; break; } // console.log(`orientation --> ${orientation} , degree --> ${degree}`); // console.log(`w --> ${w} , h --> ${h}`); //使用canvas旋轉校訂 ctx.rotate((degree * Math.PI) / 180); return { w, h }; } /** * 截取 ‘data:image/jpeg;base64,’, * 截取到第一個逗號 */ export function removeBase64Header(content) { if (content.substr(0, 10) === 'data:image') { let splitIndex = content.indexOf(','); return content.substring(splitIndex + 1); } return content; } export function getFileType(fileName = '') { return fileName.substring(fileName.lastIndexOf('.') + 1); } export function checkAccept( file, accept = 'image/jpeg,image/jpg,image/png,image/gif' ) { return accept.toLowerCase().indexOf(file.type.toLowerCase()) !== -1; }