前端图片压缩踩坑

4 阅读2分钟

squoosh

  • squoosh.app/
  • github里的代码只能打包后部署网页,不好迁移到vue项目里

browser-image-compression

  • 前端压缩图片经常能搜到这个npm包,实际体验很差
  • 有些图片压缩很慢,压缩一分钟的都有
  • 压缩完一些图片还会变的更大

基于canvas手搓

压缩方法

/**
 * Compress an image file
 * @param {File} file - The image file to compress
 * @param {Object} options - Compression options
 * @param {number} [options.quality=0.8] - Quality from 0 to 1
 * @param {string} [options.format='image/jpeg'] - Output format
 * @param {number} [options.maxWidth=1920] - Max width
 * @param {number} [options.maxHeight=1080] - Max height
 * @returns {Promise<Blob>} - Compressed image blob
 */
export function compressImage(file, options = {}) {
  const {
    quality = 0.75,
    format = 'image/jpeg',
    maxWidth = 1920,
    maxHeight = 1080
  } = options

  return new Promise((resolve, reject) => {
    try {
      // Load image
      const img = new Image()
      img.onload = async () => {
        try {
          // Calculate new dimensions
          let { width, height } = img
          const aspectRatio = width / height

          if (width > maxWidth) {
            width = maxWidth
            height = Math.round(width / aspectRatio)
          }
          if (height > maxHeight) {
            height = maxHeight
            width = Math.round(height * aspectRatio)
          }

          // Create canvas and draw
          const canvas = document.createElement('canvas')
          canvas.width = width
          canvas.height = height

          const ctx = canvas.getContext('2d')
          ctx.imageSmoothingEnabled = true
          ctx.imageSmoothingQuality = 'high'
          ctx.drawImage(img, 0, 0, width, height)

          // Compress
          canvas.toBlob((blob) => {
            if (blob) {
              resolve(blob)
            } else {
              reject(new Error('Failed to create blob'))
            }
          }, format, quality)
        } catch (e) {
          reject(e)
        }
      }
      img.onerror = () => {
        reject(new Error('Failed to load image'))
      }
      img.src = URL.createObjectURL(file)
    } catch (e) {
      reject(e)
    }
  })
}

/**
 * Get image dimensions without full loading
 * @param {File} file
 * @returns {Promise<{width: number, height: number}>}
 */
export function getDimensions(file) {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.onload = () => resolve({ width: img.width, height: img.height })
    img.onerror = reject
    img.src = URL.createObjectURL(file)
  })
}

/**
 * Format file size for display
 * @param {number} bytes
 * @returns {string}
 */
export function formatSize(bytes) {
  if (bytes < 1024) return bytes + ' B'
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
  return (bytes / (1024 * 1024)).toFixed(2) + ' MB'
}

/**
 * Calculate compression ratio
 * @param {number} original
 * @param {number} compressed
 * @returns {number}
 */
export function getCompressionRatio(original, compressed) {
  return Math.round((1 - compressed / original) * 100)
}

使用

  • 图片的file直接传入,得到压缩后的file
export function compressImgHandler(file) {
  // 图片压缩后,生成新的 file
  return new Promise((resolve) => {
    compressImage(file).then(compressedBlob => {
      const compressedFile = new File([compressedBlob], file.name, {
        type: compressedBlob.type,
        lastModified: Date.now(),
      })
      console.log('比原图小了', getCompressionRatio(file.size, compressedFile.size) + '%')
      // 比较一下,若压缩后文件变大,用原文件
      if (compressedFile.size > file.size) {
        resolve({ file })
      } else {
        resolve({ file: compressedFile })
      }
    }).catch(error => {
      // 图片压缩失败,直接上传原文件
      console.error('图片压缩失败:', error)
      resolve({ file })
    })
  })
}