前端/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" id="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" id="file" onChange={handleChange} />
    </div>
  );
}

export default App;

 

 就這樣吧,勉強能用,現在導出的都有800-900kb,還行吧

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM