【html】【微信小程序】将图片压缩,文件上传的方法

背景

在公司接手了一个项目关于需要用到对图片进行压缩尺寸大小,花了比较多时间在压缩和把压缩的图片进行上传上,因此记录下来。

H5端的实现方法

利用canvas画布,将图片进行压缩,使用的是vue中的方法,使用的组件是vant-uiuploader组件,最后通过base64数据转为blob对象,使用js自带的File类进行上传

beforeRead(file, detail) {
   	 let _this = this;
     console.log("before-upload ==> ", file)
     let testmsg = file.name.substring(file.name.lastIndexOf('.') + 1)				
     const extension = testmsg === 'png'
     const extension2 = testmsg === 'jpg'
     const extension3 = testmsg === 'jpeg'
     if(!extension && !extension2 && !extension3) {
         this.$toast({
             message: '上传文件只能是 png、jpg、jpeg格式!',
             type: 'error'
         });
         return extension || extension2 || extension3
     }
     this.imgSize = (file.size / 1024).toFixed(1);
     console.log("img size ==> ", this.imgSize)
     if(this.imgSize < 30) {
         this.$toast({
             message: '上传文件大小不能小于 30KB!',
             type: 'error'
         });
         return  this.imgSize > 30
     }
     if(this.imgSize / 1024 > 2) {
         this.scale = true
     }
     this.$operationLog({
         btnId: "pc-upload image",
         type: '0'
     })
     //如果超过4M就执行压缩算法
     if(this.imgSize / 1024 > 4) {
         return new Promise((resolve, reject) => {
             const fileNames = file.name.split('.');
             const type = fileNames[fileNames.length - 1];
             this.handleCompressImage(file, type).then(res => {
                 console.log("res ==> ", res)
                 resolve(res)
             })
         })
     }
     else {
         return new Promise((resolve, reject) => {
             const fileNames = file.name.split('.');
             const type = fileNames[fileNames.length - 1];
             this.handleImageSize(file, type).then(res => {
                 resolve(res)
             })
         })
     }
},
handleCompressImage(img, type){
   let vm = this;
   let resultBlob = "";
   // 读取文件
   let reader = new FileReader()
   reader.readAsDataURL(img);
   return new Promise((resolve, reject) => {
       reader.onload = function(e) {
           let image = new Image(); //新建一个img标签
           image.src = e.target.result;
           image.onload = function() {
               let canvas = document.createElement('canvas');
               let context = canvas.getContext('2d');
               // 定义 canvas 大小,也就是压缩后下载的图片大小
               while(image.width > 2000 || image.height > 2000) {
                   let percent = 0.5;
                   if(image.width / 2000 > 1) {
                       percent = 2000 / image.width;
                       image.width = image.width * percent;
                       image.height = image.height * percent;
                   }
                   else if(image.height / 2000 > 1) {
                       percent = 2000 / image.height;
                       image.width = image.width * percent;
                       image.height = image.height * percent;
                   }
               }
               let imageWidth = image.width; //压缩后图片的大小
               let imageHeight = image.height;
               canvas.width = imageWidth;
               canvas.height = imageHeight;
               // 图片不压缩,全部加载展示
               context.drawImage(image, 0, 0, imageWidth, imageHeight);
               console.log(imageWidth, "height ==> ", imageHeight)
               // 图片按压缩尺寸载入
               // let imageWidth = 500; //压缩后图片的大小
               // let imageHeight = 200;
               // context.drawImage(image, 0, 0, 500, 200);
               // 图片去截取指定位置载入
               // context.drawImage(image,100, 100, 100, 100, 0, 0, imageWidth, imageHeight);
               if(type == 'jpg') {
                   type = 'jpeg'
               }
               vm.imgBase64 = canvas.toDataURL(`image/${type}`, 0.5);
               resultBlob = vm.dataURItoBlob(vm.imgBase64);
               if(type == 'jpeg' || type == 'jpg') {   
                   resolve(new File([resultBlob], new Date().getTime() + '.jpg'))
               }
               else if(type == 'png') {
                   resolve(new File([resultBlob], new Date().getTime() + '.png'))
               }
           };
       }
   })
},
/* base64转Blob对象 */
dataURItoBlob(data) {
   let byteString;
   if(data.split(',')[0].indexOf('base64') >= 0) {
       byteString = atob(data.split(',')[1])
   }else {
       byteString = unescape(data.split(',')[1])
   }
   let mimeString = data
       .split(',')[0]
       .split(':')[1]
       .split(';')[0];
   let ia = new Uint8Array(byteString.length)
   for( let i = 0; i < byteString.length; i += 1) {
       ia[i] = byteString.charCodeAt(i)
   }
   return new Blob([ia], {type: mimeString})
},

