vue + element-UI 图片压缩canvas【详解】(含完整demo)

简介: vue + element-UI 图片压缩canvas【详解】(含完整demo)

最终效果

原理详解

1.图片压缩参数

压缩图片的大小主要通过两种方式:

  1. 改变图片的尺寸–缩小宽度和高度
  2. 降低图片的清晰度

此demo中可以输入相关参数,配置方法如下:

  • 未设置宽度和高度时,压缩时将保留图片的宽度和高度
  • 仅设置宽度或仅设置高度时,压缩时将保留图片的宽高比,自动计算出对应的高度或宽度
  • 图片清晰度最小值为0.01,此时图片清晰度损失最大,可能会很模糊
  • 图片清晰度最大值为1,压缩时将保留图片的清晰度
  • 压缩后,若图片大小变大,则可以指定更小的宽度或高度,或调小清晰度

2.文件上传组件 el-upload

  1. 必须要配文件上传接口action,即便此demo中为""
  2. 通常文件上传都需要专门配请求头 headers
  3. 文件上传前的文件类型校验、文件大小限制、以及此处的图片压缩等,都在before-upload中进行,因图片压缩需要耗费时间,此处需使用异步回调,即 return new Promise,无需异步回调的情况,return true 即可。
  4. before-upload的参数file为文件对象,内含size文件大小,name文件名称,type文件类型等信息,通过 URL.createObjectURL(file) 可转换为图片的src,不过必须要用图片对象才能接收,即:
 // 获取压缩前图片的信息
 this.originalImg = new Image();
 this.originalImg.src = URL.createObjectURL(file);
 this.originalImg.size = (file.size / 1024 / 1024).toFixed(2);
  1. 图片上传成功后,会触发 on-success回调函数,以便获取上传接口返回的信息。(本demo没有上传接口,不会触发on-success回调函数)

3.图片压缩原理

本案例中使用的canvas对图片进行压缩,详见 compressUpload 函数:

  1. 创建了一个2d的canvas节点对象
  2. 根据原图片大小和图片压缩配置确定压缩后图片的宽和高
  3. 【fillRect 】在canvas画布上绘制填充的矩形
  4. 【drawImage】将原图片按压缩后的尺寸大小绘制在canvas画布中
  5. 【toDataURL】根据配置的清晰度,将图片转化为base64格式
  6. 通过【dataURItoBlob】函数,将base64格式的图片转换为Blob对象,以便传给后端。

4.图片信息的获取

原图片信息的获取(在before-upload中获取)

 // 获取压缩前图片的信息
 this.originalImg = new Image();
 this.originalImg.src = URL.createObjectURL(file);
 this.originalImg.size = (file.size / 1024 / 1024).toFixed(2);

压缩后图片信息的获取(在compressUpload中通过图片的base64格式获取)

// 获取压缩后图片的信息
this.compressedImg = new Image();
this.compressedImg.src = compressData;
this.compressedImg.height = height;
this.compressedImg.width = width;
this.compressedImg.size = (blobImg.size / 1024 / 1024).toFixed(2);

5.图片的查看

使用了el-dialog的全屏模式fullscreen,以便展示超出可视区域的图片

6.图片的下载

创建一个超链接a标签,通过js触发点击事件dispatchEvent,实现图片的下载。

    // 下载图片
    downloadImg(imgSrc, imgName) {
      let a = document.createElement("a");
      let event = new MouseEvent("click");
      // 自定义下载后图片的名称
      a.download = imgName;
      a.href = imgSrc;
      a.dispatchEvent(event);
    },

完整代码

