真实的图片裁剪组件V2.0(宽高及大小)

效果展示

裁剪前:

        宽高:1920px * 1080px;大小:2.38MB

裁剪后:

        宽高:140px * 140px;大小:18.7KB

前置条件

1.vue2 + elementUI

2.基于cropperjs二次封装,需引入cropperjs组件

npm install  cropperjs --save

使用方法

<!-- 按需引入或在main.js进行全局引入,自行实现,此处不做示例 -->

<!-- 以下是完整demo -->

<template>
  <div class="demo">
    <div class="title">* 单图版示例</div>
    <cropper ref="cropper" :width="150" :height="150" :prefix="$http.getFile" :url="url" :action="$http.uploadFile" :success="handleCropper"/>

    <div style="margin-top: 40px"></div>

    <div class="title">* 多图版示例</div>
    <multicropper ref="multicropper" :width="150" :height="150" :prefix="$http.getFile" :url="urls" :action="$http.uploadFile"
                  :success="handleMultiCropper" :num="3"/>
  </div>
</template>

<script>
export default {
  data() {
    return {
      //单图版结果
      url: "",
      //多图版结果
      urls: [],
    }
  },
  methods: {
    /**
     * 单图版裁剪完成
     * @param url
     */
    handleCropper(url) {
      this.url = url;
      console.log(url)
    },
    /**
     * 多图版裁剪完成
     * @param url
     */
    handleMultiCropper(url) {
      this.urls = url;
      console.log(url)
    },
  }
}
</script>
<style scoped lang="less">
.demo {
  .title {
    color: #d62120;
    margin-bottom: 10px;
  }
}
</style>

参数说明

参数名类型是否必传默认值含义
width整数目标尺寸宽度(即裁剪后)
height整数0目标尺寸高度(即裁剪后),如果不传,将根据上传的图片实际宽高按比例缩放
prefix字符串空字符串图片链接统一前缀,用于相对路径场景
url字符串默认展示的图片链接,用于修改图片场景
num整数多图版必传限制上传图片的数量
name字符串file上传文件后端接口需要接收的参数名,需询问后端
size整数5120限制上传图片的尺寸大小,单位KB
success函数上传成功后的回调函数
action字符串上传图片的后端接口地址

 以下为自定义组件完整代码

* 单图版

<template>
  <div class="cropper">
    <el-image class="upload" :src="require('./upload.png')" @click="chooseImage" v-if="isBlank(resultUrl) && isBlank(url)"></el-image>
    <div v-else>
      <img class="result-image" :src="prefix + (isBlank(url) ? resultUrl : url)" style="object-fit: contain;" alt="Image" @click="chooseImage"/>
    </div>

    <div class="tip">尺寸:{{ width }}px * {{ height > 0 ? (height + "px") : "按比例计算" }}</div>

    <el-dialog title="裁剪压缩" :visible.sync="uploading" :width="(areaW + 150) + 'px'" :show-close="false" :close-on-click-modal="false"
               :close-on-press-escape="false" :append-to-body="true">
      <div class="area flex-row-center">
        <div :style="'width: '+ (areaW + 100) +'px;max-height: '+ (areaH + 100) +'px;'">
          <img class="image" v-if="imageUrl && uploading && areaW > 0 && areaW > 0" ref="image" :src="imageUrl" @load="onImageLoad" alt="Image"/>
        </div>
      </div>
      <div slot="footer" class="flex-row-center">
        <el-button class="confirm" type="primary" @click="confirm" :loading="isLoading">裁剪并上传</el-button>
        <el-button class="cancel" type="plain" @click="init">取消上传</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>

import axios from "axios";

