前端/JS/React/ES6:纯前端实现图片压缩技术

网上图片压缩文章抄来抄去,真没意思,我自己写一个

import React from 'react';
// import logo from './logo.svg';
import './App.css';

function App() {

  const handleChange = e => {
    e.persist() // 为了看到原生file的内容,不加这句,原生file内容会被react隐藏
    let fileObj = e.target.files[0]
    // console.log(e)
    console.log(fileObj)

    // 设定标准上限为1MB,进行压缩处理
    compress(fileObj, 1024, (data) => {
      console.log(data)
    })
  }

  // 异步读取图片的promise
  const loadImageAsync = (url) => {
    return new Promise(function (resolve, reject) {
      const image = new Image()

      image.onload = function () {
        resolve(image)
      };

      image.onerror = function () {
        reject(new Error('Could not load image at ' + url))
      };

      image.src = url
    })
  }

  // 异步转换成base64编码的promise
  const fileToImgAsync = (file) => {
    return new Promise(function (resolve, reject) {
      const reader = new FileReader()

      reader.onload = function (e) {
        resolve(e.target.result);
      };

      reader.onerror = function () {
        reject(new Error('readAsDataURL:fail'))
      };

      reader.readAsDataURL(file)
    });
  }

  const downloadFileByBlob = (blobUrl, filename) => {
    const a = document.createElement('a')
    a.download = filename
    a.style.display = 'none'
    a.href = blobUrl
    // 触发点击
    document.body.appendChild(a)
    a.click()
    // 然后移除
    document.body.removeChild(a)
  }

  // async 搭配 promise 使用
  const compress = async (file, maxSizeKB, succFunc) => {
    if (file.size > 3 * 1024 * 1024) {
      let rate = 0 // 压缩率

      // 文件转图片
      const dataUrl = await fileToImgAsync(file)

      // 图片转画布
      const image = await loadImageAsync(dataUrl)
      // console.log(dataUrl, image)

      // 文件大小KB, file.size给的是字节Byte
      const fileSizeKB = file.size / 1024
      console.log(fileSizeKB)

      // 当图片大小超标,才进行压缩
      if (fileSizeKB > maxSizeKB) {
        // 计算压缩率
        rate = (fileSizeKB - maxSizeKB) / fileSizeKB
        console.log('压缩率:', rate)
        console.log('压缩后文件大小:', fileSizeKB * (1 - rate), 'kb')
      }

      // 画布执行压缩
      let canvas = document.createElement('canvas')
      let context = canvas.getContext('2d')
      const cvWidth = image.width * (1 - rate)
      const cvHeight = image.height * (1 - rate)
      console.log(image.width, image.height, cvWidth, cvHeight)

      canvas.height = cvHeight
      canvas.width = cvWidth
      context.clearRect(0, 0, cvWidth, cvHeight)
      context.drawImage(image, 0, 0, cvWidth, cvWidth)

      // 导出图片
      canvas.toBlob((blob) => {

        // 方式一:下载到本地
        // const blobUrl = window.URL.createObjectURL(blob)
        // downloadFileByBlob(blobUrl, file.name)

        // 方式二:生成网页可读取的对象
        const newImage = new File([blob], file.name, { type: file.type });
        succFunc(newImage)
      });
    }
  }

  return (
    <div className="App">
      <input type="file"  onChange={handleChange} />
    </div>
  );
}

export default App;

其实我们可以发现一个问题,导出的图片,在window系统中,大小是 207kb并不是1024kb,很明显size的比例用在width height的比例上有出入,要加入调节因子,因此算法还可以优化,如下

import React from 'react';
// import logo from './logo.svg';
import './App.css';

function App() {

  const handleChange = e => {
    e.persist() // 为了看到原生file的内容,不加这句,原生file内容会被react隐藏
    let fileObj = e.target.files[0]
    // console.log(e)
    console.log(fileObj)

    // 设定标准上限为1MB,进行压缩处理
    compress(fileObj, 1024, (data) => {
      console.log(data)
    })
  }

  // 异步读取图片的promise
  const loadImageAsync = (url) => {
    return new Promise(function (resolve, reject) {
      const image = new Image()

      image.onload = function () {
        resolve(image)
      };

      image.onerror = function () {
        reject(new Error('Could not load image at ' + url))
      };

      image.src = url
    })
  }

  // 异步转换成base64编码的promise
  const fileToImgAsync = (file) => {
    return new Promise(function (resolve, reject) {
      const reader = new FileReader()

      reader.onload = function (e) {
        resolve(e.target.result);
      };

      reader.onerror = function () {
        reject(new Error('readAsDataURL:fail'))
      };

      reader.readAsDataURL(file)
    });
  }

  const downloadFileByBlob = (blobUrl, filename) => {
    const a = document.createElement('a')
    a.download = filename
    a.style.display = 'none'
    a.href = blobUrl
    // 触发点击
    document.body.appendChild(a)
    a.click()
    // 然后移除
    document.body.removeChild(a)
  }

  // async 搭配 promise 使用
  const compress = async (file, maxSizeKB, succFunc) => {
    if (file.size > maxSizeKB * 1024) {
      let rate = 0 // 压缩率

      // 文件转图片
      const dataUrl = await fileToImgAsync(file)

      // 图片转画布
      const image = await loadImageAsync(dataUrl)
      // console.log(dataUrl, image)

      // 文件大小KB, file.size给的是字节Byte
      const fileSizeKB = file.size / 1024
      console.log(fileSizeKB)

      // 当图片大小超标,才进行压缩
      if (fileSizeKB > maxSizeKB) {
        // 计算压缩率
        rate = (fileSizeKB - maxSizeKB) / fileSizeKB
        console.log('压缩率:', rate)
        console.log('压缩后文件大小:', fileSizeKB * (1 - rate), 'kb')
      }

      // 纠正因子,不加会导致压缩出的文件太小
      const factor = 0.2

      // 画布执行压缩
      let canvas = document.createElement('canvas')
      let context = canvas.getContext('2d')
      const cvWidth = image.width * (1 - rate + factor)
      const cvHeight = image.height * (1 - rate + factor)
      console.log(image.width, image.height, cvWidth, cvHeight)

      canvas.height = cvHeight
      canvas.width = cvWidth
      context.clearRect(0, 0, cvWidth, cvHeight)
      context.drawImage(image, 0, 0, cvWidth, cvWidth)

      // 导出图片
      canvas.toBlob((blob) => {

        // 方式一:下载到本地
        const blobUrl = window.URL.createObjectURL(blob)
        downloadFileByBlob(blobUrl, file.name)

        // 方式二:生成网页可读取的对象
        // const newImage = new File([blob], file.name, { type: file.type });
        // succFunc(newImage)
      });
    }
  }

  return (
    <div className="App">
      <input type="file"  onChange={handleChange} />
    </div>
  );
}

export default App;

就这样吧,勉强能用,现在导出的都有800-900kb,还行吧

参考:https://www.cnblogs.com/chenbeibei520/p/12534798.html