This paper mainly explains how to select pictures on the mobile terminal based on Vue + Vant, compress pictures with Canvas, and finally upload them to the server. It also wraps a utility class for direct invocation.

1. Tool class encapsulation

Without further ado, the code encapsulates a CompressImageUtils utility class:

/** * Image compression tool class * The maximum height and width are 500, if the size exceeds the scale will be equal scale. * * Note that the compressed image may be larger than the original image, determine the size at the call point and decide to upload the pre - or post-compressed image to the server. * /

// Convert Base64 to BLOB
export function convertBase64UrlToBlob(urlData) {
  let arr = urlData.split(', ')
  let mime = arr[0].match(/ : (. *?) ; /) [1]
  let bstr = atob(arr[1])
  let n = bstr.length
  let u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new Blob([u8arr], {type: mime})
}


// Compress the image
export function compressImage(path) {

  // Maximum height
  const maxHeight = 500;
  // Maximum width
  const maxWidth = 500;

  return new Promise((resolve, reject) = > {
    let img = new Image();
    img.src = path;
    img.onload = function () {
      const originHeight = img.height;
      const originWidth = img.width;
      let compressedWidth = img.height;
      let compressedHeight = img.width;
      if ((originWidth > maxWidth) && (originHeight > maxHeight)) {
        // Wider and taller,
        if ((originHeight / originWidth) > (maxHeight / maxWidth)) {
          // More severe high narrow type, determine the maximum height, compression width
          compressedHeight = maxHeight
          compressedWidth = maxHeight * (originWidth / originHeight)
        } else {
          // More serious short width type, determine the maximum width, compression height
          compressedWidth = maxWidth
          compressedHeight = maxWidth * (originHeight / originWidth)
        }
      } else if (originWidth > maxWidth && originHeight <= maxHeight) {
        // Wider, but shorter, with maxWidth as the benchmark
        compressedWidth = maxWidth
        compressedHeight = maxWidth * (originHeight / originWidth)
      } else if (originWidth <= maxWidth && originHeight > maxHeight) {
        // It is narrow, but it is high
        compressedHeight = maxHeight
        compressedWidth = maxHeight * (originWidth / originHeight)
      } else {
        // Meet the width and height limit, do not compress
      }
      / / generated canvas
      let canvas = document.createElement('canvas');
      let context = canvas.getContext('2d');
      canvas.height = compressedHeight;
      canvas.width = compressedWidth;
      context.clearRect(0.0, compressedWidth, compressedHeight);
      context.drawImage(img, 0.0, compressedWidth, compressedHeight);
      let base64 = canvas.toDataURL('image/*'.0.8);
      let blob = convertBase64UrlToBlob(base64);
      // The callback function returns the value of blob. You can also return the base64 value based on your requirements
      resolve(blob)
    }
  })
}
Copy the code

The maximum width and height are defined as 500. If at least one of the width and height of the image exceeds 500, it will be compressed in equal proportions without worrying about deformation. You can change the maxWidth and maxHeight according to your project needs.

Here, the maximum height and width of compression are directly written to 500, not passed at call time. Because the compression logic and size of a project are generally consistent, there is no need to pass it every time it is called. Of course, if you want to be more flexible, you can pass in the maxWidth, maxHeight, and compression quality in the compressImage method.

The compressImage method returns the BLOB value, which can be changed to base64 depending on the server interface requirements by changing resolve(blob) to resolve(base64).

Note that for some images that are less than 500 in width and height and have a very small resolution, the compression may be larger than before. The guess may be that the image resolution generated by canvas is higher than the original, so the final image is bigger than before compression. If the size of the compressed image is smaller than the original image, upload the compressed image. If the compressed size is larger than the original image, upload the original image.

Two, how to use

Introduce CompressImageUtils into the target file and then call compressImage to get the compressed result in the callback. Note that the compressImage method returns a Promise. Omit other extraneous code, leaving only those related to compressed images and uploads:

<template> <div> <van-uploader v-model="fileList" :after-read="afterRead" /> </div> </template> <script> import {compressImage} from '.. /.. /utils/CompressImageUtils' export default { components: {}, methods: AfterRead (file) {console.log('afterRead------', file); this._compressAndUploadFile(file); }, // uploadfile (file) {compressanduploadfile (file) {compressImage(file.content). Then (result => {console.log(' console.log ', result); // result is the compressed result console.log(' size before compression ', file.file.size); Console. log(' compressed size ', result.size); If (result.size > file.file.size){console.log(' upload original image '); if (result.size > file.file.size){console.log(' upload original image '); // Upload the original image to this._uploadFile(file.file, file.file-name); } else {// Compressed smaller than the original, upload compressed console.log(' upload compressed image '); This._uploadFile(result, file.file.name)}})}, // uploadFile(file, filename) {let params = new FormData(); params.append("file", file, filename); this.$api.uploadImage(params).then(res => { console.log('uploadImage', res); }). Catch (err => {console.log('err', err); }); }, } } </script>Copy the code

A layer judgment is added to the returned result. If the compressed result is larger than the original, the original image will be uploaded. Compressed smaller than the original, upload compressed. Solve the situation where the compressed image is larger than the original image. This.$api.uploadimage (params) is the enclosing API method called as follows:

  // Upload the image
 uploadImage(params){
    return axios.post(`${base}/api/v1/file`, params, {
      headers: {'content-type': 'multipart/form-data'}})},Copy the code

Three, the effect of use

First, upload a very large image with the size of 6016 × 4016 and 16.8m. Check the output log. After compression, the size is only about 260K. In this case, determine that the compressed image is smaller than that before compression and upload the compressed image to the server.

Let’s look at a 300 × 300, 12 K small graph. The size before compression is 11252, and after compression is 93656, which is much larger. In this case, the size after compression is larger than that before compression, and the uploaded image is the original image.

Summary: This utility class is very effective in compressing large images, no matter how big the image is, the compression should not exceed 300K. However, for some small graphs, the compression may become larger. Adding a layer of pre – and post-compression size comparisons to the call would solve this problem perfectly. Of course, it can also be judged within the utility class, but I think it is better not to put the code related to business logic in the common utility class.