图片压缩保证让你看的明明白白

场景

很多时候,都会遇见图片上传的场景。
在上传给服务器之前。
前端为了节省服务器的存储空间。
会对图片进行压缩。
下面我们来一起学习一下图片压缩。
图片压缩的步骤: 
1.选择图片。使用 <input type="file">来实现
2.将选择的图片显示出来。 获取到图片的base64,然后进行赋值
3.对图片进行进行压缩,如果压缩后的图比原图大,继续调用压缩函数,直到比原图小(如小30%)

选择图片并把图片显示出来

我们在读取文件的时候。
需要对图片的类型进行限制。
目前我们支持 'image/png', 'image/jpeg', 'image/gif', 'image/bmp'这些格式。
然后我们把图片转化为base64编码。
<style>
  #originPic{
    display: none;
  }
</style>

<div>
  <input type="file" id="file" value="请选择图片" accept="image/*">
</div>
<div id="originPic">
  <span>原图:</span>
  <img src="" id="originImg">
</div>

<script>
  let fileNode = document.getElementById('file')
  // 当用户选择文件的时候,就会触发readFile方法
  fileNode.addEventListener('change', readFile, false)
  // 装图片的盒子
  let originPicNode = document.getElementById('originPic')
  // 图片DOM节点
  let originImg = document.getElementById('originImg')
  // 常见的图片类型格式
  let canSelectPicTypeArr = ['image/png', 'image/jpeg', 'image/gif', 'image/bmp']
  function readFile(e){
    // 获取用户选择的文件
    let file = e.target.files[0]
    // 检查选择的图片是否符合要求
    if(canSelectPicTypeArr.includes(file.type)){
      // 创建一个文件对象
      let reader = new FileReader()
      reader.readAsDataURL(file)
      // 图片进行读取完成后,将图片的src赋值给img标签
      reader.onload = function(){
        originImg.src = reader.result
        originPicNode.style.display = 'block'
      }
    }else{
      let str = canSelectPicTypeArr.join(',')
      alert('目前只支持图片格式' + str)
    }
  }
</script>

对图片进行压缩

要对图片进行压缩。
我们需要创建canvas标签,img标签。
将选择的图片绘制在canvas上,可以借助 ctx.drawImage函数来实现
最后通过 canvas.toDataURL来实现压缩。
canvas.toDataURL(picType, quality)
picType:表示的是图片类型
quality:表示的是压缩质量,取值范围0-1。默认是 0.92。值越小图压缩越大。
// 我们现在开始压缩图片
function assetImg(originPicInfo){
  // 创建canvas标签
  let canvasEle = document.createElement('canvas')
  // 创建img标签
  let imgEle = document.createElement('img')
  // 创建img标签
  imgEle.src = originPicInfo.originImgSrc
  // canvas的上下文
  let ctx= canvasEle.getContext('2d')
  // 读取图片
  imgEle.onload = ()=>{ 
    // 获取图片的宽高
    const imgWidth = imgEle.width
    const imgHeight = imgEle.height
    // 将图片的宽高设置给canvas
    canvasEle.width = imgWidth
    canvasEle.height = imgHeight
    // 把图片绘制在canvas上
    ctx.drawImage(imgEle, 0,0,imgWidth, imgHeight)
    // 生成压缩图片
    const assetPicBase64 = canvasEle.toDataURL(originPicInfo.imgType, 0.7)
    console.log('压缩后的图片', assetPicBase64)
  }
}

图片编码为base64后为啥还比原图要大?

