preface

Uploading images/videos/files is a common problem, but once the image is too large, it can lead to a bad operation experience. Image uploading is a common business scenario in the front-end. Whether foreground or background, appropriate image compression processing, can significantly improve the user experience. And in the background management system, picture compression can not only improve the background administrator operation experience, but also can prevent the background setting too large pictures lead to the foreground picture loading too long, thus affecting the user experience.

About Compression

thinking

Think about the basic process of compressing images

  • Input is read into a file and converted to Base64 encoding using FileReader
  • Create img with its SRC pointing to base64
  • Create a new canvas and draw IMG on the canvas
  • Export canvas as Base64 or Blob using Canvas. ToDataURL /toBlob
  • Convert base64 or Blob to File

Breaking down these steps one by one, we’ll see that it seems to involve image quality in the Canvas. ToDataURL, so let’s start there.

To prepare

HTMLCanvasElement.toDataURL()

HTMLCanvasElement. ToDataURL () method returns a data URI contains pictures show. You can use the type parameter, which defaults to PNG. The resolution of the image is 96dpi.

  • If the height or width of the canvas is 0, the string “data:,” is returned.
  • If the passed type is not “image/ PNG”, but the returned value begins with “data:image/ PNG”, then the passed type is not supported.

grammar

canvas.toDataURL(type, encoderOptions);

parameter

  • The type of the optional

Image format, default is image/ PNG

  • EncoderOptions optional

When the image format is specified as Image/JPEG or Image /webp, the image quality can be selected from a range of 0 to 1. If the value is out of range, the default value 0.92 will be used. Other parameters are ignored. Chrome supports the “image/webp” type.

guess

Maybe the smaller the second parameter (quality) of toDataURL(type,quality) is, the smaller the file size is

practice

If you look at the original image, the size is 7.31 KB

ToDataURL (type,quality) Quality default: 0.92

<input id="fileInput" type="file" />
<img id="img" src="" alt="">
Copy the code
let fileId = document.getElementById('fileInput')
let img = document.getElementById('img')
fileId.onchange = function (e) {
  let file = e.target.files[0]
  compressImg(file, 0.92).then(res= > {// See the appendix for the compressImg method
    console.log(res)
    img.src = window.URL.createObjectURL(res.file); })}Copy the code

For the compressImg method, see the appendix

You can see that the image size is8.83 KBAfter compression, the picture is bigger instead. How is this?

It seems that the original guess was not quite right, so let’s keep looking.

fileId.onchange = function (e) {
  let file = e.target.files[0]
  compressImg(file, 0.1).then(res= > {// See the appendix for the compressImg method
    console.log(res)
    img.src = window.URL.createObjectURL(res.file); })}Copy the code

qualityFor 0.1, the image is only1.63 KB, the same quality also decreased

Continue to… A long time later

So let’s do it on a line chart to make it more intuitive

I took a few more images and asked them to use the default value of 0.92, all of which were larger than the original

Therefore, the default value of the image is often larger than the original

Let’s take a look at what quality can maximize the compression efficiency of the image

Maximize compression efficiency, that is, maximize compression without affecting image quality

After trying a series of images, I found that when the quality is between 0.2 and 0.5, the image quality does not change much. The smaller the quality value is, the more impressive the compression efficiency is (that is, when the quality is around 0.2, the compression image can be maximized without much impact on the image quality).

conclusion

After practice, it can be concluded that the default value of the image is often higher than the original image quality.

whenqualityBetween 0.2 and 0.5, the image quality does not change much,qualityThe smaller the value of, the more impressive the compression efficiency (that is, at about 0.2, the compression image can be maximized, while the image quality is not too much affected)

The appendix

/** * Compression method *@param {string} The file file *@param {Number} Quality The value ranges from 0 to 1 */
function compressImg(file, quality) {
  if (file[0]) {
    return Promise.all(Array.from(file).map(e= > compressImg(e,
      quality))) // Return a Promise array if it is a file array
  } else {
    return new Promise((resolve) = > {
      const reader = new FileReader() / / create FileReader
      reader.onload = ({ target: { result: src } }) = > {
        const image = new Image() // Create the img element
        image.onload = async() = > {const canvas = document.createElement('canvas') // Create canvas element
          canvas.width = image.width
          canvas.height = image.height
          canvas.getContext('2d').drawImage(image, 0.0, image.width, image.height) / / drawing canvas
          const canvasURL = canvas.toDataURL('image/jpeg', quality)
          const buffer = atob(canvasURL.split(', ') [1])
          let length = buffer.length
          const bufferArray = new Uint8Array(new ArrayBuffer(length))
          while (length--) {
            bufferArray[length] = buffer.charCodeAt(length)
          }
          const miniFile = new File([bufferArray], file.name, {
            type: 'image/jpeg'
          })
          resolve({
            file: miniFile,
            origin: file,
            beforeSrc: src,
            afterSrc: canvasURL,
            beforeKB: Number((file.size / 1024).toFixed(2)),
            afterKB: Number((miniFile.size / 1024).toFixed(2))
          })
        }
        image.src = src
      }
      reader.readAsDataURL(file)
    })
  }
}
Copy the code

