手把手教你实现一个图片压缩工具(Vue与Node的完美配合)
前言
图片压缩对于我们日常生活来讲,是非常实用的一项功能。有时我们会在在线图片压缩网站上进行压缩,有时会在电脑下软件进行压缩。那么我们能不能用前端的知识来自己实现一个图片压缩工具呢?答案是有的。
效果展示
原图片大小:82KB
压缩后的图片大小:17KB
测试
是不是特别good!!!看到上面的压缩后的图片,可能你还会质疑图片的清晰度,那么看下面(第一张图为压缩后的图片):
教程
这么好的工具,那我们来看看怎么用代码实现它。首先你可能需要一些Vue.js和Node.js的基础,另外你可能还需要一点对知识的渴望~ 哈哈哈。
话不多说,我们来上干货。
前台搭建
<template>
<div class="face">
<label for="file" class="inputlabelBox">
<input
type="file"
ref="pic"
id="file"
name="face"
accept="image/*"
capture="camera"
:style="{ display: 'none' }"
@change="handleClick"
/>
<div class="upload">上传图片</div>
</label>
<div class="imgbox" v-show="imgsrc != ''">
<img src id="imgs" alt />
</div>
<div>
<p class="upload" @click="keepImg" v-show="imgsrc != ''">确定</p>
</div>
</div>
</template>
<script>
import EXIF from "exif-js";
export default {
name: "imgzip",
data() {
return {
imgsrc: "",
};
},
methods: {
// 上传图片
handleClick() {
if (this.$refs.pic.files[0]) {
// this.fileToBase64(this.$refs.pic.files[0]).then((res) => {
// this.imgsrc = res;
// });
this.rotateImg(this.$refs.pic.files[0]).then((res) => {
this.imgsrc = res;
});
}
},
// 压缩和图片旋转
rotateImg(imgFile) {
return new Promise((resolve) => {
EXIF.getData(imgFile, function () {
let exifTags = EXIF.getAllTags(this);
let reader = new FileReader();
reader.readAsDataURL(imgFile);
reader.onload = (e) => {
let imgData = e.target.result;
document.querySelector("#imgs").src = e.target.result;
// 8 表示 顺时针转了90
// 3 表示 转了 180
// 6 表示 逆时针转了90
if (
exifTags.Orientation == 8 ||
exifTags.Orientation == 3 ||
exifTags.Orientation == 6
) {
//翻转
//获取原始图片大小
const img = new Image();
img.src = imgData;
img.onload = function () {
let cvs = document.createElement("canvas");
let ctx = cvs.getContext("2d");
//如果旋转90
if (exifTags.Orientation == 8 || exifTags.Orientation == 6) {
cvs.width = img.height;
cvs.height = img.width;
} else {
cvs.width = img.width;
cvs.height = img.height;
}
if (exifTags.Orientation == 6) {
//原图逆时针转了90, 所以要顺时针旋转90
ctx.rotate((Math.PI / 180) * 90);
ctx.drawImage(
img,
0,
0,
img.width,
img.height,
0,
-img.height,
img.width,
img.height
);
}
if (exifTags.Orientation == 3) {
//原图逆时针转了180, 所以顺时针旋转180
ctx.rotate((Math.PI / 180) * 180);
ctx.drawImage(
img,
0,
0,
img.width,
img.height,
-img.width,
-img.height,
img.width,
img.height
);
}
if (exifTags.Orientation == 8) {
//原图顺时针旋转了90, 所以要你时针旋转90
ctx.rotate((Math.PI / 180) * -90);
ctx.drawImage(
img,
0,
0,
img.width,
img.height,
-img.width,
0,
img.width,
img.height
);
}
let data = cvs.toDataURL("image/jpeg"); // 输出压缩后的base64
let arr = data.split(","),
mime = arr[0].match(/:(.*?);/)[1], // 转成blob
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let files = new window.File(
[new Blob([u8arr], { type: mime })],
"test.jpeg",
{ type: "image/jpeg" }
);
resolve(files);
};
} else {
let image = new Image(); //新建一个img标签(还没嵌入DOM节点)
image.src = e.target.result;
image.onload = function () {
let canvas = document.createElement("canvas"), // 新建canvas
context = canvas.getContext("2d"),
imageWidth = image.width, //压缩后图片的大小
imageHeight = image.height,
data = "";
canvas.width = imageWidth;
canvas.height = imageHeight;
context.drawImage(image, 0, 0, imageWidth, imageHeight);
data = canvas.toDataURL("image/jpeg"); // 输出压缩后的base64
let arr = data.split(","),
mime = arr[0].match(/:(.*?);/)[1], // 转成blob
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let files = new window.File(
[new Blob([u8arr], { type: mime })],
"test.jpeg",
{ type: "image/jpeg" }
); // 转成file
resolve(files);
};
}
};
});
});
},
/*
fileToBase64(file) {
let that = this,
reader = new FileReader();
reader.readAsDataURL(file);
return new Promise((resolve, reject) => {
reader.onload = function (e) {
//这里是一个异步,所以获取数据不好获取在实际项目中,就用new Promise解决
if (this.result) {
let image = new Image(); //新建一个img标签(还没嵌入DOM节点)
image.src = e.target.result;
document.querySelector("#imgs").src = e.target.result;
image.onload = function () {
let canvas = document.createElement("canvas"), // 新建canvas
context = canvas.getContext("2d"),
imageWidth = image.width / 2, //压缩后图片的大小
imageHeight = image.height / 2,
data = "";
canvas.width = imageWidth;
canvas.height = imageHeight;
context.drawImage(image, 0, 0, imageWidth, imageHeight);
data = canvas.toDataURL("image/jpeg"); // 输出压缩后的base64
let arr = data.split(","),
mime = arr[0].match(/:(.*?);/)[1], // 转成blob
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let files = new window.File(
[new Blob([u8arr], { type: mime })],
"test.jpeg",
{ type: "image/jpeg" }
); // 转成file
resolve(files);
};
} else {
reject("err");
}
};
});
},
*/
// 保存图片
keepImg() {
// this.$emit("canvasToImage", this.imgsrc);
const fd = new FormData();
fd.append("file", this.imgsrc);
fetch("http://localhost:6300/upload", {
method: "post",
mode:"cors",
body:fd,
})
.then((response) => response.json())
.then((response) => {
if(response.success){
console.log(this.imgsrc);
const size = this.imgsrc.size<1024?this.imgsrc.size+"字节":Math.round(this.imgsrc.size/1024)+"KB";
console.log(size);
alert(`图片${response.name}${response.msg}!压缩后图片大小为:${size}。`);
}
})
.catch((err) => {
console.log(err);
});
},
},
};
</script>
<style scoped lang="less">
.upload {
display: inline-block;
background: #ffb90f;
color: white;
font-size: 16px;
text-align: center;
border-radius: 4px;
padding: 10px 30px;
margin-bottom: 20px;
}
.upload:hover {
filter: brightness(110%);
}
.upload:active {
filter: brightness(60%);
}
.imgbox {
text-align: center;
width: 60%;
margin: 0 auto;
img {
width: 100%;
height: 60vh;
object-fit: contain;
}
}
.face {
margin-top: 30px;
.container1 {
background: #000;
position: relative;
width: 580px;
height: 436px;
margin: 0 auto;
#canvas1 {
position: absolute;
}
video,
#canvas,
#canvas1 {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 581px;
height: 436px;
}
}
.btns {
padding: 10px;
button {
margin: 20px 20px 20px 0;
}
}
.tips {
font-size: 26px;
color: #666;
margin: 10px 0;
line-height: 48px;
}
.imgs {
p {
font-size: 28px;
}
}
}
</style>
我在这里实现了一个Vue组件(所以你得知道Vue是什么?组件又是什么?)。知道这些还不够,你还要知道怎么从依赖库下载依赖,这里需要另外下载的依赖是exif-js。
一个JavaScript库,用于从图像文件中读取EXIF元数据。
您可以通过图像或文件输入元素在浏览器中的图像上使用它。EXIF和IPTC元数据均被检索。该软件包还可以在AMD或CommonJS环境中使用。
备注;使用exif.js依赖的作用是 为了防止在IOS系统中拍照上传图片旋转90度问题。
后台搭建
const Koa = require('koa');// koa框架
const Router = require('koa-router');// 接口必备
const cors = require('koa2-cors'); // 跨域必备
const fs = require('fs'); // 文件系统
const koaBody = require('koa-body'); //文件保存库
const path = require('path'); // 路径
let app = new Koa();
let router = new Router();
// 跨域
app.use(cors({
origin: function (ctx) {
return ctx.header.origin;
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
maxAge: 5,
credentials: true,
withCredentials: true,
allowMethods: ['GET', 'POST', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}));
//上传文件限制
app.use(koaBody({
multipart: true,
formidable: {
maxFileSize: 1000 * 1024 * 1024 // 设置上传文件大小最大限制,默认10M
}
}));
// 上传图片
router.post('/upload', async (ctx, next) => {
if (ctx.request.files.file) {
var file = ctx.request.files.file;
// 创建可读流
var reader = fs.createReadStream(file.path);
// 修改文件的名称
var myDate = new Date();
var newFilename = myDate.getTime() + '.' + file.name.split('.')[1];
var targetPath = path.join(__dirname, './images/') + `${newFilename}`;
//创建可写流
var upStream = fs.createWriteStream(targetPath);
// 可读流通过管道写入可写流
reader.pipe(upStream);
ctx.body = {
success: true,
name: newFilename,
msg:"压缩成功"
};
}
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(6300)
console.log('服务器运行中')
后台的逻辑其实很简单,就是实现一个接口,接收前台发来的文件,保存到本地目录上以及返回给前台状态。
作者:Vam的金豆之路
主要领域:前端开发
我的微信:maomin9761
微信公众号:前端历劫之路