小宋爱睡觉 小宋爱睡觉
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
  • 图片压缩
    • file2DataUrl(file, callback)
    • file2Image(file, callback)
    • image2Canvas(image)
    • canvas2DataUrl(canvas, quality, type)
    • dataUrl2Image(dataUrl, callback)
    • dataUrl2Blob(dataUrl, type)
    • canvas2Blob(canvas, callback, quality, type)
    • blob2DataUrl(blob, callback)
    • blob2Image(blob, callback)
    • upload(url, file, callback)
    • 流程
    • 遇到的问题
      • 原尺寸输出不符合要求
      • png格式的图片进行同格式压缩体积反增大
      • 大尺寸 png 格式图片在一些手机上,压缩后出现“黑屏”现象
  • 图片懒加载
  • 渲染十万条数据
  • 如果让你来设计网络
  • articles
Crucials
2022-02-06

图片压缩

# 图片压缩

最近做的一个项目,为了让小程序的banner图片加载的快一点,遂在小程序的后台管理系统上对图片动点手脚

首先介绍一下几种相关的格式转换

# file2DataUrl(file, callback)

这是将file转为dataurl,使用FileReader读取file,将file转为base64字符串,在result可以拿到内容

function file2DataUrl(file, callback) {
  var reader = new FileReader();
  reader.onload = function () {
    callback(reader.result);
  };
  reader.readAsDataURL(file);
}
1
2
3
4
5
6
7

Data URL 由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身:

data:<mediatype>,<data>

比如一张 png 格式图片,转化为 base64 字符串形式:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAgAElEQVR4XuxdB5g

# file2Image(file, callback)

除了上面的base64可以把图片转成url供img显示之外,还可以直接用URL对象,引用保存在 File 和 Blob 中数据的 URL。使用对象 URL 的好处是可以不必把文件内容读取到 JavaScript 中 而直接使用文件内容

