基于Express框架的图片压缩上传

在网上查看了很多技术文档,也阅读了Express官方的技术文档,很多在运行上都多少会有一些问题,所以决定今天把程序从创建到运行完整的记录下来,方便参考。其中第三部分使用了canvas的图片压缩,如果你只需要压缩部分的代码说明,可以直接跳至第三部分。

一、运行环境及基本配置说明:

​ phpstorm 2018.1版本

​ nodejs 8.11.1版本

​ Express 4.16.0版本

二、项目创建及框架的搭建

​ 首先我们运行phpstorm,并create new project:

因为我是初学者,对于node的模板部署还不是很熟悉,所以这里用了静态的html(其实后来都把css和js写在html文档里了,所以这一部分默认属性也不会产生影响)

项目创建好之后,我们主要对下面三个文件:index.html、app.js以及package.json进行操作:

因为上传文件我们需要用到express的multer中间件,所以这里也要先配置好(默认是没有的),打开命令行,进入项目的目录中:

然后输入:

1
npm install express multer --save

等待完成:

这时我们再去查看项目的package.json文件,可以看到我们的中间件已经添加成功:

基本配置已经完成,接下来就是代码上场。

三、图片压缩模块:

这一部分不牵扯express框架或者nodejs,为纯html和js的canvas图片压缩,如果你只需要做这一部分的话,可以单独使用,无需前两部分的操作。

canvas进行图片压缩的原理简单来说,就是将原来的图片转为base64格式的代码,然后重新绘制在画布上。其实也可以理解为将一幅巨大的壁画,重新copy在一张小的画布上,并删除一些细节的过程。

那么先上我们的html代码,这里我把css也写进去了(虽然说并没有用什么样式):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
<title>photo——compress</title>
<link rel="stylesheet" href="/stylesheets/style.css">
<style>
#modify_img{
display: block;
width: 300px;
height: 300px;
background-size: 100% 100%;
}
</style>
</head>

<body>
<div class="img_post">
<img src="images" alt="" id="modify_img">
<input id="file" type="file" accept="image/*">
<button id="upload">上传</button>
<span id="progress">0%</span>
</div>
</body>

</html>

下面来逐一说明,div里面的标签:

“img”:必要,主要是为canvas画布固定一个位置,它必须设置为block显示, 同时设置宽高,其中的background-size属性是防止图片本来就很小而无法完全占满整个框造成显示错误的情况。

“input”:必要,设置id和input属性,同时只允许选择图片类型文件,在点击选择完文件后,会自动在”img”中生成压缩好的图片。

“button”:必要(如果不需要上传功能可忽略),点击按钮可以将压缩好的图片上传至express服务器,同时控制台会返回相关信息。

“span”:不必要,主要是为了显示上传进度。

接下来是canvas绘图的代码,因为写的时候已经注释好了,后面就不再逐一赘述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var getfile = document.querySelector('#file');  //获取文件中id为file的元素
var reader = new FileReader(); //FileReader对象允许WEB应用程序异步读取存储在用户计算机上的文件
var img = new Image(); //Image功能等价于 document.createElement('img')
var file = null;
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d'); //返回一个用于在画布上绘图的环境,目前唯一合法值为“2d”
var newUrl = null;

//对input绑定事件监听,当内容改变时触发
getfile.addEventListener('change', function(event){
file = event.target.files[0];
if ( file.type.indexOf("image") == 0 ){
reader.readAsDataURL(file);
}
});

reader.onload = function(e){ //当文件上传完后触发,主要是为了获取源文件的长宽
img.src = e.target.result;
};


img.onload = function(){ //在图片加载完成后立即发生
var orgin_width = this.width;
var orgin_height = this.height;
var max_width = 300;
var max_height = 300;
var target_width = orgin_width;
var target_height= orgin_height;

if (orgin_width > max_width || orgin_height > max_height){ //判断图像是否大于限定的最大长宽
if (orgin_width / orgin_height > max_width / max_height){ //上传图像的宽高比较大,以宽为长轴对高进行拉伸
target_width = max_width;
target_height = Math.round(max_width * ( orgin_height / orgin_width )); //round()方法表示四舍五入取整数
}
else {
target_width = Math.round(max_height * ( orgin_width / orgin_height )); //高宽比较大,以高为长轴,对宽进行缩短
target_height = max_height;
}
}

canvas.width = target_width;
canvas.height = target_height;
//如果是png转jpg如果canvas存在透明区域,实际绘制出来会变为黑色,因为canvas默认为rgba(0,0,0,0)
//在转成jpg的时候变成了rgba(0,0,0,1),解决办法就是在绘制之前为canvas铺上一层白色的底色
context.fillStyle = "#FFF";
context.fillRect(0, 0, target_width, target_height); //绘制已填充矩形
//context.clearRect(0, 0, target_width, target_height); //清除画布并设置宽高
context.drawImage(img, 0, 0, target_width, target_height); //绘画

newUrl = canvas.toDataURL('image/jpeg', 0.92); //base64 格式
modify_img.style.backgroundImage='url(' + newUrl + ')';
};

设置好后点击右上角的chrome进行测试:

