胖蔡叨叨叨
你听我说

前端图片压缩

使用VUE或者React框架加载HTML大图时极容易导致前端页面卡死报out of memory错误,报出这个错误还好,如果没有报错浏览器可能就无法操作了。

前端压缩

为保证前端页面正常渲染html大图,需要在html加载前压缩图片。前端解决这个问题有两种方案,一个是上传前压缩,一个是上传后压缩。上传前可以保证当前图片无论在哪里显示大小已经被压缩过,无需再去管理。上传后压缩可以保证服务器端拥有高清原图,显示前需要做一次压缩。
无论那种压缩,压缩方式基本一致

1.获取图片尺寸
2.计算压缩比率
3.通过canvas.toDataUrl 参数转化压缩图片
4.将base64字符转转成文件或者直接显示

上传前压缩

获取到本地文件后,通过FileReader将本地文件写入到内存中

// 获取文件图片的宽高
function getImageWHFromFile(file, fn, err) {
  try {
    // 读取上传之后的文件对象
    const imageReader = new FileReader()
    // 将图片的内存url地址添加到FileReader中
    imageReader.readAsDataURL(file)
    // 当图片完全加载到FileReader对象中后,触发下面的事件
    imageReader.addEventListener('loadend', function(e) {
    // 获取上传到本机内存中的图片的url地址
      const imageSrc = e.target.result
      // 调用计算图片大小的方法
      calculateImageSize(imageSrc).then(function({ image, width, height }) {
      // 得到图片的宽和高
        fn(image, width, height)
      }).catch(e => {
        err()
      })
    })
    // 可以做一些其他状态监听
  } catch (e) {
    err(file)
  }
}

计算内存中图片的宽高

// 根据图片地址获取图片的宽和高
function calculateImageSize(src) {
  return new Promise(function(resolve, reject) {
    const image = new Image()
    image.addEventListener('load', function(e) {
      resolve({
        image: image,
        width: e.target.width,
        height: e.target.height,
      })
    })
    image.addEventListener('error', function(e) {
      reject(Error('获取尺寸失败'))
    })
    // 将图片的url地址添加到图片地址中
    image.setAttribute('crossOrigin', 'Anonymous')
    image.src = src
  })
}

得到图片宽高后就可以计算压缩比率,通过图片真水的宽高和期望的宽高计算压缩率,基本上可以得到合适的图片尺寸

// 计算压缩比率
function getRatio(oriW, oriH, width, height) {
  const proW = (oriW / width).toFixed(5)
  const proH = (oriH / height).toFixed(5)
  return proW < proH ? proW : proH
}

得到压缩比率就可以执行压缩,压缩使用canvas实现

// 缩放img对象中的图片
function compressImageToBase64(image, quality) {
  const canvas = document.createElement('canvas')
  const context = canvas.getContext('2d')
  const imageWidth = image.width * quality
  const imageHeight = image.height * quality
  canvas.width = imageWidth
  canvas.height = imageHeight
  context.drawImage(image, 0, 0, imageWidth, imageHeight)
  return canvas.toDataURL('image/jpeg', quality)
}

压缩完得到图片的base64(DataURL)数据,可以用这个数据直接输出到img标签显示,或者继续加工生成图片文件
将base64转成blob对象

// DataURL转Blob对象
function dataURLToBlob(dataurl) {
  const arr = dataurl.split(',')
  const mime = arr[0].match(/:(.*?);/)[1]
  const bstr = atob(arr[1])
  let n = bstr.length
  const u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new Blob([u8arr], { type: mime })
}

将Blob转成File,调用接口用FormData的形式上传文件

// 对图片进行压缩并返回文件格式
function compressToFile(image, quality) {
  const blob = dataURLToBlob(compressImageToBase64(image, quality))
  return new File([blob], new Date().getTime() + '.jpg')
}

到此,就得到了一个压缩后的文件对象

上传后压缩

上传后压缩的意思是,服务器返回一个图片url后,如果这个url指向的图片尺寸很大,比如:10M,这时候在浏览器中显示这个图片会导致页面卡死。我的解决方案是压缩后再显示,压缩方式大概与上传前压缩类似,从calculateImageSize方法开始计算图片尺寸,然后进行压缩,得到base64数据后回调显示这个图片。这里存在一个问题:压缩开始后chrome的控制台无法打开。原因则是image加载图片后会在控制台的Network中输出base64格式的日志,这个日志占用了太大的内存。

