vue封装原生可预览裁剪上传图片插件兼容H5

189 阅读1分钟

思路:1.先做出一个上传的图片的上传区

<!-- 上传区 -->
<label for="fileUp">
  <div class="upBorder">
    <img src="../assets/add.png" alt="" />
    <input
      ref="fileUp"
      type="file"
      id="fileUp"
      accept="image"
      style="display: none"
      @change="upload()"
    />
  </div>
</label>
js
```upload() {
  let that = this;
  console.log(this.$refs.fileUp.files);
  if (this.$refs.fileUp.files.length != 0) {
    const reader = new FileReader();
    reader.readAsDataURL(this.$refs.fileUp.files[0]);
    reader.onload = function () {
      const img = new Image();
      img.src = reader.result;
      that.fileList.push(reader.result);
      that.$refs.fileUp.value = null; //上传后重置上传input的value,这样才能同时上传相同的图片
      console.log(reader.result);
    };
    this.upLodaOk = true;
  }
},

给上传图片的input绑定上ref属性然后通过FileReader构造函数获取上传的文件。

2.完成已上传文件的预览区域

<!-- 预览区域 -->
<div
class="preView"
v-for="(i, index) in fileList"
:key="index"
ref="preList"
>
<div class="fileList" v-if="upLodaOk">
  <img
    src="../assets/remove.png"
    alt=""
    class="remove"
    @click="removeProp(index)"
  />
  <img
    :src="fileList[index]"
    alt=""
    class="img"
    @click="cut(index)"
    ref="imgitem"
  />
</div>
</div>

在upload方法中将通过FileReader构造函数获取上传的文件push到fileList数组中然后遍历渲染出已经上传的图片列表,并且给每一个图片绑定ref属性。

3.完成图片删除的功能

<!-- 删除弹窗 -->
<div
class="prop"
:style="{
  height: this.windowHeight + 'px',
  width: this.windowWidth + 'px',
}"
v-if="show"
>
<div class="text">
  <img
    src="../assets/remove.png"
    alt=""
    class="close"
    @click="removePropClose()"
  />
  <div>要删除这张照片吗</div>
  <div class="action">
    <button class="btn green" @click="removePropClose()">取消</button>
    <button class="btn blue" @click="remove()">确定</button>
  </div>
</div>
</div>
removeProp(index) {
  //v-for循环中的ref是个数组,根据index来取每一个对应的dom元素
  this.removeIndex = index;
  this.show = true;
},
removePropClose() {
  this.show = false;
},
remove() {
  this.fileList.splice(this.removeIndex, 1);
  this.$refs.fileUp.value = null; //删除后重置上传input的value,这样才能同时上传相同的图片
  console.log(this.$refs.fileUp.value);
  this.show = false;
},

点击预览图片上的x会触发删除确认弹窗,在removeProp方法中将要删除的图片的Index接收并存储的removeIndex变量中,remove方法中将fileList数组中对应索引的元素去掉并且重置一下上传属性,也可以在每次上传后重置,并且关闭弹窗

4.完成上传时的剪裁功能

<!-- 裁剪蒙层 -->
    <div
      class="prop center"
      v-if="cutProp"
      :style="{
        height: this.windowHeight + 'px',
        width: this.windowWidth + 'px',
      }"
    >
      <div v-html="pre" ref="preimg" class="imgContent"></div>
      <div class="cutHandler">
        <button class="btn green" @click="cancel()">取消</button>
        <button class="btn blue" @click="qdcut()">剪裁</button>
      </div>
    </div>
//裁剪方法
cut(index) {
  this.selIndex = index;
  this.pre = `<img
        src="${this.fileList[index]}"
        alt=""
        class='cutImg'
      />`;
  this.cutProp = true;
  console.log(this.$refs);
  this.$nextTick(function () {
    console.log(this.$refs.preimg.firstChild); //使用nextTick,dom更新完成后才能获取到子节点
    this.myCropper = new Cropper(this.$refs.preimg.firstChild, {
      aspectRatio: 1 / 1,
      dragMode: "move",
      outputType: "png", //防止图片背景变黑
      crop(event) {
        console.log(event.detail.x);
        console.log(event.detail.y);
        console.log(event.detail.width);
        console.log(event.detail.height);
        console.log(event.detail.rotate);
        console.log(event.detail.scaleX);
        console.log(event.detail.scaleY);
      },
    });
  });
},
qdcut() {
  let cropBox = this.myCropper.getCropBoxData();
  console.log(this.myCropper.getCropBoxData()); //打印裁剪数据
  let cropCanvas = this.myCropper.getCroppedCanvas({
    width: cropBox.width,
    height: cropBox.height,
  }); //使用画布画出裁剪后的图片
  let imgData = cropCanvas.toDataURL(); //导出裁剪后图片的数据
  console.log(imgData);
  this.fileList.splice(this.selIndex, 1, imgData);
  console.log(this.fileList);
  this.cutProp = false;
}, //确定裁剪
cancel() {
  this.cutProp = false;
}, //取消裁剪

因为本次封装的是预览时裁剪的功能,所以裁剪的是点击预览列表中的文件触发的,cut方法将选择的图片的index存储selIndex变量中,然后通过v-html指令在剪裁弹窗中加载出对应的图片来进行裁剪,裁剪使用cropper.js来进行的,注意使用时要在this.$nextTick方法的回调中来进行剪裁函数的初始化,这样才能获取到通过v-html指令插入的图片。

  选择合适的裁剪尺寸后点击确认才加调用qdcut方法,通过cropper.js的内置方法getCropBoxData()获取剪裁的数据,通过getCroppedCanvas()传入对应的数据然后导出剪裁后的图片,将fileList中对应的元素替换即可完成

6.下面附上整个代码,可以直接拿去使用:

<template>
  <div>
    <!-- 裁剪蒙层 -->
    <div
      class="prop center"
      v-if="cutProp"
      :style="{
        height: this.windowHeight + 'px',
        width: this.windowWidth + 'px',
      }"
    >
      <div v-html="pre" ref="preimg" class="imgContent"></div>
      <div class="cutHandler">
        <button class="btn green" @click="cancel()">取消</button>
        <button class="btn blue" @click="qdcut()">剪裁</button>
      </div>
    </div>
    <!-- 删除弹窗 -->
    <div
      class="prop"
      :style="{
        height: this.windowHeight + 'px',
        width: this.windowWidth + 'px',
      }"
      v-if="show"
    >
      <div class="text">
        <img
          src="../assets/remove.png"
          alt=""
          class="close"
          @click="removePropClose()"
        />
        <div>要删除这张照片吗</div>
        <div class="action">
          <button class="btn green" @click="removePropClose()">取消</button>
          <button class="btn blue" @click="remove()">确定</button>
        </div>
      </div>
    </div>
    <!-- 上传区域 -->
    <div class="upContent">
      <!-- 预览区域 -->
      <div
        class="preView"
        v-for="(i, index) in fileList"
        :key="index"
        ref="preList"
      >
        <div class="fileList" v-if="upLodaOk">
          <img
            src="../assets/remove.png"
            alt=""
            class="remove"
            @click="removeProp(index)"
          />
          <img
            :src="fileList[index]"
            alt=""
            class="img"
            @click="cut(index)"
            ref="imgitem"
          />
        </div>
      </div>
      <!-- 上传区 -->
      <label for="fileUp">
        <div class="upBorder">
          <img src="../assets/add.png" alt="" />
          <input
            ref="fileUp"
            type="file"
            id="fileUp"
            accept="image"
            style="display: none"
            @change="upload()"
          />
        </div>
      </label>
    </div>
  </div>
</template>
<script>
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.css";
export default {
  name: "upload",
  data() {
    return {
      cutProp: false,
      pre: "", //准备剪裁的图片
      selIndex: "", //选择照片的索引
      removeIndex: "", //准备删除的照片的索引
      show: false, //删除弹出层
      myCropper: null,
      afterImg: "",
      ingData: null,
      upLodaOk: false, //是否展示预览列表
      fileList: [], //已经上传图片的列表
    };
  },
  methods: {
    upload() {
      let that = this;
      console.log(this.$refs.fileUp.files);
      if (this.$refs.fileUp.files.length != 0) {
        const reader = new FileReader();
        reader.readAsDataURL(this.$refs.fileUp.files[0]);
        reader.onload = function () {
          const img = new Image();
          img.src = reader.result;
          that.fileList.push(reader.result);
          that.$refs.fileUp.value = null; //上传后重置上传input的value,这样才能同时上传相同的图片
          console.log(reader.result);
        };
        this.upLodaOk = true;
      }
    },
    removeProp(index) {
      //v-for循环中的ref是个数组,根据index来取每一个对应的dom元素
      this.removeIndex = index;
      this.show = true;
    },
    removePropClose() {
      this.show = false;
    },
    remove() {
      this.fileList.splice(this.removeIndex, 1);
      this.$refs.fileUp.value = null; //删除后重置上传input的value,这样才能同时上传相同的图片
      console.log(this.$refs.fileUp.value);
      this.show = false;
    },
    cut(index) {
      this.selIndex = index;
      this.pre = `<img
            src="${this.fileList[index]}"
            alt=""
            class='cutImg'
          />`;
      this.cutProp = true;
      console.log(this.$refs);
      this.$nextTick(function () {
        console.log(this.$refs.preimg.firstChild); //使用nextTick,dom更新完成后才能获取到子节点
        this.myCropper = new Cropper(this.$refs.preimg.firstChild, {
          aspectRatio: 1 / 1,
          dragMode: "move",
          outputType: "png", //防止图片背景变黑
          crop(event) {
            console.log(event.detail.x);
            console.log(event.detail.y);
            console.log(event.detail.width);
            console.log(event.detail.height);
            console.log(event.detail.rotate);
            console.log(event.detail.scaleX);
            console.log(event.detail.scaleY);
          },
        });
      });
    },
    qdcut() {
      let cropBox = this.myCropper.getCropBoxData();
      console.log(this.myCropper.getCropBoxData()); //打印裁剪数据
      let cropCanvas = this.myCropper.getCroppedCanvas({
        width: cropBox.width,
        height: cropBox.height,
      }); //使用画布画出裁剪后的图片
      let imgData = cropCanvas.toDataURL(); //导出裁剪后图片的数据
      console.log(imgData);
      this.fileList.splice(this.selIndex, 1, imgData);
      console.log(this.fileList);
      this.cutProp = false;
    }, //确定裁剪
    cancel() {
      this.cutProp = false;
    }, //取消裁剪
  },
  mounted() {},
  computed: {
    windowWidth() {
      return document.documentElement.clientWidth;
    },
    windowHeight() {
      return document.documentElement.clientHeight;
    },
  }, //监听屏幕的宽度和高度
};
</script>
<style>
.upBorder {
  width: 8rem;
  height: 8rem;
  border: 1px silver dashed;
  display: flex;
  justify-content: center;
  align-items: center;
}
.upContent {
  display: flex;
  justify-content: center;
  align-items: center;
}
.img {
  width: 8rem;
  height: 8rem;
}

.fileList {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.remove {
  position: absolute;
  width: 1rem;
  height: 1rem;
  top: 0rem;
  right: 0rem;
  cursor: pointer;
}
.prop {
  vertical-align: middle;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 999;
  background-color: rgba(0, 0, 0, 0.7);
}
.text {
  border-radius: 0.2rem;
  top: 50%;
  left: 50%;
  -webkit-transform: translate3d(-50%, -50%, 0);
  transform: translate3d(-50%, -50%, 0);
  position: fixed;
  z-index: 1000;
  color: black;
  text-align: center;
  background-color: #fff;
  padding: 2rem 4rem;
  white-space: nowrap;
}
.close {
  position: absolute;
  top: 0.3rem;
  right: 0.3rem;
  width: 1rem;
  height: 1rem;
}
.action {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 1rem;
}
.btn {
  font-size: 0.12rem;
  color: #fff;
  padding: 0.2rem 0.8rem;
}
.blue {
  background-color: #1989fa;
  border: 1px solid #1989fa;
}
.green {
  background-color: #07c160;
  border: 1px solid #07c160;
}
.cropper-point.point-se {
  width: 5px;
  height: 5px;
}
.cropper {
  position: fixed;
  top: 0;
  z-index: 999;
}

/* .cropper-container{
   top: 50%;
  left: 50%;
  -webkit-transform: translate3d(-50%, -50%, 0);
  transform: translate3d(-50%, -50%, 0);
} */
.imgContent {
  width: 16rem;
  height: 16rem;
  display: inline-block;
  /* top: 50%;
  left: 50%;
  -webkit-transform: translate3d(-50%, -50%, 0);
  transform: translate3d(-50%, -50%, 0); */
}
.cutImg {
  display: block;
  max-width: 100%;
}
.center {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.cropper-bg {
  background: none;
}
.cutHandler {
  margin-top: 2rem;
  width: 16rem;
  text-align: center;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.cropper-modal {
  background: rgba(0, 0, 0, 0);
}
</style>

使用效果:

image.png