upgrading

Boundary problems were found in the process of use, that is, the image size is too large, IOS size limit, PNG transparent image black and other problems

Therefore, the large size image is optimized and scaled down, which greatly improves the compression efficiency

For example, take a 14M, 6016X4016 image

a14MThe original image (6016X4016) is left after compression (1400X935) on the premise of changing only the size without changing the mass139.62 KB

It is conceivable that the limitation of size can maximize the compression efficiency


  /** * Compression method *@param {file} The file file *@param {Number} Quality Image quality (between 0 and 1, the value is 0.92 by default) */
  compressImg(file, quality) {
    var qualitys = 0.52
    console.log(parseInt((file.size / 1024).toFixed(2)))
    if (parseInt((file.size / 1024).toFixed(2))"1024) {
      qualitys = 0.85
    }
    if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
      qualitys = 0.92
    }
    if (quality) {
      qualitys = quality
    }
    if (file[0]) {
      return Promise.all(Array.from(file).map(e= > this.compressImg(e,
        qualitys))) // Return a Promise array if it is a file array
    } else {
      return new Promise((resolve) = > {
        console.log(file)
        if ((file.size / 1024).toFixed(2) < 300) {
          resolve({
            file: file
          })
        } else {
          const reader = new FileReader() / / create FileReader
          reader.onload = ({ target: { result: src } }) = > {
            const image = new Image() // Create the img element
            image.onload = async() = > {const canvas = document.createElement('canvas') // Create canvas element
              const context = canvas.getContext('2d')
              var targetWidth = image.width
              var targetHeight = image.height
              var originWidth = image.width
              var originHeight = image.height
              if (1 * 1024< =parseInt((file.size / 1024).toFixed(2&&))parseInt((file.size / 1024).toFixed(2)) < =10 * 1024) {
                var maxWidth = 1600
                var maxHeight = 1600
                targetWidth = originWidth
                targetHeight = originHeight
                // The image size exceeds the limit
                if (originWidth > maxWidth || originHeight > maxHeight) {
                  if (originWidth / originHeight > maxWidth / maxHeight) {
                    // Wider. The size is defined according to the width
                    targetWidth = maxWidth
                    targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                  } else {
                    targetHeight = maxHeight
                    targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                  }
                }
              }
              if (10 * 1024< =parseInt((file.size / 1024).toFixed(2&&))parseInt((file.size / 1024).toFixed(2)) < =20 * 1024) {
                maxWidth = 1400
                maxHeight = 1400
                targetWidth = originWidth
                targetHeight = originHeight
                // The image size exceeds the limit
                if (originWidth > maxWidth || originHeight > maxHeight) {
                  if (originWidth / originHeight > maxWidth / maxHeight) {
                    // Wider. The size is defined according to the width
                    targetWidth = maxWidth
                    targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                  } else {
                    targetHeight = maxHeight
                    targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                  }
                }
              }
              canvas.width = targetWidth
              canvas.height = targetHeight
              context.clearRect(0.0, targetWidth, targetHeight)
              context.drawImage(image, 0.0, targetWidth, targetHeight) / / drawing canvas
              const canvasURL = canvas.toDataURL('image/jpeg', qualitys)
              const buffer = atob(canvasURL.split(', ') [1])
              let length = buffer.length
              const bufferArray = new Uint8Array(new ArrayBuffer(length))
              while (length--) {
                bufferArray[length] = buffer.charCodeAt(length)
              }
              const miniFile = new File([bufferArray], file.name, {
                type: 'image/jpeg'
              })
              console.log({
                file: miniFile,
                origin: file,
                beforeSrc: src,
                afterSrc: canvasURL,
                beforeKB: Number((file.size / 1024).toFixed(2)),
                afterKB: Number((miniFile.size / 1024).toFixed(2)),
                qualitys: qualitys
              })
              resolve({
                file: miniFile,
                origin: file,
                beforeSrc: src,
                afterSrc: canvasURL,
                beforeKB: Number((file.size / 1024).toFixed(2)),
                afterKB: Number((miniFile.size / 1024).toFixed(2))
              })
            }
            image.src = src
          }
          reader.readAsDataURL(file)
        }
      })
    }
  },
Copy the code

Write in the last

I am Liangcheng A, a front end, who loves technology and life.

I’m glad to meet you.

If you want to learn more, please click here and look forward to your little ⭐⭐

  • Feel free to correct any mistakes in the comments section, and if this post helped you, feel free to like and follow 😊

  • This article was originally published in Nuggets and cannot be reproduced without permission at 💌