export default {
  props: {
    //图片宽度
    width: {
      type: Number,
      required: true
    },
    //图片高度
    height: {
      type: Number,
      required: false,
      default: 0
    },
    //图片链接统一前缀
    prefix: {
      type: String,
      required: false,
      default: "",
    },
    //默认图片链接
    url: {
      type: String,
      required: false
    },
    //上传文件后端接收的参数名
    name: {
      type: String,
      required: false,
      default: "file",
    },
    //图片大小限制(单位kb)
    size: {
      type: Number,
      required: false,
      default: 5120,
    },
    //裁剪并上传
    success: {
      type: Function,
      required: true
    },
    //图片上传地址
    action: {
      type: String,
      required: true
    },
  },
  watch: {
    url(cur) {
      //初始化裁剪对象
      this.init();
      this.resultUrl = this.isBlank(cur) ? "" : cur;
    },
    width(cur) {
      this.realW = cur;
    },
    height(cur) {
      this.realH = cur;
    },
  },
  data() {
    return {
      //裁剪对象
      cropper: null,
      //裁剪组件宽度
      areaW: 0,
      //裁剪组件高度
      areaH: 0,
      //最终图片宽度
      realW: 0,
      //最终图片高度
      realH: 0,
      //裁剪时的临时图片链接
      imageUrl: "",
      //裁剪后的图片链接
      resultUrl: "",
      //文件名
      fileName: "",
      //是否指定高度
      isSetH: true,
      //是否正在上传
      uploading: false,
      //是否正在裁剪
      isLoading: false,
    };
  },
  created() {
    this.isSetH = this.height > 0;
    this.realW = this.width;
    this.realH = this.height;
    //初始化裁剪对象
    this.init();
  },
  methods: {
    /**
     * 初始化裁剪对象
     */
    init() {
      this.imageUrl = "";
      this.fileName = "";
      if (this.cropper) {
        this.cropper.destroy();
        this.cropper = null;
      }
      this.uploading = false;
    },
    /**
     * 计算最大宽高
     */
    getMaxSize() {
      let maxW = 800;
      let maxH = 400;

      let areaW = 0;
      let areaH = 0;

      let wRate = this.realW / maxW;
      let hRate = this.realH / maxH;

      if (wRate > hRate) {
        areaW += maxW;
        areaH += Math.round(maxW / this.realW * this.realH);
      } else {
        areaW += Math.round(maxH / this.realH * this.realW);
        areaH += maxH;
      }

      this.areaW = areaW;
      this.areaH = areaH;
    },
    /**
     * 选择一张图片
     */
    chooseImage() {
      //初始化裁剪对象
      this.init();
      const input = document.createElement("input");
      input.type = "file";
      input.accept = "image/jpeg,image/png";
      input.addEventListener("change", (event) => {
        const file = event.target.files[0];
        if (file) {
          if (file.size > 1024 * this.size) {
            this.$message.warning("图片大小不能超过" + this.formatSize());
            return;
          }

          // 获取文件名
          this.fileName = file.name;
          const reader = new FileReader();
          reader.onload = (e) => {
            this.imageUrl = e.target.result;
            if (!this.isSetH) {
              //如果未指定图片高度,将获取上传的图片宽高来计算比例
              const image = new Image();
              image.onload = () => {
                const width = image.width;
                const height = image.height;
                this.realH = Math.round(this.width / width * height);
                this.getMaxSize();
              };
              image.src = e.target.result;
            }
          };
          reader.readAsDataURL(file);
          this.getMaxSize();
          this.uploading = true;
        }
      });
      input.click();
    },
    /**
     * 格式化尺寸
     */
    formatSize() {
      if (this.size < 1024) {
        return `${this.size.toFixed(2)} KB`;
      } else {
        const sizeInMB = this.size / 1024;
        return `${sizeInMB.toFixed(2)} MB`;
      }
    },
    /**
     * 当图片加载完成后,加载裁剪对象
     */
    onImageLoad() {
      if (this.cropper) {
        this.cropper.destroy();
      }
      this.cropper = new this.$cropper(this.$refs.image, {
        aspectRatio: this.areaW / this.areaH,// 裁剪区域的宽高比
        viewMode: 1, // 视图模式,1 表示限制裁剪区域在图片内
        dragMode: "move",//定义裁剪器的拖动模式
        autoCropArea: 0,// 自动裁剪区域的大小,0 到 1 之间
        cropBoxResizable: false, // 禁止调整裁剪区域的大小
        minCanvasWidth: this.areaW, // 设置图片最小宽度
        minCanvasHeight: this.areaH, // 设置图片最小高度
        minContainerWidth: (this.areaW + 50), // 设置画布的最小宽度
        minContainerHeight: (this.areaH + 50), // 设置画布的最小高度
        wheelZoomRatio: 0.1,//缩放图片时的比例
        cropBoxMovable: false, // 禁止裁剪框移动
        toggleDragModeOnDblclick: false,//是否允许双击切换图片容器拖拽模式("crop"和"move")
        ready: () => {
          const containerData = this.cropper.getContainerData();
          const cropBoxWidth = this.areaW;
          const cropBoxHeight = this.areaH;
          const cropBoxLeft = (containerData.width - cropBoxWidth) / 2;
          const cropBoxTop = (containerData.height - cropBoxHeight) / 2;

          this.cropper.setCropBoxData({
            left: cropBoxLeft,
            top: cropBoxTop,
            width: cropBoxWidth,
            height: cropBoxHeight,
          });
        },
      });
    },
    /**
     * 裁剪并上传
     */
    confirm() {
      if (!this.cropper) {
        this.$message.warning("请先选择一张图片");
        return;
      }

      this.isLoading = true;

      //先压缩至指定的尺寸
      const canvas = this.cropper.getCroppedCanvas({
        width: this.realW,
        height: this.realH,
        fillColor: "transparent", // 设置导出图片的背景颜色为透明
        imageSmoothingQuality: "high",
      });

      //检查图片类型
      let mimeType = this.getImageMimeType();
      if (mimeType == null) {
        this.$message.warning("仅支持上传jpeg和png格式的图片");
        this.isLoading = false;
        return;
      }

      //转换为blob对象,上传至图片服务器
      canvas.toBlob((blob) => {
        //执行上传操作
        this.upload(blob, this.fileName, result => {
          //判断是否返回了上传后的图片链接
          if (this.isBlank(result.message)) {
            this.$message.warning("图片裁剪失败,请刷新页面后重试")
            return;
          }
          //上传成功,初始化裁剪对象,并将图片地址传递给父页面
          this.init();
          this.success(result.message);
          this.isLoading = false;
          this.resultUrl = result.message;
        });
      }, mimeType, 1);
    },
    /**
     * 获取图片类型
     */
    getImageMimeType() {
      // 获取文件扩展名
      const fileExtension = this.fileName.split('.').pop();
      let mimeType;
      switch (fileExtension.toLowerCase()) {
        case "jpg":
        case "jpeg":
          mimeType = "image/jpeg";
          break;
        case "png":
          mimeType = "image/png";
          break;
        default:
          mimeType = null;
      }
      return mimeType;
    },
    /**
     * 判断是否为空
     */
    isBlank(obj) {
      if (obj instanceof Date) {
        return false;
      }
      if (typeof obj === "number" && !isNaN(obj)) {
        return false;
      }
      if (!obj || obj === "undefined" || obj === "null" || obj === "" || obj === {} || obj === [] || obj.length === 0) {
        return true;
      }
      return Object.keys(obj).length < 1;
    },
    /**
     * 上传文件
     */
    upload(file, fileName = null, success) {
      let formdata = new FormData();
      if (fileName != null) {
        formdata.append(this.name, file, fileName);
      } else {
        formdata.append(this.name, file);
      }
      axios.post(this.action, formdata, {"Content-Type": "multipart/form-data"}).then(response => {
        let result = response.data;
        if (result.flag !== 200) {
          this.$message.warning("上传出错,原因是:" + result.message);
          return;
        }
        if (success) {
          success(result);
        }
      }).catch((error) => {
        this.$message.warning("内部错误,原因是:" + error);
      })
    },
  },
};
</script>

