背景
实际生产中经常遇到这样的场景:为减小服务器压力,上传附件尤其是图片的时候,往往需要限制上传文件的大小。而限制的方案也有两种,一种就是限制用户可上传的文件大小,由用户来选择上传的文件和如果文件过大由用户自行进行压缩裁剪;另一种就是由服务进行图片的压缩和大小控制然后再上传到服务器。这里主要介绍的是第二种方案。
主要技术
前边有介绍过证书的生成和下载,其中就有证书的压缩和打包的相关操作,感兴趣的可以看下本人的那篇文章。这里同样是采用的该原理,步骤如下:
关键步骤
图片文件-->文件流(base64位编码)-->canvas-->压缩-->生成压缩后的文件-->上传。
这里的压缩过程,做了相应的优化。优化方案有两种,一种是重复压缩,一种是计算比例压缩。
而由于压缩比和文件大小并不是正比例关系,所有可以保险起见再乘以一个系数。比如:quality:
1024*0.7/fileObj.size(0.7是保险系数,1024是限制大小1M的意思,可根据个人需要自行调整参数,也可以封装成接口参数统一修改)
这里还自行封装了一个进度组件,使用的是原生js。
代码
代码和相关注释如下:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>
文件压缩上传</title> 6 <script type="text/javascript"> 7 /* 8 三个参数 9
file:一个是文件(类型是图片格式), 10 w:一个是文件压缩的后宽度,宽度越小,字节越小 11
objDivOrCallback:一个是容器或者回调函数 12 photoCompress() 13 */ 14 function
photoCompress(file,w,objDivOrCallback) { 15 var ready = new FileReader() 16 /*
开始读取指定的Blob对象或File对象中的内容.
当读取操作完成时,readyState属性的值会成为DONE,如果设置了onloadend事件处理程序,则调用之.同时,result属性中将包含一个data:
URL格式的字符串以表示所读取文件的内容.*/ 17 ready.readAsDataURL(file) 18 ready.onload =
function() { 19 var re = this.result 20 canvasDataURL(re, w,
objDivOrCallback) 21 } 22 } 23 function canvasDataURL(path, obj, callback) {
24 var img = new Image() 25 img.src = path 26 img.onload = function(){ 27
var that = this 28 // 默认按比例压缩 29 var w = that.width, 30 h = that.height, 31
scale= w / h 32 w = obj.width || w 33 h = obj.height || (w / scale) 34 var
quality= 0.7 // 默认图片质量为0.7 35 //生成canvas 36 var canvas =
document.createElement('canvas') 37 var ctx = canvas.getContext('2d') 38 //
创建属性节点 39 var anw = document.createAttribute("width") 40 anw.nodeValue = w
41 var anh = document.createAttribute("height") 42 anh.nodeValue = h 43
canvas.setAttributeNode(anw) 44 canvas.setAttributeNode(anh) 45
ctx.drawImage(that,0, 0, w, h) 46 // 图像质量 47 if(obj.quality && obj.quality <=
1 && obj.quality > 0) { 48 quality = obj.quality 49 } 50 //
quality值越小,所绘制出的图像越模糊 51 var base64 = canvas.toDataURL('image/jpeg', quality)
52 // 这里不能直接quality: 0.2,因为这样就相当于还是在原来的大小的基础上压缩 53 var bl =
convertBase64UrlToBlob(base64) 54 // 如果还大于1M,继续压缩--代码待优化,可以减去重复生成文件和转码的过程 55 if
(bl.size/1024 > 1025) { 56 // 其实也可以在这里直接写一个匹配压缩比直到大小小于1的方法 57
photoCompress(bl, { 58 quality: 0.5 * obj.quality 59 }, callback) 60 } else
{ 61 callback(bl) 62 } 63 // 回调函数返回base64的值--改为返回文件对象 64 //
callback(base64) 65 } 66 } 67 /** 68 * 将以base64的图片url数据转换为Blob 69 *
@param urlData 70 * 用url方式表示的base64图片数据 71 */ 72 function
convertBase64UrlToBlob(urlData) { 73 var arr = urlData.split(','), mime = arr[0
].match(/:(.*?);/)[1], 74 bstr = atob(arr[1]), n = bstr.length, u8arr = new
Uint8Array(n) 75 while(n--) { 76 u8arr[n] = bstr.charCodeAt(n) 77 } 78
return new Blob([u8arr], {type:mime}) 79 } 80 81 82 var xhr 83 //上传文件方法
84 function UpladFile() { 85 var fileObj = document.getElementById("file"
).files[0] // js 获取文件对象 86 var url = "
http://pxjy.api.test.nercel.cn/file/publicFile/upload" // 接收上传文件的后台地址 87 88
var form = new FormData() // FormData 对象 89 90 if(fileObj.size/1024 > 1025) {
//大于1M,进行压缩上传 91 photoCompress(fileObj, { 92 //
这里还有一种方案,那就是这里的quality改为计算压缩比(由于压缩比和文件大小并不是正比例关系,所有可以保险起见再乘以一个系数) 93 //
压缩比计算的方案:quality: 1024*0.7/fileObj.size--0.7是保险系数--这些参数可以进一步封装 94 quality: 0.2
95 // }, function(base64Codes){ 96 // 修改为返回文件对象 97 }, function(bl){ 98 //
console.log("压缩后:" + base.length / 1024 + " " + base); 99 // var bl =
convertBase64UrlToBlob(base64Codes) 100 // form.append("file", bl,
"file_"+Date.parse(new Date())+".jpg"); // 文件对象 101 form.append("multipartFile"
, bl,"file_"+Date.parse(new Date())+".jpg") // 文件对象 102 xhr = new
XMLHttpRequest()// XMLHttpRequest 对象 103 xhr.open("post", url, true) //
post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。 104 xhr.setRequestHeader("enctype", "
multipart/form-data") // 设置请求头 105 xhr.setRequestHeader("Authorization", "
Bearer 8d782bb1-768f-4fa7-80d2-5e2b6d6a6f64") // 设置请求头 106 // open后才可以设置头 107
xhr.onload= uploadComplete //请求完成 108 xhr.onerror = uploadFailed //请求失败 109 110
xhr.upload.onprogress= progressFunction//【上传进度调用方法实现】 111
xhr.upload.onloadstart= function(){//上传开始执行方法 112 ot = new Date().getTime() //
设置上传开始时间 113 oloaded = 0//设置上传开始时,以上传的文件大小为0 114 }; 115 116 xhr.send(form) //
开始上传,发送form数据 117 }) 118 } else { //小于等于1M 原图上传 119 // form.append("file",
fileObj) // 文件对象 120 form.append("multipartFile", fileObj) // 文件对象 121 xhr =
new XMLHttpRequest() // XMLHttpRequest 对象 122 xhr.open("post", url, true) //
post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。 123 xhr.setRequestHeader("enctype", "
multipart/form-data") // 设置请求头 124 xhr.setRequestHeader("Authorization", "
Bearer 8d782bb1-768f-4fa7-80d2-5e2b6d6a6f64") // 设置请求头 125 // open后才可以设置头 126
xhr.onload= uploadComplete //请求完成 127 xhr.onerror = uploadFailed //请求失败 128 129
xhr.upload.onprogress= progressFunction//【上传进度调用方法实现】 130
xhr.upload.onloadstart= function() {//上传开始执行方法 131 ot = new Date().getTime() //
设置上传开始时间 132 oloaded = 0//设置上传开始时,以上传的文件大小为0 133 } 134 135 xhr.send(form) //
开始上传,发送form数据 136 } 137 } 138 139 //上传成功响应 140 function uploadComplete(evt) {
141 //服务断接收完文件返回的结果 142 var data = JSON.parse(evt.target.responseText) 143 if
(data.code=== 200) { 144 uploadSuccess() 145 } else { 146 uploadFailed() 147
}148 149 } 150 //上传失败 151 function uploadFailed(evt) { 152 alert("上传失败!") 153
}154 //上传成功 155 function uploadSuccess(evt) { 156 alert("上传成功!") 157 } 158 //
取消上传 159 function cancleUploadFile(){ 160 xhr.abort() 161 } 162 163 //
上传进度实现方法,上传过程中会频繁调用该方法 164 function progressFunction(evt) { 165 var progressBar
= document.getElementById("progressBar") 166 var percentageDiv =
document.getElementById("percentage") 167 //
event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0
168 if (evt.lengthComputable) {// 169 progressBar.max = evt.total 170
progressBar.value= evt.loaded 171 percentageDiv.innerHTML =
Math.round(evt.loaded/ evt.total * 100) + "%" 172 } 173 var time =
document.getElementById("time") 174 var nt = new Date().getTime()//获取当前时间 175
var pertime = (nt-ot)/1000 //计算出上次调用该方法时到现在的时间差,单位为s 176 ot = new
Date().getTime()//重新赋值时间,用于下次计算 177 var perload = evt.loaded - oloaded //
计算该分段上传的文件大小,单位b 178 oloaded = evt.loaded//重新赋值已上传文件大小,用以下次计算 179 //上传速度计算 180
var speed = perload/pertime//单位b/s 181 var bspeed = speed 182 var units = 'b/s'
//单位名称 183 if(speed/1024>1) { 184 speed = speed/1024 185 units = 'k/s' 186 }
187 if(speed/1024>1) { 188 speed = speed/1024 189 units = 'M/s' 190 } 191
speed= speed.toFixed(1) 192 //剩余时间 193 var resttime = ((evt.total-evt.loaded)/
bspeed).toFixed(1) 194 time.innerHTML = ',速度:'+speed+units+',剩余时间:'+resttime+'s
' 195 if(bspeed==0) time.innerHTML = '上传已取消' 196 } 197 </script> 198 </head>
199 <body> 200 <progress id="progressBar" value="0" max="100" style="width:
300px;"></progress> 201 <span id="percentage"></span><span id="time"></span> 202
<br /><br /> 203 <input type="file" id="file" name="myfile" accept
="image/x-png, image/jpg, image/jpeg, image/gif"/> 204 <input type="button"
onclick="UpladFile()" value="上传" /> 205 <input type="button" onclick
="cancleUploadFile()" value="取消" /> 206 </body> 207 </html>
此处是借鉴网上思路的基础上的个人修改完善后的代码, 并且有待有时间的时候做进一步封装优化和封装成npm组件以及vue组件。
代码git地址:
https://github.com/MRlijiawei/components/blob/master/file/%E5%9B%BE%E7%89%87%E5%8E%8B%E7%BC%A9%E4%B8%8A%E4%BC%A0.html
<https://github.com/MRlijiawei/components/blob/master/file/%E5%9B%BE%E7%89%87%E5%8E%8B%E7%BC%A9%E4%B8%8A%E4%BC%A0.html>
扩展
png图片的另一种压缩方案
png的简介
什么是png:
PNG的全称叫便携式网络图型(Portable Network Graphics)是目前最流行的网络传输和展示的图片格式,原因有如下几点:
*
无损压缩:PNG图片采取了基于LZ77派生算法对文件进行压缩,使得它压缩比率更高,生成的文件体积更小,并且不损失数据。
*
体积小
:它利用特殊的编码方法标记重复出现的数据,使得同样格式的图片,PNG图片文件的体积更小。网络通讯中因受带宽制约,在保证图片清晰、逼真的前提下,优先选择PNG格式的图片。
*
支持透明效果:PNG支持对原图像定义256个透明层次,使得图像的边缘能与任何背景平滑融合,这种功能是GIF和JPEG没有的。
当初就是因为png的透明特性才开始喜欢它的。
png的类型:
*
PNG 8:PNG 8中的8,其实指的是8bits,相当于用2^8(2的8次方)大小来存储一张图片的颜色种类,2^8等于256,也就是说PNG
8能存储256种颜色,一张图片如果颜色种类很少,将它设置成PNG 8得图片类型是非常适合的。
*
PNG 24:PNG 24中的24,相当于3乘以8 等于 24,就是用三个8bits分别去表示
R(红)、G(绿)、B(蓝)。R(0~255),G(0~255),B(0~255),可以表达256乘以256乘以256=16777216种颜色的图片,这样PNG
24就能比PNG 8表示色彩更丰富的图片。但是所占用的空间相对就更大了。
*
PNG 32:PNG 32中的32,相当于PNG 24 加上
8bits的透明颜色通道,就相当于R(红)、G(绿)、B(蓝)、A(透明)。R(0~255),G(0~255),B(0~255),A(0~255)。比PNG
24多了一个A(透明),也就是说PNG 32能表示跟PNG 24一样多的色彩,并且还支持256种透明的颜色,能表示更加丰富的图片颜色类型。
png图片的数据编码:
PNG图片的数据结构其实跟http请求的结构很像,都是一个数据头,后面跟着很多的数据块,如下图所示:
使用16进制编码打开png图片,部分编码示例如下:
8950 4e47 0d0a 1a0a:这个是PNG图片的头,所有的PNG图片的头都是这一串编码,图片软件通过这串编码判定这个文件是不是PNG格式的图片。
0000 000d:是iHDR数据块的长度,为13。
4948 4452:是数据块的type,为IHDR,之后紧跟着是data。
0000 0292:是图片的宽度。
0000 024e:是高度。
以此类推,每一段十六进制编码就代表着一个特定的含义。感兴趣的可以自行百度。
所以,颜色重复度越大的、越接近的(渐变的颜色或透明度等),编码重复度就越大,就越容易压缩。
压缩原理:
png图片用差分编码(Delta encoding)对图片进行预处理,处理每一个的像素点中每条通道的值。
压缩阶段会将预处理阶段得到的结果进行Deflate压缩,它由 Huffman 编码 和 LZ77压缩构成。
压缩后的结果就是一串处理后的编码,保存到数据库中,占用空间会小很多,在使用的时候,再进行逆向解析渲染。
具体代码暂无。
热门工具 换一换