页面显示如下(请无视那个button的乱码,过会我们用服务器进行测试就正常了):

为了测试,我选择了一张1.3M的照片:

点击打开后,图片会进行压缩,然后显示在”img”标签里,可以明显的通过图片的锯齿感看出图片已经进行了压缩:

这时候如果我们去检查文档,会发现图片已经被转为了base64编码:

到此,我们的canvas已经顺利完成了图片的压缩工作,接下来就是服务器上场。

四、利用Express的multer中间件实现文件与服务器的交互

当时在做这一部分的时候走了很多弯路,因为express4.x已经不再包括body-parser,需要自己进行相关配置,搞得很头痛。后来发现到最新的express已经为我们提供了multer这个中间件,方便了不少,接下来先上js的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var upload = document.querySelector('#upload');           //为"上传"按钮绑定变量
var progress = document.querySelector('#progress'); //为进度条绑定变量
var xhr = new XMLHttpRequest(); //定义xhr


upload.addEventListener('click', uploadFile, false); //设置按钮点击事件

function uploadFile(event){
function dataURLtoFile(dataurl, filename) { //将我们上面转换的base64格式编码转为文件类型
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}

var file_img = dataURLtoFile(newUrl, file.name); //定义文件类型的图片
var formData = new FormData(); //定义formdata的处理方法
formData.append('test-upload', file_img); //定义处理域以及文件传入
xhr.onload = uploadSuccess; //上载成功的返回函数
xhr.upload.onprogress = setProgress; //处理进度的放回函数
xhr.open('post', '/upload', true); //ajax数据请求方法
xhr.send(formData); //ajax数据发送方法
}

function uploadSuccess(event){
if(xhr.readyState === 4){
console.log(xhr.responseText);
}
}

function setProgress(event) {
if (event.lengthComputable) {
var complete = Number.parseInt(event.loaded / event.total * 100);
progress.innerHTML = complete + '%';
}
}

这里需要注意的是

1
2
3
...
var file_img = dataURLtoFile(newUrl, file.name); //定义文件类型的图片
...

第二个参数file.name中,file为第三部分中定义过的file,目的是为了传入压缩过的文件名称,如果需要修改请将dataURLtoFile()函数一并修改。

然后我们需要对express进行一些配置,在第二部分我们已经引入中间件,那么只需要在app.js中进行引入。首先在文件头定义上传和文件这两个变量,方便我们使用,并设置好端口号:(省略号之间的为新写入的内容!!!)

1
2
3
4
5
6
7
var logger = require('morgan');
//...
var upload = require('multer')({ dest: 'uploads/' }); //上传图片保存的文件夹为uploads
var fs = require('fs');
var port = 8000;
//...
var indexRouter = require('./routes/index');

接下来定义文件的上传输出信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
app.use('/users', usersRouter);
//...
app.set('port', port);
app.get('*', (req, res) => {
res.redirect('index.html');
});

app.post('/upload', upload.single('test-upload'), (req, res) => {
// 没有附带文件
if (!req.file) {
res.json({ ok: false });
return;
}

// 输出文件信息
console.log('====================================================');
console.log('fieldname: ' + req.file.fieldname);
console.log('originalname: ' + req.file.originalname);
console.log('encoding: ' + req.file.encoding);
console.log('mimetype: ' + req.file.mimetype);
console.log('size: ' + (req.file.size / 1024).toFixed(2) + 'KB');
console.log('destination: ' + req.file.destination);
console.log('filename: ' + req.file.filename);
console.log('path: ' + req.file.path);

// 重命名文件
let oldPath = path.join(__dirname, req.file.path);
let newPath = path.join(__dirname, 'uploads/' + req.file.originalname);
fs.rename(oldPath, newPath, (err) => {
if (err) {
res.json({ ok: false });
console.log(err);
} else {
res.json({ ok: true });
}
});
});

app.listen(port, () => {
console.log("[Server] localhost:" + port);
});
//...
module.exports = app;

至此,我们的上传功能也搭建完毕。

五、程序运行:

上述操作全部完成后,因为编译器已经帮我们配好了运行环境(真的是省了一大步的事),所以我们只需要点击右上角的绿色小箭头:

运行:

这时我们的服务器已经启动了,打开chrome,输入http://localhost:8000/ 就可以看到我们的界面了,然后选择文件后点击上传,进度显示100%就代表上传成功:

控制台返回给我们图片的相关信息:

可以看到图片从1.3M压缩到了33.33KB,同时存放路径为uploads的文件夹;

接下来到我们定义好的存图片的地方:

然后,然后就开心的玩这个东西了,😝。

其实express也可以通过裁剪的方式进行图片压缩,不过那需要现将源文件上传至服务器(还是会浪费流量,而且加载速度也会受影响);使用canvas先在本地进行图片压缩,然后上传服务器,这是目前主流的做法。本项目需要注意的点在于压缩好后的图片只是存在的标签下,而上传的需要是文件,这就需要用dataURLtoFile()这一个函数将图片进行转化,再对文件进行上传。否则将这一部分代码改为:

1
2
3
...
formData.append('test-upload', file);
...

上传的仍然是未被压缩的源文件。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×