Element-Ui结合canvas-select插件,实现上传图片类似PS钢笔工具的裁切

实现的功能就是图片上传,并且对上传的图片进行类似ps钢笔工具的裁切。裁切完成之后回显到el-upload并可传到后台。

 

 

 

 

 

 

<template>
  <div>
    <div class="img-upload-con">
      <el-upload
        action="#"
        list-type="picture-card"
        :auto-upload="false"
        :limit="5"
        ref="imgupload"
        :on-change="handleEditChange"
        :file-list="fileList"
      >
        <i slot="default" class="el-icon-plus"></i>
        <div slot="file" slot-scope="{ file }">
          <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
          <span class="el-upload-list__item-actions">
            <span class="el-upload-list__item-preview" @click="lassoImg(file)">
              <i class="el-icon-lasso"></i>
            </span>
            <span
              class="el-upload-list__item-delete"
              @click="handleRemove(file)"
            >
              <i class="el-icon-delete"></i>
            </span>
          </span>
        </div>
      </el-upload>
    </div>
    <lasso-tool ref="LassoTool"></lasso-tool>
  </div>
</template>
<script>
import LassoTool from "./LassoTool.vue";
export default {
  components: {
    LassoTool
  },
  data() {
    return {
      fileList: []
    };
  },
  methods: {
    handleEditChange(file, fileList) {
      // 图片改变
      this.fileList = fileList;
      this.$forceUpdate();
    },
    lassoImg(file) {
      this.$refs.LassoTool.lassoImg(file);
    },
    handleRemove(file) {
      // 图片删除
      for (let i = 0; i < this.fileList.length; i++) {
        if (this.fileList[i].uid == file.uid) {
          this.fileList.splice(i, 1);
        }
      }
      this.$forceUpdate();
    }
  }
};
</script>
<style scoped>
.img-upload-con >>> .el-icon-lasso {
  width: 20px;
  height: 20px;
  background: url(../../static/icon/lasso.svg) no-repeat center;
  background-size: 100% 100%;
}
</style>

这个组件主要实现图片上传功能。

<template>
  <div>
    <el-dialog
      :visible.sync="lassoShow"
      :before-close="beforeCropperDialogClose"
      title="自由裁剪"
      custom-class="lasso-img-dialog"
    >
      <div>
        <div class="result-img" v-if="showResult">
          <img :src="base64url" alt="" />
        </div>
        <div class="lasso-canvas-container" v-else>
          <canvas id="lassoContainer1"></canvas>
        </div>
        <div class="operate-btn" style="margin-bottom: 0">
          <span class="search-btn" @click="lassoOk">确定</span>
          <span class="empty" @click="beforeCropperDialogClose">取消</span>
        </div>
      </div>
    </el-dialog>
    <div class="shadow-canvas">
      <canvas class="lasso-canvas" id="lassoContainer2"></canvas>
    </div>
  </div>
</template>

