手把手教你實現(xiàn)一個圖片壓縮工具(Vue與Node的完美配合)
前言
圖片壓縮對于我們?nèi)粘I顏碇v,是非常實用的一項功能。有時我們會在在線圖片壓縮網(wǎng)站上進行壓縮,有時會在電腦下軟件進行壓縮。那么我們能不能用前端的知識來自己實現(xiàn)一個圖片壓縮工具呢?答案是有的。
效果展示
原圖片大?。?2KB
壓縮后的圖片大?。?7KB
測試
是不是特別good?。?!看到上面的壓縮后的圖片,可能你還會質(zhì)疑圖片的清晰度,那么看下面(第一張圖為壓縮后的圖片):
教程
這么好的工具,那我們來看看怎么用代碼實現(xiàn)它。首先你可能需要一些Vue.js和Node.js的基礎(chǔ),另外你可能還需要一點對知識的渴望~ 哈哈哈。
話不多說,我們來上干貨。
前臺搭建
<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;
});
}
},
// 壓縮和圖片旋轉(zhuǎn)
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 表示 順時針轉(zhuǎn)了90
// 3 表示 轉(zhuǎn)了 180
// 6 表示 逆時針轉(zhuǎn)了90
if (
exifTags.Orientation == 8 ||
exifTags.Orientation == 3 ||
exifTags.Orientation == 6
) {
//翻轉(zhuǎn)
//獲取原始圖片大小
const img = new Image();
img.src = imgData;
img.onload = function () {
let cvs = document.createElement("canvas");
let ctx = cvs.getContext("2d");
//如果旋轉(zhuǎn)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) {
//原圖逆時針轉(zhuǎn)了90, 所以要順時針旋轉(zhuǎn)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) {
//原圖逆時針轉(zhuǎn)了180, 所以順時針旋轉(zhuǎn)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) {
//原圖順時針旋轉(zhuǎn)了90, 所以要你時針旋轉(zhuǎn)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], // 轉(zhuǎn)成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節(jié)點)
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], // 轉(zhuǎn)成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" }
); // 轉(zhuǎn)成file
resolve(files);
};
}
};
});
});
},
/*
fileToBase64(file) {
let that = this,
reader = new FileReader();
reader.readAsDataURL(file);
return new Promise((resolve, reject) => {
reader.onload = function (e) {
//這里是一個異步,所以獲取數(shù)據(jù)不好獲取在實際項目中,就用new Promise解決
if (this.result) {
let image = new Image(); //新建一個img標簽(還沒嵌入DOM節(jié)點)
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], // 轉(zhuǎn)成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" }
); // 轉(zhuǎn)成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+"字節(jié)":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>
我在這里實現(xiàn)了一個Vue組件(所以你得知道Vue是什么?組件又是什么?)。知道這些還不夠,你還要知道怎么從依賴庫下載依賴,這里需要另外下載的依賴是exif-js。
一個JavaScript庫,用于從圖像文件中讀取EXIF元數(shù)據(jù)。
您可以通過圖像或文件輸入元素在瀏覽器中的圖像上使用它。EXIF和IPTC元數(shù)據(jù)均被檢索。該軟件包還可以在AMD或CommonJS環(huán)境中使用。
備注;使用exif.js依賴的作用是 為了防止在IOS系統(tǒng)中拍照上傳圖片旋轉(zhuǎn)90度問題。
后臺搭建
const Koa = require('koa');// koa框架
const Router = require('koa-router');// 接口必備
const cors = require('koa2-cors'); // 跨域必備
const fs = require('fs'); // 文件系統(tǒng)
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 // 設(shè)置上傳文件大小最大限制,默認10M
}
}));
// 上傳圖片
router.post('/upload', async (ctx, next) => {
if (ctx.request.files.file) {
var file = ctx.request.files.file;
// 創(chuàng)建可讀流
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}`;
//創(chuàng)建可寫流
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('服務(wù)器運行中')
后臺的邏輯其實很簡單,就是實現(xiàn)一個接口,接收前臺發(fā)來的文件,保存到本地目錄上以及返回給前臺狀態(tài)。
作者:Vam的金豆之路
主要領(lǐng)域:前端開發(fā)
我的微信:maomin9761
微信公眾號:前端歷劫之路