<template>
  <div class="container">
    <h2>图片压缩</h2>
    <h3>1.设置图片压缩参数</h3>
    <el-collapse>
      <el-collapse-item title="图片压缩参数的配置方法【必看】">
        <ul>
          <li>未设置宽度和高度时,压缩时将保留图片的宽度和高度</li>
          <li>
            仅设置宽度或仅设置高度时,压缩时将保留图片的宽高比,自动计算出对应的高度或宽度
          </li>
          <li>
            图片清晰度最小值为0.01,此时图片清晰度损失最大,可能会很模糊
          </li>
          <li>图片清晰度最大值为1,压缩时将保留图片的清晰度</li>
          <li>
            压缩后,若图片大小变大,则可以指定更小的宽度或高度,或调小清晰度
          </li>
        </ul>
      </el-collapse-item>
    </el-collapse>
    <br />
    <el-form
      ref="Ref_form"
      :model="compressConfig"
      label-width="140px"
      size="mini"
      style="width:400px"
    >
      <el-form-item
        label="压缩后的图片宽度:"
        prop="width"
        :rules="[
          {
            pattern: /^[1-9]\d*$/,
            message: '请输入大于0的整数!',
            trigger: 'change',
          },
        ]"
      >
        <el-input
          clearable
          placeholder="请输入大于0的整数!"
          v-model.number="compressConfig.width"
        >
          <template slot="append">px</template>
        </el-input>
      </el-form-item>

      <el-form-item
        label="压缩后的图片高度:"
        prop="height"
        :rules="[
          {
            pattern: /^[1-9]\d*$/,
            message: '请输入大于0的整数!',
            trigger: 'change',
          },
        ]"
      >
        <el-input
          clearable
          placeholder="请输入大于0的整数!"
          v-model="compressConfig.height"
        >
          <template slot="append">px</template>
        </el-input>
      </el-form-item>

      <el-form-item label="图片清晰度:">
        <el-slider
          :show-tooltip="false"
          :max="1"
          :min="0.01"
          v-model="compressConfig.rate"
          show-input
          :step="0.01"
          input-size="mini"
        >
        </el-slider>
      </el-form-item>
    </el-form>
    <h3>2.点击上传一张图片</h3>
    <el-upload
      class="uploader"
      :style="myStyle"
      :action="uploadApiURL"
      :headers="uploadHeaders"
      :before-upload="beforeUpload"
      :on-success="uploadSuccess"
    >
      <img v-if="imageUrl" :src="imageUrl" class="myImage" :style="myStyle" />
      <i v-else class="el-icon-plus uploader-icon" :style="myStyle"></i>
    </el-upload>

    <div v-if="imageUrl" class="infoRow">
      <div>
        <h3>
          压缩前
          <el-button
            @click="openImgWin('origin')"
            size="mini"
            type="primary"
            plain
            >查看图片</el-button
          >
          <el-button
            @click="downloadImg(originalImg.src, '原图片')"
            size="mini"
            type="primary"
            plain
            >下载图片</el-button
          >
        </h3>
        <ul>
          <li>图片大小:{{ originalImg.size }} M</li>
          <li>图片宽度:{{ originalImg.width }} px</li>
          <li>图片高度:{{ originalImg.height }} px</li>
        </ul>
      </div>

      <div>
        <h3>
          压缩后
          <el-button
            @click="openImgWin('compressed')"
            size="mini"
            type="primary"
            plain
            >查看图片</el-button
          >
          <el-button
            @click="downloadImg(compressedImg.src, '压缩后的图片')"
            size="mini"
            type="primary"
            plain
            >下载图片</el-button
          >
        </h3>
        <ul>
          <li>图片大小:{{ compressedImg.size }} M</li>
          <li>图片宽度:{{ compressedImg.width }} px</li>
          <li>图片高度:{{ compressedImg.height }} px</li>
        </ul>
      </div>
    </div>

    <el-dialog
      title="图片预览"
      :visible.sync="showImg"
      v-if="showImg"
      fullscreen
    >
      <div v-if="imgMark === 'origin'">
        <ul>
          <li>图片大小:{{ originalImg.size }} M</li>
          <li>图片宽度:{{ originalImg.width }} px</li>
          <li>图片高度:{{ originalImg.height }} px</li>
        </ul>

        <img :src="originalImg.src" alt="原图片" />
      </div>

      <div v-if="imgMark === 'compressed'">
        <ul>
          <li>图片大小:{{ compressedImg.size }} M</li>
          <li>图片宽度:{{ compressedImg.width }} px</li>
          <li>图片高度:{{ compressedImg.height }} px</li>
        </ul>
        <img :src="compressedImg.src" alt="压缩后的图片" />
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 压缩配置
      compressConfig: {
        rate: 0.5,
      },
      // 图片标识
      imgMark: "",
      // 是否展示图片弹窗
      showImg: false,
      // 原图片
      originalImg: {},
      // 压缩后的图片
      compressedImg: {},
      //上传文件的接口地址
      uploadApiURL: "",
      //上传文件的请求头--按接口需求设置
      uploadHeaders: {},
      // 上传容器中显示图片
      imageUrl: "",

      // 上传容器的尺寸
      myStyle: {
        width: "100px",
        height: "100px",
        "line-height": "100px",
      },
    };
  },
  methods: {
    // 下载图片
    downloadImg(imgSrc, imgName) {
      let a = document.createElement("a");
      let event = new MouseEvent("click");
      // 自定义下载后图片的名称
      a.download = imgName;
      a.href = imgSrc;
      a.dispatchEvent(event);
    },
    // 打开弹窗——查看图片
    openImgWin(mark) {
      this.imgMark = mark;
      this.showImg = true;
    },
    // 上传图片前校验压缩参数,并进行图片压缩
    beforeUpload(file) {
      this.$refs.Ref_form.validate((valid) => {
        if (valid) {
          // 获取压缩前图片的信息
          this.originalImg = new Image();
          this.originalImg.src = URL.createObjectURL(file);
          this.originalImg.size = (file.size / 1024 / 1024).toFixed(2);

          let _this = this;
          return new Promise((resolve, reject) => {
            let image = new Image();
            image.src = URL.createObjectURL(file);
            image.onload = () => {
              let resultBlob = "";
              // 压缩图片

              resultBlob = _this.compressUpload(
                image,
                file,
                this.compressConfig
              );
              resolve(resultBlob);
            };
            image.onerror = (err) => {
              reject(err);
            };
          });
        } else {
          this.$message.error("图片压缩参数不符合规范,请修改后重试!");

          return;
        }
      });
    },

    /* 图片压缩-canvas压缩 */
    compressUpload(image, file, compressConfig) {
      let canvas = document.createElement("canvas");
      let ctx = canvas.getContext("2d");
      let width = compressConfig.width || image.width;
      let height = compressConfig.height || image.height;

      // 只设置宽度时,等比计算高度
      if (compressConfig.width && !compressConfig.height) {
        height = (compressConfig.width / image.width) * image.height;
      }
      // 只设置高度时,等比计算宽度
      if (compressConfig.height && !compressConfig.width) {
        width = (compressConfig.height / image.height) * image.width;
      }

      canvas.width = width;
      canvas.height = height;
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(image, 0, 0, width, height);

      // 进行最小压缩0.1
      let compressData = canvas.toDataURL(
        file.type || "image/jpeg",
        compressConfig.rate || 0.1
      );
      this.imageUrl = compressData;

      // base64转Blob
      let blobImg = this.dataURItoBlob(compressData);

      // 获取压缩后图片的信息
      this.compressedImg = new Image();
      this.compressedImg.src = compressData;
      this.compressedImg.height = height;
      this.compressedImg.width = width;
      this.compressedImg.size = (blobImg.size / 1024 / 1024).toFixed(2);

      return blobImg;
    },

    /* 图片格式转换——base64转Blob对象 */
    dataURItoBlob(data) {
      let byteString;
      if (data.split(",")[0].indexOf("base64") >= 0) {
        byteString = data.split(",")[1];
      } else {
        byteString = unescape(data.split(",")[1]);
      }
      let mimeString = data
        .split(",")[0]
        .split(":")[1]
        .split(";")[0];
      let ia = new Uint8Array(byteString.length);
      for (let i = 0; i < byteString.length; i += 1) {
        ia[i] = byteString.charCodeAt(i);
      }
      return new Blob([ia], { type: mimeString });
    },

    // 此处上传接口不可用时,此方法不会被调用
    uploadSuccess(res, file) {},
  },
};
</script>