<script>
// https://github.com/bookmarkbao/canvas-select 插件地址
import CanvasSelect from "canvas-select";
export default {
  props: [],
  data() {
    return {
      currentOperateFile: null,
      lassoShow: false,
      instance: null,
      base64url: null,
      showResult: false
    };
  },
  methods: {
    beforeCropperDialogClose() {
      this.instance.destroy();
      this.lassoShow = false;
    },
  getScale(imgWidth, imgHeight) {
      const containerWidth = 500;
      const containerHeight = 400;
      const widthRatio = containerWidth / imgWidth;
      const heightRatio = containerHeight / imgHeight;
      const scale = Math.min(widthRatio, heightRatio);
      return [imgWidth * scale, imgHeight * scale];
    },

    lassoImg(file) {
      // 初始化一些数据
      this.showResult = false;
      this.base64url = null;
      if (this.instance) {
        this.instance.destroy();
      }
      this.currentOperateFile = file;
      this.lassoShow = true;
      this.$nextTick(() => {
        // 这里需要使用$nextTick等待lassoContainer1渲染完成
        const img = new Image();
        img.onload = () => {
          let canvas = document.getElementById("lassoContainer1");
          let width = img.naturalWidth;
          let height = img.naturalHeight;
          let arr = this.getScale(width, height);
          canvas.width = arr[0];
          canvas.height = arr[1];

          this.lassoAction(file);
        };
        img.src = file.url;
      });
    },
    lassoAction(file) {
      this.instance = new CanvasSelect("#lassoContainer1", file.url);
      this.instance.createType = 2;
      this.instance.on("add", info => {
        const canvas = document.getElementById("lassoContainer2");
        const ctx = canvas.getContext("2d");
        // 加载图片并绘制到 Canvas 上
        const img = new Image();
        img.onload = () => {
          const selectedCoords = info.coor;
          const minX = Math.min(...selectedCoords.map(coor => coor[0]));
          const minY = Math.min(...selectedCoords.map(coor => coor[1]));
          const maxX = Math.max(...selectedCoords.map(coor => coor[0]));
          const maxY = Math.max(...selectedCoords.map(coor => coor[1]));

          const width = maxX - minX;
          const height = maxY - minY;

          canvas.width = width;
          canvas.height = height;

          // 绘制多边形路径并裁剪
          ctx.beginPath();
          ctx.moveTo(selectedCoords[0][0] - minX, selectedCoords[0][1] - minY);
          for (let i = 1; i < selectedCoords.length; i++) {
            ctx.lineTo(
              selectedCoords[i][0] - minX,
              selectedCoords[i][1] - minY
            );
          }
          ctx.closePath();
          ctx.clip();
          ctx.drawImage(img, -minX, -minY);

          this.base64url = canvas.toDataURL("image/png", 1);
          this.showResult = true;
          this.$forceUpdate();
        };
        img.src = file.url;
      });
    },
    lassoOk() {
      // 将base64转换成file对象
      let src = this.dataUrlToFile(this.base64url);
      this.currentOperateFile.raw = src;
      this.currentOperateFile.url = this.base64url;
      this.$forceUpdate();
      this.$nextTick(() => {
        this.beforeCropperDialogClose();
      });
    },
    dataUrlToFile(dataurl, filename = "file") {
      let arr = dataurl.split(",");
      let mime = arr[0].match(/:(.*?);/)[1];
      let suffix = mime.split("/")[1];
      let bstr = atob(arr[1]);
      let n = bstr.length;
      let u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], `${filename}.${suffix}`, { type: mime });
    }
  }
};
</script>

<style scoped>
.operate-btn {
  width: 100%;
  max-width: 958px;
  overflow: hidden;
  margin-top: 21px;
  margin-bottom: 36px;
}
.search-btn {
  display: block;
  float: right;
  width: 85px;
  height: 32px;
  background: #1f4ebb;
  border-radius: 2px;
  font-size: 14px;
  color: #fff;
  text-align: center;
  line-height: 32px;
  cursor: pointer;
}
.empty {
  display: block;
  float: right;
  width: 85px;
  height: 32px;
  background: #fff;
  border-radius: 2px;
  font-size: 14px;
  color: #999;
  text-align: center;
  line-height: 30px;
  cursor: pointer;
  border: 1px solid #999;
  box-sizing: border-box;
  margin-right: 12px;
}

.lasso-canvas-container {
  width: 500px;
  height: 400px;
  display: flex;
  justify-content: center;
  align-items: center;
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
}
.lasso-canvas {
  width: 500px;
  height: 400px;
}
.shadow-canvas {
  width: 500px;
  height: 400px;
  position: fixed;
  left: -999999px;
  top: -999999px;
}
.result-img {
  width: 500px;
  height: 400px;
  display: flex;
  justify-content: center;
  align-items: center;
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
}
.result-img img {
  max-width: 500px;
  max-height: 400px;
}
</style>
<style>
.lasso-img-dialog {
  width: 544px;
}
.lasso-img-dialog .el-dialog__body {
  padding-top: 0;
}
</style>

 

posted on 2024-06-27 15:31  hanguahannibk  阅读(25)  评论(0编辑  收藏  举报