为何说这是一篇比较适合小白的前端图片压缩文章呢?由于我也是一个刚工做半年的前端小白,最近接到了一个前端图片压缩上传的任务,经过各类百度博客完成了这项任务,可是任务完成后对各类技术细节却还不是特别理解,因此我针对我不理解的每个技术细节都进行了记录和学习,最后造成本篇博客,我以为我不了解的地方可能也会有别的同窗不是很了解,因此本篇博客科普向比较重,技术深度可能达不到大佬们的眼界,但也是本身的记录和学习,加油!!!javascript
若是您只是为了copy代码实现功能,建议您不要看这篇博客了。若是您是copy了代码实现了功能,想回来了解具体的实现流程、实现原理以及部分科普,我以为本篇博客能给你带来不小的收获。css
最近的项目有一个技术场景,简单来讲就是用户须要上传图片至服务器。就是这么一个简单的技术场景,可是用户是不可控的,他们可能上传的图片会是几M甚至十几M的大小,如此大的图片必然会致使上传时间过长,用户感知很差,服务器压力大等不良影响。html
做为组里惟一一个前端,老大就给了我一个任务,在用户上传图片前把图片压缩了再上传。那么本篇博客主要讲解的就是图片压缩中我是如何进行压缩的,以后的博客会讲解如何将压缩的图片进行上传。前端
本文实现的功能流程以下:java
简要流程图以下:web
FileReader
?我知道
canvas
是画布,可是具体是怎么样用它进行图片压缩的呢?Base64我知道可是什么是
DataURL格式的数据呢?
Blob对象又是什么?
同窗们,不着急,对于这些我都会进行一一讲解,由于这也是做为一个小白的我初次见到这些陌生名词时的疑惑。canvas
FileReader
这个对象是作什么的?在本次图片压缩中起到了什么做用?后端
咱们先来看看MDN上对它的解释:FileReader
对象容许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File
或 Blob
对象指定要读取的文件或数据。api
通俗来说,就是这个对象是用来读取File
对象或Blob
对象的。File
对象就是<input type="file">
获取到的对象,而Blob
(二进制)对象在本文的第5点有讲解。跨域
做为一个js原生的用于读取文件的对象,FileReader
自己就有较为完整的钩子函数以及一些实例方法,可是本文主要介绍图片压缩,因此在这里只重点讲本文使用到的1个钩子函数和1个实例方法,对其它的钩子和方法都不作详细介绍。
FileReader.onload
:处理load
事件。即该钩子在读取操做完成时触发,经过该钩子函数能够完成例如读取完图片后进行预览的操做,或读取完图片后对图片内容进行二次处理等操做。
FileReader.readAsDataURL
:读取方法,而且读取完成后,result
属性将返回 Data URL
格式(Base64 编码)的字符串,表明图片内容。
除了用到的这个钩子和这个实例方法外,FileReader
对象还有onabort
、onerror
、onloadstart
、onloadend
、onprogress
等钩子;也有abort()
、readAsArrayBuffer
、readAsBinaryString
等实例方法,在次就不过多描述。
在onload
这个钩子对上传的图片实现了预览,而且进行了图片压缩处理。经过readAsDataURL()
方法进行了文件的读取,而且经过result
属性拿到了图片的Base64(DataURL)
格式的数据,而后经过该数据实现了图片预览的功能。有的同窗看到这里是否是有点好奇,为何拿到了这个Base64(DataURL)
格式的数据就能直接展现处图片了呢?没关系,往下看,我会在后文中解释这个DataURL
格式的神奇。
FileReader
部分代码以下:
function canvasDataURL(file,item,callback) { //压缩转化为base64
var reader = new FileReader(); //读取文件的对象
reader.readAsDataURL(file); //对文件读取,读取完成后会将内容以base64的形式赋值给result属性
reader.onload = function (e) { //读取完成的钩子
console.log("原始二进制字符串:",this.result.toString());
const img = new Image();
const quality = 0.2; // 图像质量
const canvas = document.createElement('canvas');
const drawer = canvas.getContext('2d');
img.src = this.result;
console.log("FileReader对象:",this);
//图片预览
var picDom = $(item.item).find("img");
picDom.attr('src', this.result); //图片连接(base64)
//图片压缩转码
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
drawer.drawImage(img, 0, 0, canvas.width, canvas.height);
convertBase64UrlToBlob(canvas.toDataURL(file.type, quality), callback);
}
}
}
复制代码
canvas
元素众所周知是画布,那么canvas
在图片压缩中起到了什么做用?实现图片压缩的核心内容主要是使用到了canvas
的什么方法?
实现图片压缩最核心的地方就在canvas
这里,咱们先使用CanvasRenderingContext2D.drawImage()
方法将选中的图片文件在画布上绘制出来,再使用Canvas.toDataURL()
将画布上的图片信息转换成base64(DataURL)格式的数据。有同窗会问,那么是在哪儿实现的压缩?其实压缩的核心就在Canvas.toDataURL()
方法的quality
参数上了,下面咱们会具体介绍本文中使用到的2个canvas
画布上的方法。
语法以下:
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
复制代码
这个方法是CanvasRenderingContext2D
上的绘制图片的方法,若是有朋友想了解CanvasRenderingContext2D
和canvas
之间的联系和区别的话能够自行了解,这里不过多赘述。
首先介绍一下该方法所接受的9个参数:
image
:Object;绘制在Canvas上的元素,能够是各种Canvas图片资源),如图片,SVG图像,Canvas元素自己等。
dx
:Number;在Canvas画布上规划一片区域用来放置图片,dx就是这片区域的左上角横坐标。
dy
:Nmuber;在Canvas画布上规划一片区域用来放置图片,dy就是这片区域的左上角纵坐标。
dWidth
:Number;在Canvas画布上规划一片区域用来放置图片,dWidth就是这片区域的宽度。
dHeight
:Number;在在Canvas画布上规划一片区域用来放置图片,dHeight就是这片区域的高度。
sx
:Number;表示图片元素绘制在Canvas画布上起始横坐标。
sy
:Number;表示图片元素绘制在Canvas画布上起始纵坐标。
sWidth
:Number;表示图片元素从坐标点开始算,多大的宽度内容绘制Canvas画布上。
sHeight
;表示图片元素从坐标点开始算,多大的高度内容绘制Canvas画布上。
不少同窗看到这里这么多个参数是否是有点懵逼了?不用慌,我也很懵逼,其实我对canvas也不是很了解,不过不要紧咱们直接看看本文中使用到这个方法时咱们传递了什么参数
//代码
//先建立canvas画布,再获取canvas画布上的2d绘图环境,经过这个2d绘图环境才可以使用绘制API
const canvas = document.createElement('canvas');
//返回一个在画布上绘制2d图的环境对象,该对象上包含有canvas绘制2d图形的API
const drawer = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
drawer.drawImage(img, 0, 0, canvas.width, canvas.height);
//实际传递的参数
drawer.drawImage(image, dx, dy, dWidth, dHeight)
复制代码
结合上面列举的9个参数,咱们能够知道咱们实际上只使用到了5个参数。咱们传递了1个图片,紧接着咱们定义了canvas上的起始区域坐标点以及这个canvas上放置图片的区域的宽高。
实质上,咱们设置的canvas放置图片区域的宽高大小跟图片自己是如出一辙的,由于在本文中,使用该方法的目的不在于在canvas上展现一张图片给用户看,而是在于在canvas上绘制出这张图片,这样咱们才能使用接下来的的这个Canvas.toDataURl()
方法
咚!咚!咚!敲重点了,这个方法才是本文中实现图片压缩的核心。
语法以下:
canvas.toDataURL(mimeType, quality);
复制代码
Canvas.toDataURl()
方法能够将canvas画布上的信息转换为base64(DataURL)格式的图像信息,纯字符的图片表示形式。该方法接收2个参数:
mimeType
(可选):String;表示须要转换的图像的mimeType类型。默认值是image/png,还能够是image/jpeg,甚至image/webp(前提浏览器支持)等。
quailty
(可选):Number;quality表示转换的图片质量。范围是0到1。此参数要想有效,图片的mimeType须要是image/jpeg或者image/webp,其余mimeType值无效。默认压缩质量是0.92。
到这里,不少同窗就能够知道了,前端图片压缩的核心方法,是否是就在这个方法的quailty
参数上面呢?好了,让咱们看看本文使用到该方法的地方:
const canvas = document.createElement('canvas');
const quality = 0.2; //设置压缩比例
canvas.toDataURL(file.type, quality)
复制代码
能够看到,本文中设置了压缩质量为0.2,须要注意的是否是说压缩质量设置为0.2实际压缩效果就为5倍压缩(在本文中,压缩质量设置成了0.2但实际压缩效果确实7-9倍),简单的说,当到达了这一步之后,其实图片已经完成了压缩,咱们已经能够直接拿着返回的base64(DataURL)格式的数据去渲染图片,可是,若是你的目的是将图片先进行压缩,压缩后再上传给服务器,而且服务器只接受二进制的图片信息的话,那就得好好考虑怎么将base64转换成二进制Blob对象了,关于Blob,不要着急,我会在下一篇上传篇中对它进行科普。
小tips:该方法为同步方法,若是须要转换的Canvas尺寸很大,则会阻塞脚本的运行,所以须要注意控制Canvas的尺寸。
语法以下:
canvas.toBlob(callback, mimeType, quality)
复制代码
若是看了6.3小节的同窗应该会以为,这2个方法长得差很少,参数也差很少,那么它们的效果是否也是差很少的呢?
固然,该方法的做用是将canvas画布上的信息转换为Blob对象。该方法接收的参数基本与6.3的方法相同,区别在于,该方法多接受一个参数,该参数为:
callback
:Function;toBlob()方法执行成功后的回调方法,支持一个参数,表示当前转换的Blob对象。
6.3小节的最后说过,toDataURL()
方法是同步方法,那么咱们toBlob()
与此不一样,它是一个异步的方法,因此该方法会多接受一个参数callback
,该参数就是toBlob()
的回调函数。
好了,既然本文最终目的是将file压缩后,再转换成Blob对象上传至后端,那么为何不直接使用toBlob()
方法,而是使用toDataURL()
方法呢?对于这点的解释我会在下一篇上传篇中进行详解,各位同窗能够持续关注个人博客。
不知道有心的同窗发现了吗?本文中屡次提到了DataURL
格式的数据,那究竟什么才是DataURL
格式的数据呢?
在下面对DataURL
展开了解以前,咱们能够先来复习一下常见的img
标签的src是什么样的?
那么src除了这种赋值方式以外,还有什么形式可以展现图片吗?
固然有,那就是咱们的DataURL
,详见下图:
DataURL
在实际中有什么用处呢?它的定义是什么?什么场景下须要用到它?带着这个疑问看下去吧。
先来看看我从网上找到的比较官方的定义:DataURL
是由RFC2397定义的一种把小文件直接嵌入文档的方案。格式以下:
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAS...">
复制代码
咱们将例子与格式一一对照来看
MIME type
:表示数据呈现的格式,简单来讲就是这个数据的类型是什么?是png仍是jpg甚至是html,结合实例来看,在实例中咱们的这段数据它的类型就是一个图片,并且是个jpg格式的图片。
character
:字符集;这个是可选项,默认为charset=US-ASCII
,若是指定的是图片的话,就再也不使用字符集了。在实例中,咱们的这段数据表明的就是一张图片,随意是没有这个字段的。
base64
:这一部分将代表数据编码方式,固然咱们能够不使用base64的编码方式,那样咱们将使用标准的URL编码方式。在本实例中,咱们的图片就是采起base64的编码方式。
enconded data
:这就是实际的数据了,在实例中,这就是这张图片的base64编码。
好了,DataURL
的定义讲完了,那么DataURL
只能在图片里使用吗?固然不是,它能表示的东西有不少不少,好比大家能够复制下面的这段代码到浏览器地址栏中粘贴看一看
data:text/html;charset=UTF-8,<html><body><p>欢迎看刘伟C的博客</p></body></html>
复制代码
下面会讲下DataURL
的优缺点。
这里就不作铺垫直接说
优势:
DataURL
,由于他不须要向外界发起访问。DataURL
来讲,它并不须要向服务器发送Http请求,它就天然不会占用一个http会话资源。缺点:
base64
的数据体积实际上会比原数据大,也就是Data URL形式的图片会比二进制格式的图片体积大1/3。因此若是图片较小,使用DataURL
形式的话是比较有利的,图片较小即便比原图片大一些也不会大不少,相比发起一个Http会话这点开销算什么。可是若是图片较大的状况下,使用DataURL
的开销就会相应增大了,具体如何取舍还得各位同窗结合实际场景考量。那么总结一下,DataURL
带来的便利缘由就是一个:它不须要发起http请求;而它的缺点概括起来就是两个:体积比原有还要大、不会被缓存。
看完上面的科普,同窗们是否是都了解了DataURL
的优缺点,那么不知道各位是否会好奇,本次前端压缩为什么使用的是DataURL
呢?
答案其实在第一小节中有,由于咱们想作图片预览,而图片预览是经过FileReader
对象实现的,FileReader
会去读文件,而且返回文件的内容,可是返回的内容要么就是二进制字符串,要么就是二进制字节数组,这些都不能直接用于图片展现,只有调用FileReader.readAsDataURL()
返回的DataURL
格式的数据能够直接使用。
由于公司使用的前端框架比较非主流,是layUI框架的,因此个人代码也是基于layUI的前端压缩(因此大家直接copy个人代码大几率是跑不起来的),不过核心思想核心方法核心原理在博客中都有体现,只要理解了思想,其实不难将它复刻到大家本身的项目中去。
代码以下:
//这是layUI的文件上传组件,不了解的朋友也不必去看文档,理解思想就好了
var upload1 = upload.render({
elem: '.uploadImg',
url: releaseUrl,
accept: 'images',
auto: false,
//选择文件后的钩子函数
choose: function (obj) {
var that = this;
//预读本地文件示例,不支持ie8
obj.preview(function (index, file, result) {
console.log("选中的文件:", file);
var index = layer.load(1, {
content: '图片上传中....',
shade: 0.2,
success: function (layer) {
layer.find('.layui-layer-content').css({
'paddingTop': '40px',
'width': '60px',
'textAlign': 'center',
'backgroundPositionX': 'center'
});
}
});
//这段开始才是压缩的重点,因此关注这段代码便可
//若图片超过1M则启动压缩
if (file.size > (1024 * 1024)) {
// console.log("大于1m");
canvasDataURL(file, that, function (blob) {
// console.log("压缩后的二进制Blob对象:",blob);
console.log("压缩前:" + (file.size / 1024 / 1024) + "M");
console.log("压缩后:" + (blob.size / 1024 / 1024) + "M");
var compresFile = new File([blob], file.name, {
type: file.type
})
obj.upload(1, compresFile);
})
}
//若图片不超过1M则无需压缩,直接传
else {
var picDom = $(that.item).find("img");
picDom.attr('src', result);
obj.upload(1, file);
}
});
},
})
//下面是压缩部分的核心代码
/** * 经过canvas画布实现压缩,并转化为base64格式的图片 * @param {File} file : 图片 * @param {Object} item :经过item找到当前对象的img标签 * @param {Function} callback :回调函数 */
function canvasDataURL(file,item,callback) { //压缩转化为base64
var reader = new FileReader(); //读取文件的对象
reader.readAsDataURL(file); //对文件读取,读取完成后会将内容以base64的形式赋值给result属性
reader.onload = function (e) { //读取完成的钩子
const img = new Image();
const quality = 0.2; // 图像质量
//先建立canvas画布,再获取canvas画布上的2d绘图环境,经过这个2d绘图环境才可以使用绘制API
const canvas = document.createElement('canvas'); //建立canvas画布
const drawer = canvas.getContext('2d'); //返回一个在画布上绘制2d图的环境对象,该对象上包含有canvas绘制2d图形的API
img.src = this.result;
// console.log("FileReader对象:",this);
//图片预览
var picDom = $(item.item).find("img");
picDom.attr('src', this.result); //图片连接(base64)
//图片压缩代码,须要注意的是,img图片渲染是异步的,因此必须在img的onlaod钩子中再进行相应操做
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
drawer.drawImage(img, 0, 0, canvas.width, canvas.height);
convertBase64UrlToBlob(canvas.toDataURL(file.type, quality), callback);
}
}
}
//下面是上传部分的核心代码
/** * 将base64格式转化为Blob格式 * @param {string} urlData : urlData格式的数据,经过这个转化为Blob对象 * @param {Function} callback : 回调函数 */
function convertBase64UrlToBlob(urlData, callback) { //将base64转化为文件格式
// console.log("压缩成base64的对象:",urlData);
const arr = urlData.split(',')
// console.log("arr",arr);
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1]) //atob方法用于解码base64
// console.log("将base64进行解码:",bstr);
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
// console.log("Uint8Array:",u8arr);
callback(new Blob([u8arr], {
type: mime
}));
}
复制代码
关于前端图片压缩的压缩篇告一段落,下一篇博客将介绍前端图片压缩的上传篇,重点是
也欢迎大佬在评论区留言指正博客错误的地方,也欢迎像我同样的小白前端一块儿在评论区讨论。