微信小程序端

小程序端由于canvas不能隐藏,因此要在wxml上提前写好canvas,而且最新版的小程序canvas支持2d渲染了,也就是说方式和H5端一样这里用的是uni-app,如果是原生小程序开发把uni类换为wx类,我的上传方法调用的是wx.chooseMedia

<canvas canvas-id="canvas2d" type="2d" id="handle-canvas" :style="{width: cWidth + 'px', height: cHeight + 'px'}" style="position: absolute;right: 0;top:-4000px;"></canvas>
<canvas canvas-id="canva2d-test" type="2d" id="test-canvas" :style="{width: previewWidth + 'px', height: previewHeight + 'px'}" style="position: absolute;right: 0;top:-4000px;"></canvas>
beforeRead(file) {
   let _this = this;
   let testmsg = file.fileType;
   console.log(file)
   this.imgSize = (file.size / 1024).toFixed(1);
   if(this.imgSize < 30) {
       console.log('size small')
       this.$toast({
           message: '上传文件大小不能小于 30KB!',
           type: 'error'
       });
       return  this.imgSize > 30
   }
   if(that.imgSize / 1024 > 2) {
       that.scale = true
   }
   that.$operationLog({
       btnId: "wxminiprogram-upload image",
       type: '0'
   })
   if(that.imgSize / 1024 > 4) {
       return new Promise((resolve, reject) => {
           // #ifdef H5
           const fileNames = file.name.split('.');
           const type = fileNames[fileNames.length - 1];
           // #endif
           // #ifdef MP-WEIXIN
           const type = 'image'
           // #endif
           console.log('超大了')
           // 如果超大了,则压缩图片
           that.handleCompressImage(file, type).then(res => {
               console.log("res ==> ", res)
               file.url = res
               resolve(file)
           })
       })
   }
   else {
       return new Promise((resolve, reject) => {
           // #ifdef H5
           const fileNames = file.name.split('.');
           const type = fileNames[fileNames.length - 1];
           // #endif
           // #ifdef MP-WEIXIN
           const type = 'image'
           // #endif
           that.handleImageSize(file, type).then(res => {
               file.url = res
               resolve(file)
           })
       })
   }
       // return file
},
/* 图片压缩方法-canvas压缩 */
handleCompressImage(imgFile, type){
    let vm = this;
    // 图片的文件对象
    uni.showLoading({
        title: '正在上传中',
        mask: true
    });
    // #ifdef MP-WEIXIN
    return new Promise((resolve, reject) => {
    	//这个方法获取图片的原生大小,这里传入的是路径
        wx.getImageInfo({
            src: imgFile.path || imgFile.url || imgFile.tempFilePath,
            success: function(res) {
                const query = uni.createSelectorQuery().in(vm);
                query.select("#handle-canvas").fields({
                    node: true,
                    size: true
                }).exec(ret => {
                    const canvas = ret[0].node;
                    let percent = 0.5;
                    const ctx1 = canvas.getContext('2d');
                    let image = canvas.createImage()
                    image.src = imgFile.path || imgFile.url || imgFile.tempFilePath;
                    let canvasWidth = res.width;
                    let canvasHeight = res.height;
                    if(res.width > 2000 || res.height > 2000) {
                        while(canvasWidth > 2000 || canvasHeight > 2000) {
                            if(canvasWidth / 2000 > 1) {
                                percent = 2000 / res.width;
                                canvasWidth = Math.trunc(res.width * percent);
                                canvasHeight = Math.trunc(res.height * percent);
                            }
                            if(canvasHeight / 2000 > 1) {
                                percent = 2000 / res.height;
                                canvasWidth = Math.trunc(res.width * percent)
                                canvasHeight = Math.trunc(res.height * percent)
                            }
                        }
                    }
                    canvas.width = canvasWidth;
                    canvas.height = canvasHeight;
                    image.onload = function() {
                    	// 与H5不一样的是这个图片对象不需要指定宽高,自动适配,只需要设定src属性即可
                        ctx1.drawImage(image, 0, 0, canvasWidth, canvasHeight);
                        let dataURL = canvas.toDataURL("image/png", 1);
                        let dataURL300DPI = changeDpiDataUrl(dataURL, 300)
                        let imgPath = wx.env.USER_DATA_PATH + "/tempImage" + random_int_str(2) + '.png';
                        let imageData = dataURL300DPI.replace(/^data:image\/\w+;base64,/, "");
                        //利用微信的文件系统写入一个图片
                        let fs = wx.getFileSystemManager();
                        fs.writeFileSync(imgPath, imageData , "base64");
                        // 这个路径就是你要上传的目标文件路径
                        resolve(imgPath)
                    }
                })
                // end
            }
        })
    })
    // #endif
},

