前言
图片压缩对于我们日常生活来讲,是非常实用的一项功能。有时我们会在在线图片压缩网站上进行压缩,有时会在电脑下软件进行压缩。那么我们能不能用前端的知识来自己实现一个图片压缩工具呢?答案是有的。
效果展示
原图片大小: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('服务器运行中')
后台的逻辑其实很简单,就是实现一个接口,接收前台发来的文件,保存到本地目录上以及返回给前台状态。
结语
谢谢你的浏览,如果还有需要优化的地方请及时留言哦~