首页 > 教程
JS压缩图片并保留图片元信息
- 2025-04-07
- 971 ℃
JS实现图片压缩比较简单,但是图片经过压缩后,压缩后的图片的元信息(拍摄时间、设备、地点)等会丢失掉,如果在特殊场景中需要使用这些元信息的话,就会出现问题了,因此需要将未压缩前的图片元信息填充至压缩后的图片中,以下是实现代码
// 封装一个获取变量的数据类型函数
const getType = (data: unknown): string => {
const toStingResult = Object.prototype.toString.call(data);
const type = toStingResult.replace(/^\[object (\w+)\]$/, "$1");
return type.toLowerCase();
};
// 封装一个将 Base64 的字符串转换成 Blob 流的函数
const dataURLtoBlob = (dataURL: string): Blob | null => {
const dataType = getType(dataURL);
if (dataType !== "string") return null;
const arr = dataURL.split(",");
if (!arr[0] || !arr[1]) return null;
const code = window.atob(arr[1]);
const mimeExpRes = arr[0].match(/:(.*?);/);
if (!mimeExpRes) return null;
let len = code.length;
const mime = mimeExpRes[1];
if (!mime) return null;
const ia = new Uint8Array(len);
while (len--) ia[len] = code.charCodeAt(len);
return new Blob([ia], { type: mime });
};
// 利用规律编码格式把里面的标记以及值等分割开来,传原图片的 ArrayBuffer 进来
const getSegments = (arrayBuffer: ArrayBuffer): number[][] => {
if (!arrayBuffer.byteLength) return [];
let head = 0;
let length, endPoint, seg;
const segments = [];
const arr = [].slice.call(new Uint8Array(arrayBuffer), 0);
while (1) {
if (arr[head] === 0xff && arr[head + 1] === 0xda) break;
if (arr[head] === 0xff && arr[head + 1] === 0xd8) {
head += 2;
} else {
length = arr[head + 2] * 256 + arr[head + 3];
endPoint = head + length + 2;
seg = arr.slice(head, endPoint);
head = endPoint;
segments.push(seg);
}
if (head > arr.length) break;
}
return segments;
};
// 传入上面 getSegments 的返回值,取出EXIF图片元信息
const getEXIF = (segments: number[][]): Array<number> => {
if (!segments.length) return [];
let seg: Array<number> = [];
for (let i = 0; i < segments.length; i++) {
const item = segments[i];
if (item[0] === 0xff && item[1] === 0xe1) {
seg = seg.concat(item);
}
}
return seg;
};
// 将 getEXIF 获取的元信息,插入到压缩后的图片的 Blob 中,传 压缩图片后的 Blob 流
const insertEXIF = (blob: Blob, exif: number[]): Promise<Blob> => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = () => {
const arr = [].slice.call(new Uint8Array(fileReader.result as ArrayBuffer), 0);
if (arr[2] !== 0xff || arr[3] !== 0xe0) {
return reject(new Error("Couldn't find APP0 marker from blob data"));
}
const length = arr[4] * 256 + arr[5];
const newImage = [0xff, 0xd8].concat(exif, arr.slice(4 + length));
const uint8Array = new Uint8Array(newImage);
const newBlob = new Blob([uint8Array], { type: "image/jpeg" });
resolve(newBlob);
};
fileReader.readAsArrayBuffer(blob);
});
};
// 压缩图片逻辑
const compressImage = (file: File, quality: number): Promise<Blob | null> => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = () => {
const img = new Image();
img.src = fileReader.result as string;
img.onload = () => {
const { width, height } = img;
const canvas = window.document.createElement("canvas");
const ctx = <CanvasRenderingContext2D>canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
const fileData = canvas.toDataURL("image/jpeg", quality);
const fileBlob = dataURLtoBlob(fileData);
resolve(fileBlob);
};
img.onerror = (err) => reject(err);
};
fileReader.onerror = (err) => reject(err);
fileReader.readAsDataURL(file);
});
};
/**
* @description: 完整的压缩图片,最终对外暴露的函数
* @param {File} file
* @param {number} quality 0 - 1
* @return {Promise<File>}
*/
export default (file: File, quality = 0.5): Promise<File> => {
return new Promise((resolve, reject) => {
const dataType = getType(file);
if (dataType !== "file") return reject(new Error(`Expected parameter type is file, You passed in ${dataType}`));
if (file.type.indexOf("image") === -1) return resolve(file);
// 压缩图片
compressImage(file, quality)
.then((compressdBlob) => {
if (!compressdBlob) return resolve(file);
const fileReader = new FileReader();
fileReader.onload = () => {
// 获取图片元信息
const segments = getSegments(fileReader.result as ArrayBuffer);
const exif = getEXIF(segments);
// 没有元数据的时候, 直接抛出压缩后的图片
if (!exif.length) return resolve(new File([compressdBlob], file.name, { type: file.type, lastModified: file.lastModified }));
// 有元数据的时候, 将元信息合并到压缩图片里
insertEXIF(compressdBlob, exif)
.then((newBlob) => resolve(new File([newBlob], file.name, { type: file.type, lastModified: file.lastModified })))
.catch(() => resolve(file));
};
fileReader.onerror = () => resolve(file);
fileReader.readAsArrayBuffer(file);
})
.catch(() => resolve(file));
});
};上一篇:class中函数的this指向
下一篇:原型继承和 Class 继承
相关内容
app申请支付宝移动支付
支持上百个网站的漫画下...
下载B站视频,知道这些方...
Win7和Win10系统隐藏的上...
不懂人性何谈产品
PHP基本简单实用函数
免费下载PPT模板的网站来了
人工智能:探索未知领域...
-
Nginx域名跳转 www跳转和不带www
2024-04-23 1628
-
固态硬盘必做的SSD优化,大幅提升读取速度
2025-06-25 1047
-
将浏览器窗口变成简单的文本编辑器
2021-10-27 561
-
fastadmin基于ZipArchive生成压缩文件下载到本地
2021-05-25 2028
-
php提高性能的几个小技巧
2021-04-13 700
-
边充电边玩手机会伤电池吗?
2025-07-03 1367
-
如何学习麻省理工学院课程
2025-03-03 1183
-
php结合redis实现高并发下的抢购、秒杀功能
2021-07-27 836
-
php 微信公众号接入支付宝支付
2021-06-18 966
-
原型继承和 Class 继承
2025-04-07 799
文章评论 (0)
- 这篇文章还没有收到评论,赶紧来抢沙发吧~


进入有缘空间
点击分享文章