<style lang="less" scoped>
.cropper {
  line-height: 12px !important;

  .upload {
    width: 80px;
    height: 80px;
    cursor: pointer;
  }

  .result-image {
    width: 80px;
    height: 80px;
    border-radius: 2px;
    border: 1px dashed #bbb;
  }

  .tip {
    color: #bbb;
    font-size: 12px;
    margin: 5px 0;
  }

  .area {
    width: 100%;

    .image {
      margin-top: 10px;
    }

    .buttons {
      margin-top: 10px;
    }
  }

}
</style>

* 多图版

<template>
  <div class="cropper">
    <div>
      <img class="result-image" v-for="(item,index) in resultUrl" :key="index" :src="prefix +item"
           style="object-fit: contain;" alt="Image" @click="chooseImage(index)" @contextmenu.prevent="deleteImage(index)"/>
      <el-image class="upload" :src="require('./upload.png')" @click="chooseImage(-1)" v-if="resultUrl.length < num"></el-image>
    </div>

    <div class="tip">可上传 {{ num }} 张图片,尺寸:{{ realW }}px * {{ isSetH ? (realH + "px") : "按比例计算" }},右键点击图片可删除</div>

    <el-dialog title="裁剪压缩" :visible.sync="uploading" :width="(areaW + 150) + 'px'" :show-close="false" :close-on-click-modal="false"
               :close-on-press-escape="false" :append-to-body="true">
      <div class="area flex-row-center">
        <div :style="'width: '+ (areaW + 100) +'px;max-height: '+ (areaH + 100) +'px;'">
          <img class="image" v-if="imageUrl && uploading && areaW > 0 && areaW > 0" ref="image" :src="imageUrl" @load="onImageLoad" alt="Image"/>
        </div>
      </div>
      <div slot="footer" class="flex-row-center">
        <el-button class="confirm" type="primary" @click="confirm" :loading="isLoading">裁剪并上传</el-button>
        <el-button class="cancel" type="plain" @click="init">取消上传</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>