function file2Image(file, callback) {
  var image = new Image();
  var URL = window.webkitURL || window.URL;
  if (URL) {
    var url = URL.createObjectURL(file);
    image.onload = function() {
      callback(image);
      URL.revokeObjectURL(url);
    };
    image.src = url;
  } else {
    file2DataUrl(file, function(dataUrl) {
      image.onload = function() {
        callback(image);
      }
      image.src = dataUrl;
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

值得注意的是

要创建对象 URL,可以使用 window.URL.createObjectURL() 方法,并传入 File 或 Blob 对象。如果不再需要相应数据,最好释放它占用的内容。但只要有代码在引用对象 URL,内存就不会释放。要手工释放内存,可以把对象 URL 传给 URL.revokeObjectURL()。

# image2Canvas(image)

利用 drawImage 方法将 Image 对象绘画在 Canvas 对象上。

drawImage 有三种语法形式:

void ctx.drawImage(image, dx, dy);

void ctx.drawImage(image, dx, dy, dWidth, dHeight);

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

  • image 绘制到上下文的元素;
  • sx 绘制选择框左上角以 Image 为基准 X 轴坐标;
  • sy 绘制选择框左上角以 Image 为基准 Y 轴坐标;
  • sWidth 绘制选择框宽度;
  • sHeight 绘制选择框宽度;
  • dx Image 的左上角在目标 canvas 上 X 轴坐标;
  • dy Image 的左上角在目标 canvas 上 Y 轴坐标;
  • dWidth Image 在目标 canvas 上绘制的宽度;
  • dHeight Image 在目标 canvas 上绘制的高度;

canvas-draw-image

function image2Canvas(image) {
  var canvas = document.createElement('canvas');
  var ctx = canvas.getContext('2d');
  canvas.width = image.naturalWidth;
  canvas.height = image.naturalHeight;
  ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
  return canvas;
}
1
2
3
4
5
6
7
8

# canvas2DataUrl(canvas, quality, type)

HTMLCanvasElement 对象有 toDataURL(type, encoderOptions) 方法,返回一个包含图片展示的 data URL。同时可以指定输出格式和质量

参数分别为:

  • type 图片格式,默认为 image/png。
  • encoderOptions 在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92,其他参数会被忽略。
function canvas2DataUrl(canvas, quality, type) {
  return canvas.toDataURL(type || 'image/jpeg', quality || 0.8);
}
1
2
3

# dataUrl2Image(dataUrl, callback)

图片链接也可以是 base64 字符串,直接赋值给 Image 对象 src 即可。

function dataUrl2Image(dataUrl, callback) {
  var image = new Image();
  image.onload = function() {
    callback(image);
  };
  image.src = dataUrl;
}
1
2
3
4
5
6
7

# dataUrl2Blob(dataUrl, type)

将 data URL 字符串转化为 Blob对象。主要思路是:先将 data URL 数据(data) 部分提取出来,用 atob 对经过 base64 编码的字符串进行解码,再转化成 Unicode 编码,存储在Uint8Array(8位无符号整型数组,每个元素是一个字节) 类型数组,最终转化成 Blob 对象。

function dataUrl2Blob(dataUrl, type) {
  var data = dataUrl.split(',')[1];
  var mimePattern = /^data:(.*?)(;base64)?,/;
  var mime = dataUrl.match(mimePattern)[1];
  var binStr = atob(data);
  var arr = new Uint8Array(len);

  for (var i = 0; i < len; i++) {
    arr[i] = binStr.charCodeAt(i);
  }
  return new Blob([arr], {type: type || mime});
}
1
2
3
4
5
6
7
8
9
10
11
12

# canvas2Blob(canvas, callback, quality, type)

HTMLCanvasElement 有 toBlob(callback, [type\], [encoderOptions])方法创造 Blob 对象,用以展示 canvas 上的图片;这个图片文件可以被缓存或保存到本地,由用户代理端自行决定。第二个参数指定图片格式,如不特别指明,图片的类型默认为 image/png,分辨率为 96dpi。第三个参数用于针对image/jpeg 格式的图片进行输出图片的质量设置。

function canvas2Blob(canvas, callback, quality, type){
  canvas.toBlob(function(blob) {
    callback(blob);
  }, type || 'image/jpeg', quality || 0.8);
}
1
2
3
4
5

为兼容低版本浏览器,作为 toBlob 的 polyfill 方案,可以用上面 data URL 生成 Blob 方法 dataUrl2Blob 作为HTMLCanvasElement 原型方法。

if (!HTMLCanvasElement.prototype.toBlob) {
 Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
  value: function (callback, type, quality) {
    let dataUrl = this.toDataURL(type, quality);
    callback(dataUrl2Blob(dataUrl));
  }
 });
}
1
2
3
4
5
6
7
8

# blob2DataUrl(blob, callback)

将 Blob 对象转化成 data URL 数据,由于 FileReader 的实例 readAsDataURL 方法不仅支持读取文件,还支持读取 Blob 对象数据,这里复用上面 file2DataUrl 方法即可:

function blob2DataUrl(blob, callback) {
  file2DataUrl(blob, callback);
}
1
2
3

# blob2Image(blob, callback)

将 Blob 对象转化成 Image 对象,可通过 URL 对象引用文件,也支持引用 Blob 这样的类文件对象,同样,这里复用上面 file2Image 方法即可:

function blob2Image(blob, callback) {
  file2Image(blob, callback);
}
1
2
3

# upload(url, file, callback)

上传图片(已压缩),可以使用 FormData 传入文件对象,通过 XHR 直接把文件上传到服务器。

function upload(url, file, callback) {
  var xhr = new XMLHttpRequest();
  var fd = new FormData();
  fd.append('file', file);
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
      // 上传成功
      callback && callback(xhr.responseText);
    } else {
      throw new Error(xhr);
    }
  }
  xhr.open('POST', url, true);
  xhr.send(fd);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

也可以使用 FileReader 读取文件内容,转化成二进制上传

function upload(url, file) {
  var reader = new FileReader();
  var xhr = new XMLHttpRequest();

  xhr.open('POST', url, true);
  xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');

  reader.onload = function() {
    xhr.send(reader.result);
  };
  reader.readAsBinaryString(file);
}
1
2
3
4
5
6
7
8
9
10
11
12

现在重头戏

# 流程

我们的流程是 上传的图片 -> Image对象 -> 写入Canvas画布 -> 利用canvas.toDataURL/toBlob 将 canvas 压缩并导出为 base64 或 Blob -> 将 base64 或 Blob 转化为 File

因为这是folk别人的项目进行学习的,这是代码 (opens new window)

# 遇到的问题

# 原尺寸输出不符合要求

为了避免压缩图片变形,一般采用等比缩放,首先要计算出原始图片宽高比 aspectRatio

用户设置的输出照片的高乘以 aspectRatio,得出等比缩放后的宽

若比用户设置宽的小,则用户设置的高为为基准缩放,否则以宽为基准缩放。

通过drawImage进行改造一下

function image2Canvas(image, destWidth, destHeight) {
  var canvas = document.createElement('canvas');
  var ctx = canvas.getContext('2d');
  canvas.width = destWidth || image.naturalWidth;
  canvas.height = destHeight || image.naturalHeight;
  ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
  return canvas;
}
1
2
3
4
5
6
7
8

# png格式的图片进行同格式压缩体积反增大

原因:

关键的压缩API均未支持png格式

  • toBlob(callback, [type], [encoderOptions]) 参数 encoderOptions 用于针对image/jpeg 格式的图片进行输出图片的质量设置;
  • toDataURL(type, encoderOptions 参数encoderOptions 在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。

解决方法:

我们可以设置一个阈值,如果 png 图片的质量小于这个值,就还是压缩输出 png 格式,这样最差的输出结果不至于质量太大

在此基础上,如果压缩后图片大小 “不减反增”,我们就兜底处理输出源图片给用户。当图片质量大于某个值时,我们压缩成 jpeg 格式

// `png` 格式图片大小超过 `convertSize`, 转化成 `jpeg` 格式
if (file.size > options.convertSize && options.mimeType === 'image/png') {
  options.mimeType = 'image/jpeg';
}
// 省略一些代码
// ...
// 用户期待的输出宽高没有大于源图片的宽高情况下,输出文件大小大于源文件,返回源文件
if (result.size > file.size && !(options.width > naturalWidth || options.height > naturalHeight)) {
  result = file;
}
1
2
3
4
5
6
7
8
9
10

# 大尺寸 png 格式图片在一些手机上,压缩后出现“黑屏”现象

浏览器 最大宽高 最大面积
Chrome 32,767 pixels 268,435,456 pixels(e.g.16,384 x 16,384)
Firefox 32,767 pixels 472,907,776 pixels(e.g.22,528 x 20,992)
IE 8,192 pixels N/A
IE Mobile 4,096 pixels N/A

如果图片尺寸过大,在创建同尺寸画布,再画上图片,就会出现异常情况,即生成的画布没有图片像素,而画布本身默认给的背景色为黑色,这样就导致图片“黑屏”情况。

这里可以通过控制输出图片最大宽高防止生成画布越界,并且用透明色覆盖默认黑色背景解决解决“黑屏”问题:

上次更新: 2022/02/12, 15:12:22
图片懒加载

图片懒加载→

Copyright © 2021-2025 粤ICP备2021165371号
  • 跟随系统
  • 浅色模式
  • 深色模式