<style scoped>
.uploader {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.uploader:hover {
  border-color: #409eff;
}
.uploader-icon {
  font-size: 28px;
  color: #8c939d;
  text-align: center;
}
.myImage {
  display: block;
  object-fit: cover;
}
.infoRow {
  display: flex;
  justify-content: space-between;
}
.container {
  padding-left: 30px;
  position: fixed;
  z-index: 3000;
  width: 500px;
}
</style>

测试图片

 

目录
相关文章
|
2月前
|
JavaScript
Ant Design Vue UI框架的基础使用,及通用后台管理模板的小demo【简单】
这篇文章介绍了如何使用Ant Design Vue UI框架创建一个简单的后台管理模板,包括创建Vue项目、安装和使用ant-design-vue、以及编写后台管理通用页面的代码和样式。
Ant Design Vue UI框架的基础使用,及通用后台管理模板的小demo【简单】
|
3月前
|
JavaScript
vue element-ui 中el-message重复弹出问题解决 el-message重复弹出解决办法
vue element-ui 中el-message重复弹出问题解决 el-message重复弹出解决办法
233 49
|
30天前
|
JavaScript 索引
Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案
Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案
108 0
|
3月前
|
JavaScript 前端开发 安全
[译] 在 Vue 组件中分离 UI 和业务逻辑。
[译] 在 Vue 组件中分离 UI 和业务逻辑。
|
3月前
|
开发者 图形学 前端开发
绝招放送:彻底解锁Unity UI系统奥秘,五大步骤教你如何缔造令人惊叹的沉浸式游戏体验,从Canvas到动画,一步一个脚印走向大师级UI设计
【8月更文挑战第31天】随着游戏开发技术的进步,UI成为提升游戏体验的关键。本文探讨如何利用Unity的UI系统创建美观且功能丰富的界面,包括Canvas、UI元素及Event System的使用,并通过具体示例代码展示按钮点击事件及淡入淡出动画的实现过程,助力开发者打造沉浸式的游戏体验。
95 0
|
3月前
|
JavaScript 前端开发
Vue实现Element UI框架的自定义输入框或下拉框在输入时对列表选项进行过滤,以及右键列表选项弹出菜单进行删除
本文介绍了如何在Vue框架结合Element UI库实现自定义输入框或下拉框,在输入时对列表选项进行过滤,并支持右键点击列表选项弹出菜单进行删除的功能。
75 0
|
3月前
|
JavaScript 容器
Vue+Element UI
该博客文章介绍了如何在Vue中集成Element UI来构建后台管理系统的左侧菜单,包括使用`el-menu`、`el-submenu`和`el-menu-item`等组件,并通过Vue router动态构建菜单项及其路由设置。
|
4月前
|
JavaScript
vue + element UI 表单中内嵌自定义组件的表单校验触发方案
vue + element UI 表单中内嵌自定义组件的表单校验触发方案
152 5
|
4月前
|
JavaScript API
【Element-UI】vue使用 this.$confirm区分取消与关闭,vue给this.$confirm设置多个按钮
【Element-UI】vue使用 this.$confirm区分取消与关闭,vue给this.$confirm设置多个按钮
570 0
|
4月前
|
JavaScript BI UED
vue + element UI【实战】打字闯关(含按键监听、按键音效、字符匹配、动态样式、结果判定、数据统计、音效获取和剪辑等实用技巧)
vue + element UI【实战】打字闯关(含按键监听、按键音效、字符匹配、动态样式、结果判定、数据统计、音效获取和剪辑等实用技巧)
49 0