前面是讲解了lrz基础用法,从6开始讲解源码,使用过lrz的可以直接从6开始看,中间也掺杂了一下我自己开发过程中的踩坑和经验分享,欢迎讨论!
lrz
(LocalResizeIMG)是一个前端图片压缩库,主要用于在浏览器中压缩图片并上传。以下是其主要特点和功能:
图片压缩:通过调整图片质量和尺寸来减小文件大小。
保持宽高比:压缩时可保持图片原始宽高比。
多格式支持:支持常见图片格式如 JPEG、PNG、GIF 等。
本地处理:图片在本地压缩,不依赖服务器。
预览功能:压缩后可生成预览图。
lrz(file, [options]);
file
通过 input:file
得到的文件,或者直接传入图片路径
[options]
这个参数允许忽略
width {Number}
图片最大不超过的宽度,默认为原图宽度,高度不设时会适应宽度。height {Number}
同上quality {Number}
图片压缩质量,取值 0 - 1,默认为0.7fieldName {String}
后端接收的字段名,默认:file返回值是一个promise
对象
then(rst)
rst.formData
后端可处理的数据rst.file
压缩后的file对象(默认已经丢在rst.formData有一份了),需要注意的是如果压缩率太低的话,这个会是原始的file对象rst.fileLen
生成后的图片的大小,后端可以通过此值来校验是否传输完整rst.base64
生成后的图片base64,后端可以处理此字符串为图片,也直接用于img.src = base64rst.base64Len
生成后的base64的大小,后端可以通过此值来校验是否传输完整 (如果采用base64上传方式)rst.origin
也就是原始的file对象,里面存了一些原始文件的信息,例如大小,日期等。catch(err)
always()
document.querySelector('#file').addEventListener('change', function () {
lrz(this.files[0])
.then(function (rst) {
// 处理成功会执行
console.log(rst);
})
.catch(function (err) {
// 处理失败会执行
})
.always(function () {
// 不管是成功失败,都会执行
});
});
lrz('./xxx/xx/x.png')
.then(function (rst) {
// 处理成功会执行
})
.catch(function (err){
// 处理失败会执行
})
.always(function () {
// 不管是成功失败,都会执行
});
也就是标准的文件上传方式,而且这种方式传输量也会小一些
前端发送rst.formData
给后端
后端接收 rst.file(可配置)
字段,接着按常规处理
发送base64
字符串,接着处理字符串为图片即可。
具体请使用关键字base64 转 image 开发语言
进行google、baidu。
建议前端发送一下rst.fileLen
字段,后端进行校验来防止文件没有完整传送。
document.querySelector('input').addEventListener('change', function () {
// this.files[0] 是用户选择的文件
lrz(this.files[0], {width: 1024})
.then(function (rst) {
// 把处理的好的图片给用户看看呗
var img = new Image();
img.src = rst.base64;
img.onload = function () {
document.body.appendChild(img);
};
return rst;
})
.then(function (rst) {
// 这里该上传给后端啦
/* ==================================================== */
// 原生ajax上传代码,所以看起来特别多 ╮(╯_╰)╭,但绝对能用
// 其他框架,例如jQuery处理formData略有不同,请自行google,baidu。
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:5000/');
xhr.onload = function () {
if (xhr.status === 200) {
// 上传成功
} else {
// 处理其他情况
}
};
xhr.onerror = function () {
// 处理错误
};
xhr.upload.onprogress = function (e) {
// 上传进度
var percentComplete = ((e.loaded / e.total) || 0) * 100;
};
// 添加参数
rst.formData.append('fileLen', rst.fileLen);
rst.formData.append('xxx', '我是其他参数');
// 触发上传
xhr.send(rst.formData);
/* ==================================================== */
return rst;
})
.catch(function (err) {
// 万一出错了,这里可以捕捉到错误信息
// 而且以上的then都不会执行
alert(err);
})
.always(function () {
// 不管是成功失败,这里都会执行
});
});
__webpack_public_path__
和 window.URL
__webpack_public_path__ = getJsDir('lrz') + '/';
window.URL = window.URL || window.webkitURL;
__webpack_public_path__
:设置 Webpack 的公共路径,用于动态加载资源。getJsDir('lrz')
获取当前脚本的路径,并拼接 /
。
window.URL
:兼容性处理,确保 window.URL
可用。如果浏览器不支持 window.URL
,则回退到 window.webkitURL
。
var Promise = require('Promise'),
BlobFormDataShim = require('Blob.FormData.shim'),
exif = require('exif');
Promise
:引入 Promise 库,用于处理异步操作。
BlobFormDataShim
:引入 Blob 和 FormData 的兼容性处理库,用于处理低版本浏览器的兼容性问题。
exif
:引入 EXIF 库,用于读取图片的元数据(如方向信息)。
var UA = (function (userAgent) {
var ISOldIOS = /OS (.*) like Mac OS X/g.exec(userAgent),
isOldAndroid = /Android (\d.*?);/g.exec(userAgent) || /Android\/(\d.*?) /g.exec(userAgent);
var IOS_VERSION = ISOldIOS ? +ISOldIOS.pop().replace(/_/g, '.') : 0;
return {
oldIOS: ISOldIOS ? IOS_VERSION < 8 : false, // 判断是否是 iOS 8 以下
newIOS: ISOldIOS ? IOS_VERSION >= 13.4 : false, // 判断是否是 iOS 13.4 及以上
oldAndroid: isOldAndroid ? +isOldAndroid.pop().substr(0, 3) < 4.5 : false, // 判断是否是 Android 4.5 以下
iOS: /\(i[^;]+;( U;)? CPU.+Mac OS X/.test(userAgent), // 判断是否是 iOS 设备
android: /Android/g.test(userAgent), // 判断是否是 Android 设备
mQQBrowser: /MQQBrowser/g.test(userAgent) // 判断是否是 QQ 浏览器
};
})(navigator.userAgent);
功能:通过 navigator.userAgent
检测当前设备的类型和版本。
返回值:返回一个对象,包含设备的相关信息(如是否是旧版 iOS、是否是 Android 设备等)。
Lrz
构造函数function Lrz(file, opts) {
var that = this;
if (!file) throw new Error('没有收到图片,可能的解决方案:https://github.com/think2011/localResizeIMG/issues/7');
opts = opts || {};
that.defaults = {
width: null, // 压缩后的宽度
height: null, // 压缩后的高度
fieldName: 'file', // 表单字段名
ingnoreOrientation: UA.iOS ? UA.newIOS : true, // 是否忽略方向信息
quality: 0.7 // 压缩质量(0 到 1 之间)
};
that.file = file;
for (var p in opts) {
if (!opts.hasOwnProperty(p)) continue;
that.defaults[p] = opts[p]; // 合并用户传入的配置
}
return this.init(); // 初始化
}
功能:初始化 Lrz
实例,合并默认配置和用户传入的配置。
参数:
file
:需要压缩的图片文件(可以是 File 对象、Blob 对象或 base64 字符串)。
opts
:用户传入的配置项。
init
方法Lrz.prototype.init = function () {
// 1. 保存当前上下文到 `that`,方便在 Promise 和其他函数中访问
var that = this,
// 2. 获取传入的文件对象或 base64 字符串
file = that.file,
// 3. 判断文件是否是字符串类型(base64 或 URL)
fileIsString = typeof file === 'string',
// 4. 判断文件是否是 base64 格式
fileIsBase64 = /^data:/.test(file),
// 5. 创建一个新的 Image 对象,用于加载图片
img = new Image(),
// 6. 创建一个新的 canvas 对象,用于绘制和压缩图片
canvas = document.createElement('canvas'),
// 7. 如果文件是字符串(base64 或 URL),直接使用;否则,将文件对象转换为 Blob URL
blob = fileIsString ? file : URL.createObjectURL(file);
// 8. 将 Image 对象、Blob URL 和 canvas 对象保存到实例中,方便后续使用
that.img = img;
that.blob = blob;
that.canvas = canvas;
// 9. 设置文件名:
// - 如果文件是 base64,默认文件名为 'base64.jpg'
// - 如果文件是 URL,取 URL 的最后一部分作为文件名
// - 如果文件是 File 对象,直接使用 File 对象的 name 属性
if (fileIsString) {
that.fileName = fileIsBase64 ? 'base64.jpg' : file.split('/').pop();
} else {
that.fileName = file.name;
}
// 10. 检查浏览器是否支持 canvas,如果不支持,抛出错误
if (!document.createElement('canvas').getContext) {
throw new Error('浏览器不支持canvas');
}
// 11. 返回一个 Promise 对象,用于处理异步操作
return new Promise(function (resolve, reject) {
// 12. 监听图片加载失败事件
img.onerror = function () {
// 13. 如果图片加载失败,抛出错误并 reject Promise
var err = new Error('加载图片文件失败');
reject(err);
throw err;
};
// 14. 监听图片加载成功事件
img.onload = function () {
// 15. 图片加载成功后,调用 `_getBase64` 方法生成 base64
that._getBase64()
.then(function (base64) {
// 16. 检查生成的 base64 是否有效(长度是否大于 10)
if (base64.length < 10) {
// 17. 如果 base64 无效,抛出错误并 reject Promise
var err = new Error('生成base64失败');
reject(err);
throw err;
}
// 18. 返回有效的 base64
return base64;
})
.then(function (base64) {
// 19. 初始化 FormData 对象
var formData = null;
// 20. 判断是否需要使用原始文件:
// - 如果压缩后的 base64 比原始文件还大,则使用原始文件
if (typeof that.file === 'object' && base64.length > that.file.size) {
// 21. 使用原始文件,并创建原生的 FormData 对象
formData = new FormData();
file = that.file;
} else {
// 22. 使用压缩后的文件,并创建兼容性的 FormData 对象
formData = new BlobFormDataShim.FormData();
file = dataURItoBlob(base64);
}
// 23. 将文件添加到 FormData 中,字段名为配置中的 `fieldName`,文件名为处理后的文件名(后缀改为 .jpg)
formData.append(that.defaults.fieldName, file, that.fileName.replace(/\..+/g, '.jpg'));
// 24. 返回压缩结果,包括 FormData、文件大小、base64、原始文件等信息
resolve({
formData: formData, // 压缩后的 FormData
fileLen: +file.size, // 压缩后文件的大小
base64: base64, // 压缩后的 base64
base64Len: base64.length, // base64 的长度
origin: that.file, // 原始文件
file: file // 压缩后的文件
});
// 25. 释放内存:
// - 遍历实例属性,将其置为 null
// - 释放 Blob URL
for (var p in that) {
if (!that.hasOwnProperty(p)) continue;
that[p] = null;
}
URL.revokeObjectURL(that.blob);
});
};
// 26. 如果文件不是 base64,设置图片的跨域属性为匿名(避免跨域问题)
!fileIsBase64 && (img.crossOrigin = "*");
// 27. 开始加载图片,将 Blob URL 或 base64 赋值给 img.src
img.src = blob;
});
};
功能:初始化图片加载和压缩流程。
步骤:
创建 Image
对象和 canvas
对象。
加载图片,并在图片加载完成后调用 _getBase64
方法生成 base64。
根据压缩结果生成 FormData
对象。
返回压缩后的结果(包括 base64、FormData 等)
_getBase64
方法Lrz.prototype._getBase64 = function () {
// 1. 保存当前上下文到 `that`,方便在 Promise 和其他函数中访问
var that = this,
// 2. 获取之前初始化的 Image 对象
img = that.img,
// 3. 获取传入的文件对象或 base64 字符串
file = that.file,
// 4. 获取之前初始化的 canvas 对象
canvas = that.canvas;
// 5. 返回一个 Promise 对象,用于处理异步操作
return new Promise(function (resolve) {
// 6. 使用 try-catch 捕获可能的异常
try {
// 7. 读取图片的 EXIF 信息:
// - 如果文件是 File 对象,直接读取文件的 EXIF 信息
// - 如果文件是 base64 或 URL,读取 Image 对象的 EXIF 信息
exif.getData(typeof file === 'object' ? file : img, function () {
// 8. 获取图片的方向信息(Orientation):
// - 如果配置中设置了 `ingnoreOrientation` 为 true,则忽略方向信息,设置为 0
// - 否则,从 EXIF 中读取方向信息
that.orientation = that.defaults.ingnoreOrientation ? 0 : exif.getTag(this, "Orientation");
// 9. 调用 `_getResize` 方法,计算压缩后的图片尺寸
that.resize = that._getResize();
// 10. 获取 canvas 的 2D 上下文,用于绘制图片
that.ctx = canvas.getContext('2d');
// 11. 设置 canvas 的宽度和高度为压缩后的尺寸
canvas.width = that.resize.width;
canvas.height = that.resize.height;
// 12. 设置 canvas 的背景色为白色:
// - 因为 JPEG 格式不支持透明背景,默认背景为黑色,设置为白色可以避免黑色背景
that.ctx.fillStyle = '#fff';
that.ctx.fillRect(0, 0, canvas.width, canvas.height);
// 13. 根据设备类型选择不同的处理方式:
// - 如果是旧版 iOS 设备,调用 `_createBase64ForOldIOS` 方法
// - 否则,调用 `_createBase64` 方法
if (UA.oldIOS) {
that._createBase64ForOldIOS().then(resolve);
} else {
that._createBase64().then(resolve);
}
});
} catch (err) {
// 14. 如果发生异常,抛出错误
throw new Error(err);
}
});
};
功能:获取图片的 base64 数据。
步骤:
读取图片的 EXIF 信息(如方向信息)。
根据方向信息和配置调整图片尺寸。
调用 _createBase64ForOldIOS
或 _createBase64
生成 base64。
_createBase64ForOldIOS
方法Lrz.prototype._createBase64ForOldIOS = function () {
// 1. 保存当前上下文到 `that`,方便在 Promise 和其他函数中访问
var that = this,
// 2. 获取之前初始化的 Image 对象
img = that.img,
// 3. 获取之前初始化的 canvas 对象
canvas = that.canvas,
// 4. 获取默认配置
defaults = that.defaults,
// 5. 获取图片的方向信息(Orientation)
orientation = that.orientation;
// 6. 返回一个 Promise 对象,用于处理异步操作
return new Promise(function (resolve) {
// 7. 动态加载 `megapix-image` 库,用于处理旧版 iOS 设备的图片渲染
require(['megapix-image'], function (MegaPixImage) {
// 8. 使用 `MegaPixImage` 创建一个新的图片对象
var mpImg = new MegaPixImage(img);
// 9. 判断方向信息是否需要旋转:
// - 如果方向信息是 5、6、7、8,表示图片需要旋转 90 度或 270 度
// - 此时需要交换 canvas 的宽度和高度
if ("5678".indexOf(orientation) > -1) {
// 10. 调用 `mpImg.render` 方法,将图片渲染到 canvas 上:
// - 设置宽度为 canvas 的高度
// - 设置高度为 canvas 的宽度
// - 传递方向信息,确保图片正确旋转
mpImg.render(canvas, {
width: canvas.height, // 交换宽度和高度
height: canvas.width, // 交换宽度和高度
orientation: orientation // 传递方向信息
});
} else {
// 11. 如果方向信息不需要旋转,直接按原尺寸渲染图片
mpImg.render(canvas, {
width: canvas.width, // 使用原宽度
height: canvas.height, // 使用原高度
orientation: orientation // 传递方向信息
});
}
// 12. 将 canvas 中的图片转换为 base64 格式:
// - 使用 `canvas.toDataURL` 方法,生成 JPEG 格式的 base64
// - 设置图片质量为配置中的 `quality`
resolve(canvas.toDataURL('image/jpeg', defaults.quality));
});
});
};
功能:针对旧版 iOS 设备生成 base64。
实现:使用 megapix-image
库处理图片,并根据方向信息调整图片。
_createBase64
方法Lrz.prototype._createBase64 = function () {
// 1. 保存当前上下文到 `that`,方便在 Promise 和其他函数中访问
var that = this,
// 2. 获取之前计算的压缩后的图片尺寸
resize = that.resize,
// 3. 获取之前初始化的 Image 对象
img = that.img,
// 4. 获取之前初始化的 canvas 对象
canvas = that.canvas,
// 5. 获取 canvas 的 2D 上下文
ctx = that.ctx,
// 6. 获取默认配置
defaults = that.defaults,
// 7. 获取图片的方向信息(Orientation)
orientation = that.orientation;
// 8. 根据方向信息调整图片:
// - 方向信息(Orientation)来自 EXIF 数据,用于处理图片的旋转和翻转
switch (orientation) {
case 3:
// 9. 方向为 3:图片需要旋转 180 度
ctx.rotate(180 * Math.PI / 180); // 旋转 180 度
ctx.drawImage(img, -resize.width, -resize.height, resize.width, resize.height); // 绘制图片
break;
case 6:
// 10. 方向为 6:图片需要旋转 90 度
ctx.rotate(90 * Math.PI / 180); // 旋转 90 度
ctx.drawImage(img, 0, -resize.width, resize.height, resize.width); // 绘制图片
break;
case 8:
// 11. 方向为 8:图片需要旋转 270 度
ctx.rotate(270 * Math.PI / 180); // 旋转 270 度
ctx.drawImage(img, -resize.height, 0, resize.height, resize.width); // 绘制图片
break;
case 2:
// 12. 方向为 2:图片需要水平翻转
ctx.translate(resize.width, 0); // 平移画布
ctx.scale(-1, 1); // 水平翻转
ctx.drawImage(img, 0, 0, resize.width, resize.height); // 绘制图片
break;
case 4:
// 13. 方向为 4:图片需要垂直翻转
ctx.translate(resize.width, 0); // 平移画布
ctx.scale(-1, 1); // 水平翻转
ctx.rotate(180 * Math.PI / 180); // 旋转 180 度
ctx.drawImage(img, -resize.width, -resize.height, resize.width, resize.height); // 绘制图片
break;
case 5:
// 14. 方向为 5:图片需要水平翻转并旋转 90 度
ctx.translate(resize.width, 0); // 平移画布
ctx.scale(-1, 1); // 水平翻转
ctx.rotate(90 * Math.PI / 180); // 旋转 90 度
ctx.drawImage(img, 0, -resize.width, resize.height, resize.width); // 绘制图片
break;
case 7:
// 15. 方向为 7:图片需要水平翻转并旋转 270 度
ctx.translate(resize.width, 0); // 平移画布
ctx.scale(-1, 1); // 水平翻转
ctx.rotate(270 * Math.PI / 180); // 旋转 270 度
ctx.drawImage(img, -resize.height, 0, resize.height, resize.width); // 绘制图片
break;
default:
// 16. 默认情况:不需要旋转或翻转,直接绘制图片
ctx.drawImage(img, 0, 0, resize.width, resize.height);
}
// 17. 返回一个 Promise 对象,用于处理异步操作
return new Promise(function (resolve) {
// 18. 判断是否需要使用兼容性处理:
// - 如果是旧版 Android 设备、QQ 浏览器或无法获取用户代理信息,使用 `JPEGEncoder` 生成 base64
if (UA.oldAndroid || UA.mQQBrowser || !navigator.userAgent) {
// 19. 动态加载 `jpeg_encoder_basic` 库,用于兼容性处理
require(['jpeg_encoder_basic'], function (JPEGEncoder) {
// 20. 创建 `JPEGEncoder` 实例
var encoder = new JPEGEncoder(),
// 21. 获取 canvas 中的图片数据
img = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 22. 使用 `JPEGEncoder` 将图片数据编码为 base64
resolve(encoder.encode(img, defaults.quality * 100));
});
} else {
// 23. 如果不需要兼容性处理,直接使用 `canvas.toDataURL` 生成 base64
resolve(canvas.toDataURL('image/jpeg', defaults.quality));
}
});
};
功能:生成 base64 数据。
实现:根据方向信息调整图片,并使用 canvas.toDataURL
或 JPEGEncoder
生成 base64。
_getResize
方法Lrz.prototype._getResize = function () {
// 1. 保存当前上下文到 `that`,方便在函数中访问
var that = this,
// 2. 获取之前初始化的 Image 对象
img = that.img,
// 3. 获取默认配置
defaults = that.defaults,
// 4. 获取配置中设置的宽度和高度
width = defaults.width,
height = defaults.height,
// 5. 获取图片的方向信息(Orientation)
orientation = that.orientation;
// 6. 初始化返回值 `ret`,表示压缩后的尺寸,默认值为图片的原始尺寸
var ret = {
width: img.width, // 图片的原始宽度
height: img.height // 图片的原始高度
};
// 7. 判断方向信息是否需要交换宽度和高度:
// - 如果方向信息是 5、6、7、8,表示图片需要旋转 90 度或 270 度
// - 此时需要交换宽度和高度
if ("5678".indexOf(orientation) > -1) {
ret.width = img.height; // 交换宽度和高度
ret.height = img.width; // 交换宽度和高度
}
// 8. 如果原图的宽度或高度小于设定的宽度或高度,直接返回原图尺寸
if (ret.width < width || ret.height < height) {
return ret;
}
// 9. 计算图片的宽高比
var scale = ret.width / ret.height;
// 10. 如果配置中同时设置了宽度和高度
if (width && height) {
// 11. 判断图片的宽高比是否大于设定的宽高比
if (scale >= width / height) {
// 12. 如果图片的宽度大于设定的宽度,调整宽度和高度
if (ret.width > width) {
ret.width = width; // 设置宽度为设定值
ret.height = Math.ceil(width / scale); // 根据宽高比计算高度
}
} else {
// 13. 如果图片的高度大于设定的高度,调整高度和宽度
if (ret.height > height) {
ret.height = height; // 设置高度为设定值
ret.width = Math.ceil(height * scale); // 根据宽高比计算宽度
}
}
}
// 14. 如果只设置了宽度
else if (width) {
// 15. 如果图片的宽度大于设定的宽度,调整宽度和高度
if (width < ret.width) {
ret.width = width; // 设置宽度为设定值
ret.height = Math.ceil(width / scale); // 根据宽高比计算高度
}
}
// 16. 如果只设置了高度
else if (height) {
// 17. 如果图片的高度大于设定的高度,调整高度和宽度
if (height < ret.height) {
ret.width = Math.ceil(height * scale); // 根据宽高比计算宽度
ret.height = height; // 设置高度为设定值
}
}
// 18. 处理 iOS 设备的限制:
// - 如果宽度或高度超过 3264 或 2448,iOS 设备可能无法生成 base64
// - 逐步缩小图片尺寸,直到满足条件
while (ret.width >= 3264 || ret.height >= 2448) {
ret.width *= 0.8; // 缩小宽度
ret.height *= 0.8; // 缩小高度
}
// 19. 返回计算后的压缩尺寸
return ret;
};
功能:计算压缩后的图片尺寸。
实现:根据配置的宽度、高度和图片的原始尺寸,计算压缩后的尺寸。
getJsDir
方法function getJsDir(src) {
// 1. 初始化变量 `script`,用于保存找到的脚本元素
var script = null;
// 2. 判断是否传入了 `src` 参数:
// - 如果传入了 `src`,表示需要根据 `src` 查找特定的脚本元素
// - 如果没有传入 `src`,表示需要获取当前页面中最后一个脚本元素
if (src) {
// 3. 使用 `Array.prototype.filter` 方法遍历页面中的所有脚本元素
// - `document.scripts` 返回页面中所有的 `