手把手教你实现一个图片压缩工具(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

微信公众号:前端历劫之路