changeDpiDataUrl方法源码

这个是从npm包changedpi那里复制出来的方法

'use strict';

var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
        // Regular expression to check formal correctness of base64 encoded strings
        b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
function btoa(string) {
    string = String(string);
    var bitmap, a, b, c,
        result = "",
        i = 0,
        rest = string.length % 3; // To determine the final padding
    
    for (; i < string.length;) {
        if ((a = string.charCodeAt(i++)) > 255 ||
            (b = string.charCodeAt(i++)) > 255 ||
            (c = string.charCodeAt(i++)) > 255)
            throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");
    
        bitmap = (a << 16) | (b << 8) | c;
        result += b64.charAt(bitmap >> 18 & 63) + b64.charAt(bitmap >> 12 & 63) +
            b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63);
    }
    
    // If there's need of padding, replace the last 'A's with equal signs
    return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result;
}

function atob(string) {
    // atob can work with strings with whitespaces, even inside the encoded part,
    // but only \t, \n, \f, \r and ' ', which can be stripped.
    string = String(string).replace(/[\t\n\f\r ]+/g, "");
    if (!b64re.test(string))
        throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
    
    // Adding the padding if missing, for semplicity
    string += "==".slice(2 - (string.length & 3));
    var bitmap, result = "",
        r1, r2, i = 0;
    for (; i < string.length;) {
        bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 |
            (r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++)));
    
        result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) :
            r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) :
            String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255);
    }
    return result;
}

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.changeDpiBlob = changeDpiBlob;
exports.changeDpiDataUrl = changeDpiDataUrl;

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function createPngDataTable() {
  /* Table of CRCs of all 8-bit messages. */
  var crcTable = new Int32Array(256);
  for (var n = 0; n < 256; n++) {
    var c = n;
    for (var k = 0; k < 8; k++) {
      c = c & 1 ? 0xedb88320 ^ c >>> 1 : c >>> 1;
    }
    crcTable[n] = c;
  }
  return crcTable;
}

function calcCrc(buf) {
  var c = -1;
  if (!pngDataTable) pngDataTable = createPngDataTable();
  for (var n = 0; n < buf.length; n++) {
    c = pngDataTable[(c ^ buf[n]) & 0xFF] ^ c >>> 8;
  }
  return c ^ -1;
}

var pngDataTable = void 0;

var PNG = 'image/png';
var JPEG = 'image/jpeg';

// those are 3 possible signature of the physBlock in base64.
// the pHYs signature block is preceed by the 4 bytes of lenght. The length of
// the block is always 9 bytes. So a phys block has always this signature:
// 0 0 0 9 p H Y s.
// However the data64 encoding aligns we will always find one of those 3 strings.
// this allow us to find this particular occurence of the pHYs block without
// converting from b64 back to string
var b64PhysSignature1 = 'AAlwSFlz';
var b64PhysSignature2 = 'AAAJcEhZ';
var b64PhysSignature3 = 'AAAACXBI';

var _P = 'p'.charCodeAt(0);
var _H = 'H'.charCodeAt(0);
var _Y = 'Y'.charCodeAt(0);
var _S = 's'.charCodeAt(0);

function changeDpiBlob(blob, dpi) {
  // 33 bytes are ok for pngs and jpegs
  // to contain the information.
  var headerChunk = blob.slice(0, 33);
  return new Promise(function (resolve, reject) {
    var fileReader = new FileReader();
    fileReader.onload = function () {
      var dataArray = new Uint8Array(fileReader.result);
      var tail = blob.slice(33);
      var changedArray = changeDpiOnArray(dataArray, dpi, blob.type);
      resolve(new Blob([changedArray, tail], { type: blob.type }));
    };
    fileReader.readAsArrayBuffer(headerChunk);
  });
}