服务器压缩

服务器压缩是最好的且能满足产品对大图要求的解决方案。前端上传原图,服务器压缩后生成两个地址,一个缩略图一个原图,缩略图供前端显示,原图供下载。+
附全部代码供参考

/**
 * @param {待压缩图片文件} file
 * @param {异步回调} fn
 * @param {压缩期望尺寸} size [width ,height]
 */
const fileCompressToFile = function(file, fn, size) {
  const _size = getComperssSize(size)
  if (_size === null) {
    fn(file)
    return
  }
  // 开启后执行压缩
  getImageWHFromFile(file, (image, width, height) => {
    const pro = getRatio(_size[0], _size[1], width, height)
    if (pro >= 1) {
      fn(file)
      return
    }
    // 开始压缩
    fn(compressToFile(image, pro))
  }, () => {
    fn(file)
  })
}

// 通过url压缩图片,压缩成功返回base64
const urlCompressToBase64 = function(url, fn, size) {
  const _size = getComperssSize(size)
  if (_size === null) {
    fn(url)
    return
  }
  calculateImageSize(url).then(function({ image, width, height }) {
    const pro = getRatio(_size[0], _size[1], width, height)
    if (pro >= 1) {
      fn(url)
      return
    }
    // 开始压缩
    fn(compressImageToBase64(image, pro))
  }).catch(e => {
    console.error(e)
    fn(url)
  })
}

// 格式化压缩尺寸
function getComperssSize(size) {
  if (!size) {
    return null
  }
  const defaultSize = 500
  if (Array.isArray(size)) {
    return [size[0] || defaultSize, size[1] || defaultSize]
  }
  const pf = parseFloat(size)
  return [pf || defaultSize, pf || defaultSize]
}

// 计算压缩比率
function getRatio(oriW, oriH, width, height) {
  const proW = (oriW / width).toFixed(5)
  const proH = (oriH / height).toFixed(5)
  return proW < proH ? proW : proH
}

// 缩放img对象中的图片
function compressImageToBase64(image, quality) {
  const canvas = document.createElement('canvas')
  const context = canvas.getContext('2d')
  const imageWidth = image.width * quality
  const imageHeight = image.height * quality
  canvas.width = imageWidth
  canvas.height = imageHeight
  context.drawImage(image, 0, 0, imageWidth, imageHeight)
  return canvas.toDataURL('image/jpeg', quality)
}

// 对图片进行压缩并返回文件格式
function compressToFile(image, quality) {
  const blob = dataURLToBlob(compressImageToBase64(image, quality))
  return new File([blob], new Date().getTime() + '.jpg')
}

// DataURL转Blob对象
function dataURLToBlob(dataurl) {
  const arr = dataurl.split(',')
  const mime = arr[0].match(/:(.*?);/)[1]
  const bstr = atob(arr[1])
  let n = bstr.length
  const u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new Blob([u8arr], { type: mime })
}

// 获取文件图片的宽高
function getImageWHFromFile(file, fn, err) {
  try {
    // 读取上传之后的文件对象
    const imageReader = new FileReader()
    // 将图片的内存url地址添加到FileReader中
    imageReader.readAsDataURL(file)
    // 当图片完全加载到FileReader对象中后,触发下面的事件
    imageReader.addEventListener('loadend', function(e) {
    // 获取上传到本机内存中的图片的url地址
      const imageSrc = e.target.result
      // 调用计算图片大小的方法
      calculateImageSize(imageSrc).then(function({ image, width, height }) {
      // 得到图片的宽和高
        fn(image, width, height)
      }).catch(e => {
        err()
      })
    })
    // 可以做一些其他状态监听
  } catch (e) {
    err(file)
  }
}

// 根据图片地址获取图片的宽和高
function calculateImageSize(src) {
  return new Promise(function(resolve, reject) {
    const image = new Image()
    image.addEventListener('load', function(e) {
      resolve({
        image: image,
        width: e.target.width,
        height: e.target.height,
      })
    })
    image.addEventListener('error', function(e) {
      reject(Error('获取尺寸失败'))
    })
    // 将图片的url地址添加到图片地址中
    image.setAttribute('crossOrigin', 'Anonymous')
    image.src = src
  })
}

export {
fileCompressToFile,
  urlCompressToBase64,
}
赞(1) 打赏
转载请附上原文出处链接:胖蔡叨叨叨 » 前端图片压缩
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