要回答上面这个问题,就需要去了解base64的编码基本原理。
它的基本原理是:采用64个基本的 ASCII 码字符对数据进行重新编码。
首先他将源码数据拆分字节数组。
以3字节为一组,按顺序排列 24 位的数据。
然后再将24位数据分成4组,即每组6位。
在每组前面加两个00, 凑足一个字节。
这样就把一个 3 字节为一组的数据重新编码成了 4 个字节。
[读到这里,也许你应该猜到为啥图片通过base64编码后比原图要大的原因了]
当要编码的数据的字节数不是3的整倍数,
也就是说在分组时最后一组不 3个字节时。
会在最后一组填充1到2个0 字节。
并在最后编码完成时结尾添加1到2个 "="
我们通过编码规则可以得知,使用 Base64 编码。
原来的 3 个字节编码后将成为 4 个字节。
即字节增加了 33.3%,数据量相应变大。
所以 31KB 的数据通过 Base64 编码后大小大概为 31M*133.3%=42KM左右。
这一部分的参考相关链接:
https://www.ruanyifeng.com/blog/2008/06/base64.html
https://my.oschina.net/u/1422143/blog/702602

如何处理压缩后的图片比原图小?

我们刚刚知道了为啥base64编码后图片的大小比原图要大。
是因为: 3 个字节编码后将成为 4 个字节。所以比原图要大了。
现在我们只需要对图片的大小进行判断。
如果压缩后的图片比大,继续调用压缩函数。
如果比原图小,则停止下来。返回压缩后的图片的base64格式。

实现图片压缩功能

<style>
  #originPic{
    display: none;
  }
  #assetsBox{
    display: none;
  }
</style>

<div>
  <input type="file" id="file" value="请选择图片" accept="image/*">
</div>
<div id="originPic">
  <p>原图:</p>
  <img src="" id="originImg">
</div>
<div id="assetsBox">
  <p>压缩后的图:</p>
  <img src="" id="assetsImg">
</div>
// 图片的类型
let fileType = ''
// 压缩后的图片(base64)
let compressImgSrc = ''
// 压缩质量值
let qualityValue =0.9
imgEle.onload = ()=>{ 
  // ... 其他核心代码...
  // 调用图片压缩
  doPicCompress(canvasEle, originPicInfo.originImgSrc,fileType)
  console.log('压缩',compressImgSrc, qualityValue)
  // 将压缩的图片显示在页面上
  if(assetsBox){
    assetsBox.style.display = 'block'
    assetsImg.src = compressImgSrc
  }
}

// 实现图片压缩
function doPicCompress(canvas, imgSrc, type){
  // 将图片转化为base64
  compressImgSrc = canvas.toDataURL(type, qualityValue)
  // 如果压缩后的图片大于原图,且图片质量还可以继续下调,则继续压缩
  if(compressImgSrc.length>=imgSrc.length && qualityValue>0.1){
    // 这里压缩的核心是下调图片的质量
    qualityValue = qualityValue - 0.1
    // 继续压缩
    doPicCompress(canvas, imgSrc, type)
  }
}


会不会出现多次压缩后,图片仍然比原图要大

有的小伙伴可能会说:
刚刚你说图片编码为base64后会比原图大。
如果是一张比较小的图片(30KB)左右。
会出现经过多次压缩后,图片仍然比原图要大的这种情况吗?
其实这一种是可能出现的。
原因的话是:图片本来就是较小的。编码base64后会比原图大。
借助toDataURL的压缩,并不一定会比原图小。
所以,图片较小,我们这种通过toDataURL进行压缩的方式就不好了。
当然处理前端进行图片压缩之后,后端也可以进行图片压缩。
下面我们通过node插件来简单看下

使用sharp来进行图片压缩

sharp:这个插件主要用于将常见格式的大图像转换为更小,
更适合网页使用的JPEG、PNG、WebP、GIF和AVIF格式的图像。
并且可以调整图像的尺寸。
安装: npm install sharp -S
具体使用的地址:https://www.npmjs.com/package/shap
// 引入依赖
const sharp = require('sharp');
// 传入图片路径为./js.png,进行压缩,然后输出新的yaSuoJS.png保存
sharp('./js.png').png({ quality: 50 }).toFile('yaSuoJS.png', (err, info) => {
  if (err) throw err;
  if(info){
    console.log(info);
  }
 });