function changeDpiDataUrl(base64Image, dpi) {
  var dataSplitted = base64Image.split(',');
  var format = dataSplitted[0];
  var body = dataSplitted[1];
  var type = void 0;
  var headerLength = void 0;
  var overwritepHYs = false;
  if (format.indexOf(PNG) !== -1) {
    type = PNG;
    var b64Index = detectPhysChunkFromDataUrl(body);
    // 28 bytes in dataUrl are 21bytes, length of phys chunk with everything inside.
    if (b64Index >= 0) {
      headerLength = Math.ceil((b64Index + 28) / 3) * 4;
      overwritepHYs = true;
    } else {
      headerLength = 33 / 3 * 4;
    }
  }
  if (format.indexOf(JPEG) !== -1) {
    type = JPEG;
    headerLength = 18 / 3 * 4;
  }
  // 33 bytes are ok for pngs and jpegs
  // to contain the information.
  var stringHeader = body.substring(0, headerLength);
  var restOfData = body.substring(headerLength);
  var headerBytes = atob(stringHeader);
  var dataArray = new Uint8Array(headerBytes.length);
  for (var i = 0; i < dataArray.length; i++) {
    dataArray[i] = headerBytes.charCodeAt(i);
  }
  var finalArray = changeDpiOnArray(dataArray, dpi, type, overwritepHYs);
  // 
  var base64Header = btoa(String.fromCharCode.apply(String, _toConsumableArray(finalArray)));
  //
  // var base64Header = weappJwt(String.fromCharCode.apply(String, _toConsumableArray(finalArray)))
  return [format, ',', base64Header, restOfData].join('');
}

function detectPhysChunkFromDataUrl(data) {
  var b64index = data.indexOf(b64PhysSignature1);
  if (b64index === -1) {
    b64index = data.indexOf(b64PhysSignature2);
  }
  if (b64index === -1) {
    b64index = data.indexOf(b64PhysSignature3);
  }
  // if b64index === -1 chunk is not found
  return b64index;
}

function searchStartOfPhys(data) {
  var length = data.length - 1;
  // we check from the end since we cut the string in proximity of the header
  // the header is within 21 bytes from the end.
  for (var i = length; i >= 4; i--) {
    if (data[i - 4] === 9 && data[i - 3] === _P && data[i - 2] === _H && data[i - 1] === _Y && data[i] === _S) {
      return i - 3;
    }
  }
}

function changeDpiOnArray(dataArray, dpi, format, overwritepHYs) {
  if (format === JPEG) {
    dataArray[13] = 1; // 1 pixel per inch or 2 pixel per cm
    dataArray[14] = dpi >> 8; // dpiX high byte
    dataArray[15] = dpi & 0xff; // dpiX low byte
    dataArray[16] = dpi >> 8; // dpiY high byte
    dataArray[17] = dpi & 0xff; // dpiY low byte
    return dataArray;
  }
  if (format === PNG) {
    var physChunk = new Uint8Array(13);
    // chunk header pHYs
    // 9 bytes of data
    // 4 bytes of crc
    // this multiplication is because the standard is dpi per meter.
    dpi *= 39.3701;
    physChunk[0] = _P;
    physChunk[1] = _H;
    physChunk[2] = _Y;
    physChunk[3] = _S;
    physChunk[4] = dpi >>> 24; // dpiX highest byte
    physChunk[5] = dpi >>> 16; // dpiX veryhigh byte
    physChunk[6] = dpi >>> 8; // dpiX high byte
    physChunk[7] = dpi & 0xff; // dpiX low byte
    physChunk[8] = physChunk[4]; // dpiY highest byte
    physChunk[9] = physChunk[5]; // dpiY veryhigh byte
    physChunk[10] = physChunk[6]; // dpiY high byte
    physChunk[11] = physChunk[7]; // dpiY low byte
    physChunk[12] = 1; // dot per meter....

    var crc = calcCrc(physChunk);

    var crcChunk = new Uint8Array(4);
    crcChunk[0] = crc >>> 24;
    crcChunk[1] = crc >>> 16;
    crcChunk[2] = crc >>> 8;
    crcChunk[3] = crc & 0xff;

    if (overwritepHYs) {
      var startingIndex = searchStartOfPhys(dataArray);
      dataArray.set(physChunk, startingIndex);
      dataArray.set(crcChunk, startingIndex + 13);
      return dataArray;
    } else {
      // i need to give back an array of data that is divisible by 3 so that
      // dataurl encoding gives me integers, for luck this chunk is 17 + 4 = 21
      // if it was we could add a text chunk contaning some info, untill desired
      // length is met.

      // chunk structur 4 bytes for length is 9
      var chunkLength = new Uint8Array(4);
      chunkLength[0] = 0;
      chunkLength[1] = 0;
      chunkLength[2] = 0;
      chunkLength[3] = 9;

      var finalHeader = new Uint8Array(54);
      finalHeader.set(dataArray, 0);
      finalHeader.set(chunkLength, 33);
      finalHeader.set(physChunk, 37);
      finalHeader.set(crcChunk, 50);
      return finalHeader;
    }
  }
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值