需求
客户端上传图片到服务器。大部分情况下,客户端的图片质量,远大于业务实际需要。上传,存储和下载展示,多出的都是浪费,节能环保,从源头做起。
实现原理
实现图片的压缩,实际上就是改变图片的尺寸(宽和高),或者改变图片的体积。使用CanvasRenderingContext2D.drawImage()和HTMLCanvasElement.toDataURL()这两个API,就可以实现这两种操作。CanvasRenderingContext2D.drawImage() 可以将Image写入到canvas中,写入过程中可以自定义画布上绘制图像的宽度和高度,从而实现对图片尺寸的改变。HTMLCanvasElement.toDataURL()可以将canvas的内容,导出成图片,导出时可以指定图片的质量,从而改变图片的体积。
但是我们从input得到的文件数据是File类型的,需要转变成CanvasRenderingContext2D.drawImage()能够写入的HTMLImageElement,HTMLCanvasElement.toDataURL()导出的数据格式是DataURL,而我们上传时需要Blob类型的数据,因此要实现得到文件,压缩,然后上传,我们需要对数据做一系列的处理。如同流水线加工,塞进去一只活蹦乱跳的大母鸡,最后得到一盒全家桶。
实现过程
实现过程分为三个部分:
- 得到目标文件
- 压缩
- 上传
第一步:得到目标文件
var input = document.getElementById('file'); input.addEventListener('change',function(e){ var file = this.files[0]; fileName = file.name; });
第二步:压缩
压缩过程中,图片对应的数据类型在不断变化,按照类型变化,分为7个步骤
- File转化成Data URIs
- Data URIs转化成HTMLImageElement
- HTMLImageElement写入canvas
- canvas转化成DataURL
- DataURL转化成二进制String
- 二进制String转化成ArrayBuffer
- ArrayBuffer转化成Blob
1.File转化成Data URIs
function fileToDataURL(file,callback) { var reader = new FileReader(); reader.onload = function () { var result = this.result; callback(result); } reader.readAsDataURL(file); }
2.Data URIs转化成HTMLImageElement
function dataURLToImage(dataURL,callback) { var img = new Image(); img.onload = function() { callback(img); } img.src = dataURL; }
3.HTMLImageElement写入canvas
创建canvas之后,可以设置canvas的宽和高。图片写入canvas的过程中,可以指定写入后的宽和高。这个过程,就是对图片尺寸进行更改。
4.canvas转化成DataURL
这个过程中需要注意,canvas.toDataURL(type, encoderOptions); 当type为''image/jpeg''或者'image/webp'时,encoderOptions指定的图片质量系数才是有效的。
function compress(img) { var canvas = document.createElement('canvas'), width = img.width, height = img.height; canvas.width = width; canvas.height = height; var context = canvas.getContext('2d'); context.drawImage(img, 0, 0, width, height); var dataUrl = canvas.toDataURL('image/jpeg',0.7); return dataUrl; }
5.DataURL转化成二进制String
6.二进制String转化成ArrayBuffer
7.ArrayBuffer转化成Blob
function dataUrlToBlob(dataUrl) { var text = window.atob(dataUrl.split(",")[1]); var buffer = new ArrayBuffer(text.length); var ubuffer = new Uint8Array(buffer); for (var i = 0; i < text.length; i++) { ubuffer[i] = text.charCodeAt(i); } var blob; var type = 'image/jpeg'; blob = new window.Blob([buffer], {type: type}); return blob; }
第三步:上传
得到图片对应的Blob数据,使用FormData发送到服务端。
function upload(blob,name) { var formData = new FormData(); name = name.substring(0,name.lastIndexOf('.')) + '.jpeg'; formData.append('file', blob ,name); formData.append('name',name); formData.append('size',blob.size); formData.append('type','image/jpeg'); var xhr = new XMLHttpRequest(); xhr.open('post','/fedemos/server/fileupload.php'); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log() } } xhr.send(formData); }
代码示例
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>
<body>
<input id="file" type="file" name="file" >
<script type="text/javascript">
var input = document.getElementById('file');
input.addEventListener('change',function(e){
var file = this.files[0];
fileName = file.name;
imageCompress(file,function(blob){
upload(blob,fileName);
})
});
function imageCompress(file,callback) {
fileToDataURL(file,function(dataURL){
dataURLToImage(dataURL,function(img){
transform(img)
})
})
function fileToDataURL(file,callback) {
var reader = new FileReader();
reader.onload = function () {
var result = this.result;
callback(result);
}
reader.readAsDataURL(file);
}
function dataURLToImage(dataURL,callback) {
var img = new Image();
img.onload = function() {
callback(img);
}
img.src = dataURL;
}
function transform(img) {
var dataUrl = compress(img);
var blob = dataUrlToBlob(dataUrl);
callback(blob);
}
function compress(img) {
var canvas = document.createElement('canvas'),
width = img.width,
height = img.height;
// if(width>1280 && width>height) {
// height = height*1280/width;
// width = 1280;
// } else if(height>width && height>1280) {
// width = width*1280/height;
// height = 1280;
// }
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0, width, height);
var dataUrl = canvas.toDataURL('image/jpeg',0.7);
return dataUrl;
}
function dataUrlToBlob(dataUrl) {
var text = window.atob(dataUrl.split(",")[1]);
var buffer = new ArrayBuffer(text.length);
var ubuffer = new Uint8Array(buffer);
for (var i = 0; i < text.length; i++) {
ubuffer[i] = text.charCodeAt(i);
}
var blob;
var type = 'image/jpeg';
blob = new window.Blob([buffer], {type: type});
return blob;
}
}
function upload(blob,name) {
var formData = new FormData();
name = name.substring(0,name.lastIndexOf('.')) + '.jpeg';
formData.append('file', blob ,name);
formData.append('name',name);
formData.append('size',blob.size);
formData.append('type','image/jpeg');
var xhr = new XMLHttpRequest();
xhr.open('post','/fedemos/server/fileupload.php');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log()
}
}
xhr.send(formData);
}
</script>
</body>
</html>