全部代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #originPic{
      display: none;
    }
    #assetsBox{
      display: none;
    }
  </style>
</head>
<body>
  <div>
    <input type="file" id="file" value="请选择图片" accept="image/*">
  </div>
  <div id="originPic">
    <p>原图:</p>
    <img src="" id="originImg">
  </div>

  <div id="assetsBox">
    <p>压缩后的图:</p>
    <img src="" id="assetsImg">
  </div>
</body>
</html>
<script>
  let fileNode = document.getElementById('file')
  // 当用户选择文件的时候,就会触发readFile方法
  fileNode.addEventListener('change', readFile, false)
  // 装图片的盒子
  let originPicNode = document.getElementById('originPic')
  // 图片DOM节点
  let originImg = document.getElementById('originImg')
  // 压缩后装图片的DOM节点
  let assetsBox = document.getElementById('assetsBox')
  //  压缩后图片的DOM节点
  let assetsImg = document.getElementById('assetsImg')
  // 常见的图片类型格式
  let canSelectPicTypeArr = ['image/png', 'image/jpeg', 'image/gif', 'image/bmp']
  // 图片的类型
  let fileType = ''
  // 压缩后的图片(base64)
  let compressImgSrc = ''
  // 压缩质量值
  let qualityValue =0.2
  function readFile(e){
    // 获取用户选择的文件
    let file = e.target.files[0]
    fileType = file.type
    // 检查是否选择了符合要求的图片
    if(canSelectPicTypeArr.includes(fileType)){
      // 创建一个文件对象
      let reader = new FileReader()
      reader.readAsDataURL(file)
      reader.onload = function(){
        originImg.src = reader.result
        console.log(reader.result, '压缩后的大小', )
        originPicNode.style.display = 'block'
        // 调用图片压缩这个函数
        assetImg({originImgSrc: originImg.src, quality:0.9, imgType: fileType})
      }
    }else{
      let str = canSelectPicTypeArr.join(',')
      alert('目前只支持图片格式' + str)
    }
  }

  // 我们现在开始压缩图片
  function assetImg(originPicInfo){
    // 创建canvas标签
    let canvasEle = document.createElement('canvas')
    // 创建img标签
    let imgEle = document.createElement('img')
    imgEle.src = originPicInfo.originImgSrc
    let ctx= canvasEle.getContext('2d')
    // 读取图片
    imgEle.onload = ()=>{ 
      // 获取图片的宽高
      const imgWidth = imgEle.width
      const imgHeight = imgEle.height
      // 将图片的宽高设置给canvas
      canvasEle.width = imgWidth
      canvasEle.height = imgHeight
      // 把图片绘制在canvas上
      ctx.drawImage(imgEle, 0,0,imgWidth, imgHeight)
      // 调用图片压缩
      doPicCompress(canvasEle, originPicInfo.originImgSrc,fileType)
      console.log(compressImgSrc, qualityValue, '压缩之后')
      // 将压缩的图片显示在页面上
      if(assetsBox){
        assetsBox.style.display = 'block'
        assetsImg.src = compressImgSrc
      }
    }
  }

  // 实现图片压缩
  function doPicCompress(canvas, imgSrc, type){
    // 将图片转化为base64
    compressImgSrc = canvas.toDataURL(type, qualityValue)
    // 如果压缩后的图片大于原图,且图片质量还可以继续下调,则继续压缩
    if(compressImgSrc.length>=imgSrc.length && qualityValue>0.1){
      // 这里压缩的核心是下调图片的质量
      qualityValue = qualityValue - 0.1
      // 继续压缩
      doPicCompress(canvas, imgSrc, type)
    }
  }
</script>

尾声

如果小伙伴觉得我写的不错的话,
可以给我点个赞吗?感谢了。
不说了,今天又是修改bug的一天
posted @ 2024-08-10 11:25  南风晚来晚相识  阅读(95)  评论(0编辑  收藏  举报