import axios from "axios";

export default {
  props: {
    //图片宽度
    width: {
      type: Number,
      required: true
    },
    //图片高度
    height: {
      type: Number,
      required: false,
      default: 0
    },
    //图片链接统一前缀
    prefix: {
      type: String,
      required: false,
      default: "",
    },
    //图片数量
    num: {
      type: Number,
      required: true,
    },
    //默认图片链接数组
    url: {
      type: Array,
      required: false
    },
    //上传文件后端接收的参数名
    name: {
      type: String,
      required: false,
      default: "file",
    },
    //图片大小限制(单位kb)
    size: {
      type: Number,
      required: false,
      default: 5120,
    },
    //裁剪并上传
    success: {
      type: Function,
      required: true
    },
    //图片上传地址
    action: {
      type: String,
      required: true
    },
  },
  watch: {
    url(cur) {
      this.resultUrl = this.isBlank(cur) ? [] : cur.filter(item => !this.isBlank(item));
    },
    width(cur) {
      this.realW = cur;
    },
    height(cur) {
      this.realH = cur;
    },
  },
  data() {
    return {
      //裁剪对象
      cropper: null,
      //裁剪组件宽度
      areaW: 0,
      //裁剪组件高度
      areaH: 0,
      //最终图片宽度
      realW: 0,
      //最终图片高度
      realH: 0,
      //裁剪时的临时图片链接
      imageUrl: "",
      //裁剪后的图片链接数组
      resultUrl: [],
      //文件名
      fileName: "",
      //是否指定高度
      isSetH: true,
      //是否正在上传
      uploading: false,
      //是否正在裁剪
      isLoading: false,
      //更改的图片下标
      urlIndex: -1,
    };
  },
  created() {
    this.isSetH = this.height > 0;
    this.realW = this.width;
    this.realH = this.height;
    //初始化裁剪对象
    this.init();
    if (!this.isBlank(this.url)) {
      this.resultUrl = this.url.filter(item => !this.isBlank(item));
    }
  },
  methods: {
    /**
     * 初始化裁剪对象
     */
    init() {
      this.imageUrl = "";
      this.fileName = "";
      if (this.cropper) {
        this.cropper.destroy();
        this.cropper = null;
      }
      this.uploading = false;
    },
    /**
     * 计算最大宽高
     */
    getMaxSize() {
      let maxW = 800;
      let maxH = 400;

      let areaW = 0;
      let areaH = 0;

      let wRate = this.realW / maxW;
      let hRate = this.realH / maxH;

      if (wRate > hRate) {
        areaW += maxW;
        areaH += Math.round(maxW / this.realW * this.realH);
      } else {
        areaW += Math.round(maxH / this.realH * this.realW);
        areaH += maxH;
      }

      this.areaW = areaW;
      this.areaH = areaH;
    },
    /**
     * 选择一张图片
     */
    chooseImage(index) {
      this.urlIndex = index;

      //初始化裁剪对象
      this.init();
      const input = document.createElement("input");
      input.type = "file";
      input.accept = "image/jpeg,image/png";
      input.addEventListener("change", (event) => {
        const file = event.target.files[0];
        if (file) {
          if (file.size > 1024 * this.size) {
            this.$message.warning("图片大小不能超过" + this.formatSize());
            return;
          }

          // 获取文件名
          this.fileName = file.name;
          const reader = new FileReader();
          reader.onload = (e) => {
            this.imageUrl = e.target.result;
            if (!this.isSetH) {
              //如果未指定图片高度,将获取上传的图片宽高来计算比例
              const image = new Image();
              image.onload = () => {
                const width = image.width;
                const height = image.height;
                this.realH = Math.round(this.width / width * height);
                this.getMaxSize();
              };
              image.src = e.target.result;
            }
          };
          reader.readAsDataURL(file);
          this.getMaxSize();
          this.uploading = true;
        }
      });
      input.click();
    },
    /**
     * 格式化尺寸
     */
    formatSize() {
      if (this.size < 1024) {
        return `${this.size.toFixed(2)} KB`;
      } else {
        const sizeInMB = this.size / 1024;
        return `${sizeInMB.toFixed(2)} MB`;
      }
    },
    /**
     * 当图片加载完成后,加载裁剪对象
     */
    onImageLoad() {
      if (this.cropper) {
        this.cropper.destroy();
      }
      this.cropper = new this.$cropper(this.$refs.image, {
        aspectRatio: this.areaW / this.areaH,// 裁剪区域的宽高比
        viewMode: 1, // 视图模式,1 表示限制裁剪区域在图片内
        dragMode: "move",//定义裁剪器的拖动模式
        autoCropArea: 0,// 自动裁剪区域的大小,0 到 1 之间
        cropBoxResizable: false, // 禁止调整裁剪区域的大小
        minCanvasWidth: this.areaW, // 设置图片最小宽度
        minCanvasHeight: this.areaH, // 设置图片最小高度
        minContainerWidth: (this.areaW + 50), // 设置画布的最小宽度
        minContainerHeight: (this.areaH + 50), // 设置画布的最小高度
        wheelZoomRatio: 0.1,//缩放图片时的比例
        cropBoxMovable: false, // 禁止裁剪框移动
        toggleDragModeOnDblclick: false,//是否允许双击切换图片容器拖拽模式("crop"和"move")
        ready: () => {
          const containerData = this.cropper.getContainerData();
          const cropBoxWidth = this.areaW;
          const cropBoxHeight = this.areaH;
          const cropBoxLeft = (containerData.width - cropBoxWidth) / 2;
          const cropBoxTop = (containerData.height - cropBoxHeight) / 2;

          this.cropper.setCropBoxData({
            left: cropBoxLeft,
            top: cropBoxTop,
            width: cropBoxWidth,
            height: cropBoxHeight,
          });
        },
      });
    },
    /**
     * 右键删除图片
     */
    deleteImage(index) {
      this.$confirm("将删除该图片,是否确认?", "提示", {type: "warning"}).then(() => {
        this.resultUrl.splice(index, 1); // 删除指定下标的图片
      });
    },
    /**
     * 裁剪并上传
     */
    confirm() {
      if (!this.cropper) {
        this.$message.warning("请先选择一张图片");
        return;
      }

      this.isLoading = true;

      //先压缩至指定的尺寸
      const canvas = this.cropper.getCroppedCanvas({
        width: this.realW,
        height: this.realH,
        fillColor: "transparent", // 设置导出图片的背景颜色为透明
        imageSmoothingQuality: "high",
      });

      //检查图片类型
      let mimeType = this.getImageMimeType();
      if (mimeType == null) {
        this.$message.warning("仅支持上传jpeg和png格式的图片");
        this.isLoading = false;
        return;
      }

      //转换为blob对象,上传至图片服务器
      canvas.toBlob((blob) => {
        //执行上传操作
        this.upload(blob, this.fileName, result => {
          //判断是否返回了上传后的图片链接
          if (this.isBlank(result.message)) {
            this.$message.warning("图片裁剪失败,请刷新页面后重试")
            return;
          }
          //上传成功,初始化裁剪对象,并将图片地址传递给父页面
          this.init();
          this.isLoading = false;
          if (this.urlIndex === -1) {
            this.resultUrl.push(result.message);
          } else {
            this.resultUrl.splice(this.urlIndex, 1, result.message);
          }
          this.success(this.resultUrl);
        });
      }, mimeType, 1);
    },
    /**
     * 获取图片类型
     */
    getImageMimeType() {
      // 获取文件扩展名
      const fileExtension = this.fileName.split('.').pop();
      let mimeType;
      switch (fileExtension.toLowerCase()) {
        case "jpg":
        case "jpeg":
          mimeType = "image/jpeg";
          break;
        case "png":
          mimeType = "image/png";
          break;
        default:
          mimeType = null;
      }
      return mimeType;
    },
    /**
     * 判断是否为空
     */
    isBlank(obj) {
      if (obj instanceof Date) {
        return false;
      }
      if (typeof obj === "number" && !isNaN(obj)) {
        return false;
      }
      if (!obj || obj === "undefined" || obj === "null" || obj === "" || obj === {} || obj === [] || obj.length === 0) {
        return true;
      }
      return Object.keys(obj).length < 1;
    },
    /**
     * 上传文件
     */
    upload(file, fileName = null, success) {
      let formdata = new FormData();
      if (fileName != null) {
        formdata.append(this.name, file, fileName);
      } else {
        formdata.append(this.name, file);
      }
      axios.post(this.action, formdata, {"Content-Type": "multipart/form-data"}).then(response => {
        let result = response.data;
        if (result.flag !== 200) {
          this.$message.warning("上传出错,原因是:" + result.message);
          return;
        }
        if (success) {
          success(result);
        }
      }).catch((error) => {
        this.$message.warning("内部错误,原因是:" + error);
      })
    },
  },
};
</script>

<style lang="less" scoped>
.cropper {
  line-height: 12px !important;

  .upload {
    width: 80px;
    height: 80px;
    cursor: pointer;
    margin: 6px 0;
  }

  .result-image {
    width: 80px;
    height: 80px;
    border-radius: 2px;
    border: 1px dashed #bbb;
    margin: 5px;

    &:first-child {
      margin-left: 0 !important;
    }

    &:last-child {
      margin-right: 0 !important;
    }
  }

  .tip {
    color: #bbb;
    font-size: 12px;
    margin: 5px 0;
  }

  .area {
    width: 100%;

    .image {
      margin-top: 10px;
    }

    .buttons {
      margin-top: 10px;
    }
  }

}
</style>

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值