XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • leaflet改变坐标原点
  • 补间动画gsap与tween
  • 文字转语音mp3文件
  • JavaScript引入
  • JavaScript高级程序设计阅读笔记
  • Javascript函数参数按值传递传递
  • JS防抖和节流
  • 手写JS常用方法
  • 手写Promise
  • JS Event Loop
  • 重排和重绘
  • em、px、rem、vh、vw 区别
  • css也需要性能优化
  • 图解 js 原型链
  • js函数参数按值传递
  • BOM浏览器对象模型
  • DOM
  • 事件
  • js对象数组sort按需排序
  • 文件上传功能技术选型和前后端实现
  • 前端图片处理
  • 让你的JavaScript代码简单又高效
  • BEM:class命名规范
  • 前端规范-CSS属性那么多(杂),怎么排序
  • TypeScript Tips
  • jsx
  • canvas基础
  • 前端日志
  • 浏览器存储
  • CSS世界阅读笔记
  • CSS揭秘阅读笔记
  • js变量命名常用规范词
  • 你不知道的JavaScript阅读笔记
  • js对象的底层数据结构
  • js判断数据类型
  • JS浮点数精度问题和解决办法
  • js作用域
  • js堆栈溢出和内存泄漏
  • 浏览器是如何渲染页面的
  • 疑难杂症和踩坑问题合集
  • 免费在线API收集
  • 原生JS实现Ajax请求
  • cookie、session、localStorage、sessionStorage的区别
  • Sass与Less
  • arrayBuffer、blob、file对象
  • TypeScript基础
  • 前端
XingYun
2022-11-28
目录

cavans实现图片压缩

# 核心原理

  1. canvas 的toDataURL 能压缩格式为 image/jpeg 或者 image/webp 的图片

  2. canvas 绘制图片 ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

  3. 二分法求最接近目标大小的值

  4. 将压缩好的 dataURL(base64)转为 File

  5. 根据 createObjectURL 和 新建a标签, 手动触发 click 事件下载压缩好的文件

# 上代码

canvas 压缩 根据画质压缩图片

/**
 *
 * @param {file: File|Blob, maxSize: number, scale: 0-1}
 * @return {name: string, blob: Blob, dataURL: base64}
 */
export function compress(file: File, maxSize = 500, scale = 0.5) {
  maxSize *= 1024 // maxSize 单位为kb,这里转为b
  const { size: sourceSize, name } = file
  const fileType = name.replace(/^.*\.(\w*)$/, '$1').toLowerCase()
  if (!['jpg', 'jpeg', 'png', 'webp'].includes(fileType))
    return Promise.reject('文件格式不支持!')
  const blobUrl = URL.createObjectURL(file)
  const tmpImg = new Image()
  const canvas = document.createElement('canvas')
  const context = canvas.getContext('2d')
  return new Promise((resolve, reject) => {
    tmpImg.onload = async function (e: any) {
      const img = e.target
      // URL.revokeObjectURL(file) // 清理图片缓存
      let { width, height } = img
      width *= scale
      height *= scale
      //如果图片大于四百万像素,重写缩放比例比并将大小压至400万以下
      if ((width * height) / 4e6 > 1) {
        scale = 4e6 / (width * height)
        width *= scale
        height *= scale
      }
      // 设置画布大小
      canvas.width = width
      canvas.height = height
      context!.fillStyle = '#fff' // 填充底色,处理png背景为黑色的问题
      context!.fillRect(0, 0, width, height)
      // canvas 绘图,这一步将图片按比例缩放,并处理瓦片问题
      drawImage(context, img, scale)
      compressCalculation(canvas, maxSize).then((res: any) => {
        console.log(
          '压缩完成,压缩前:',
          sourceSize,
          ' => 压缩后:',
          res.blob.size
        )
        let file = dataURLtoFile(res.dataURL, name)
        resolve(file)
      })
    }
    tmpImg.src = blobUrl
  })
}

/**
 * @description 将图片绘制到画布上,并根据缩放比例进行缩放,若图片大于2000000像素则进行瓦片处理
 * @param {context: canvasContext, img: imgObj, scale: 0-1}
 */
function drawImage(context: any, img: any, scale: number) {
  const { width: sourceWidth, height: sourceHeight } = img
  const pSize = sourceWidth * sourceHeight
  const targetWidth = sourceWidth * scale,
    targetHeight = sourceHeight * scale
  const defSize = 1000 // 设置瓦片默认宽高
  const cols = pSize > 2e6 ? ~~(targetWidth / defSize) + 1 : 1 // 列数
  const rows = pSize > 2e6 ? ~~(targetHeight / defSize) + 1 : 1 // 行数
  // 瓦片绘图
  for (let i = 0; i < rows; i++) {
    // 遍历列
    for (let j = 0; j < cols; j++) {
      // 遍历行
      const canvasWidth = j === cols - 1 ? targetWidth % defSize : defSize
      const canvasHeight = i === rows - 1 ? targetHeight % defSize : defSize
      const canvasLeft = j * defSize
      const canvasTop = i * defSize
      const imgWidth = canvasWidth / scale
      const imgHeight = canvasHeight / scale
      const imgLeft = canvasLeft / scale
      const imgTop = canvasTop / scale
      context.drawImage(
        img,
        imgLeft,
        imgTop,
        imgWidth,
        imgHeight,
        canvasLeft,
        canvasTop,
        canvasWidth,
        canvasHeight
      )
    }
  }
}

/**
 * @param {canvas: Canvas, maxSize: number}
 */
function compressCalculation(canvas: any, maxSize: number) {
  let count = 1 // 记录循环次数,防止死循环
  let quality = 1 // 图片质量
  let maxQuality = 1,
    minQuality = 0 // 定义图片质量范围
  return new Promise(function (resolve, reject) {
    async function fn() {
      quality = (maxQuality + minQuality) / 2
      // 获取压缩后结果
      let res = await compressBlob(canvas, quality)
      const { blob, dataURL }: any = res
      const { size } = blob
      console.log(`第${count}次压缩:`, quality, size)
      // 二分法获取最接近值
      function partition() {
        count++
        if (size > maxSize) {
          maxQuality = quality
        } else {
          minQuality = quality
        }
        fn()
      }
      if (count < 10 && size !== maxSize) {
        // 默认循环不大于20次,认为10次内的结果已足够接近
        partition()
      } else if (size > maxSize) {
        // 若限制次数之后size > maxSize,则继续执行,至结果小于maxSize
        partition()
      } else {
        resolve(res)
      }
    }
    fn()
  })
}

/**
 * @description 该步骤为关键步骤,通过canvas的toDataURL和toBlob对图片进行压缩
 * @param {canvas: Canvas, quality: 0-1 图片质量}
 */
function compressBlob(canvas: any, quality: number) {
  const type = 'image/jpeg' // 只有jpeg格式图片支持图片质量压缩
  const dataURL = canvas.toDataURL(type, quality)
  return new Promise((resolve, reject) => {
    canvas.toBlob(
      function (blob: Blob) {
        resolve({
          dataURL,
          blob
        })
      },
      type,
      quality
    )
  })
}

/**
 * 下载File 或者 base64类型文件
 * @param data  File类型 或者 base64类型
 * @param fileType  fileType 文件类型
 */
export function downloadFile(data: File | string, fileType: string) {
  const fileStream = new Blob([data], { type: fileType })
  let url = window.URL.createObjectURL(fileStream)
  let link = document.createElement('a')
  link.style.display = 'none'
  link.href = url
  link.setAttribute('download', '压缩后')
  document.body.appendChild(link)
  link.click()
  link.remove()
}

/**
 * base64 转 File
 */
export function dataURLtoFile(
  base64Str: string,
  fileName: string = 'test'
): File {
  let arr: any = base64Str.split(',')
  let mime = arr[0].match(/:(.*?);/)[1]
  let bstr = atob(arr[1])
  let n = bstr.length
  let u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new File([u8arr], fileName, { type: mime })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#Javascript
上次更新: 2023/04/05, 09:41:10
最近更新
01
JavaScript-test
07-20
02
二维码的原理
07-20
03
利用ChatGPT优化代码
07-20
更多文章>
Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式