图片裁剪组件二次封装,傻瓜式使用

221 阅读2分钟

移动端vue2项目图片裁剪(vue3没有尝试不清楚)(组件样式为移动端样式)

  • vue2项目H5,做一个图片裁剪功能,我百度了一下找到了,但是用起来不方便,因此我自己封装了一下,用的时候只需要下载依赖,然后复制MyCropper组件的所以代码,然后就可以在你想用的地方使用,你不需要知道MyCropper组件的代码是怎么实现的,只需要知道:1.用的时候调用this.$refs.croc.change(要裁剪的图片,参数可以是图片的url或者图片File)会打开图片裁剪的一个东东;2:使用自定义方法returnCrop可以拿到裁剪后的图片数据。即可

依赖

 "core-js": "^3.8.3",
 "cropperjs": "^1.5.12",
 "exif-js": "^2.3.0",

文档

1. api

名称功能参数可选值
this.$refs.croc.change(data)打开裁剪组件必填,传递的参数url 地址, File
@returnCrop点击确认按钮裁剪,返回裁剪后的数据接受返回的参数{baseb4Data:'base64类型',blobData:'blob类型'}

2. props

名称功能默认值可选值
aspectRatioH裁剪框比例,如果有值则固定比例nullNumber
initialAspectRatioH裁剪框默认比例,aspectRatioH为null时才会生效1Number
guidesH是否显示裁剪框的虚线trueBoolean
zoomableH是否允许放大图像trueBoolean
zoomOnWheelH是否可以通过鼠标滚轮缩放图片,zoomableH为true时才会生效trueBoolean
zoomOnTouchH是否可以通过拖动触摸来放大图像,zoomableH为true时才会生效trueBoolean
cropBoxMovableH是否通过拖拽来移动剪裁框trueBoolean
cropBoxResizableH是否通过拖动来调整剪裁框的大小trueBoolean
viewModeH视图控制2Number:0无限制,1限制裁剪框不能超出图片的范围,2限制裁剪框不能超出图片的范围且图片填充模式为cover最长边填充,3限制裁剪框不能超出图片的范围且图片填充模式为contain最短边填充
autoCropAreaH设置裁剪区域占图片的大小 值为 0-10.5Number
movableH图片是否可以拖动trueBoolean

3. 样式

名称描述默认值可选值
.container最外层容器--
.cropper-container裁剪区域容器--
#cancel_clip取消按钮--
#clip_button确认按钮--

使用

<template>
  <div>
    <button @click="onUploadFile(btn)">blob</button>
    <button @click="vUrl">url</button>
    <MyCropper ref="croc" @returnCrop="returnCrop"></MyCropper>
  </div>
</template>

<script>
import MyCropper from './MyCropper.vue'
export default {
  components: {
    MyCropper
  },
  methods: {
    // blobl类型的裁剪目标传参方式
    btn(files) {
      this.$refs.croc.change(files[0])

    },

    // url类型的裁剪目标传参方式
    vUrl() {
      this.$refs.croc.change('https://p9-passport.byteacctimg.com/img/mosaic-legacy/3791/5035712059~300x300.image')
    },
    // 裁剪后返回的数据 {base64,blob}
    returnCrop(data) {
      console.log(data)
    },
    // 这个是一个简单获取图片的上传文件的方法,主要是为了拿到图片的Blob(可以用其他方法代替,仅做演示)
    onUploadFile(callback, attribute = {}) {
      let opiton = {
        accept: attribute.accept ? attribute.accept : "image/*",
        multiple: attribute.multiple ? attribute.multiple : false
      }
      const oInput = document.createElement('input')
      oInput.type = 'file'
      oInput.accept = opiton.accept
      oInput.multiple = opiton.multiple
      oInput.click()
      oInput.addEventListener('change', onChange)
      async function onChange() {
        callback(this.files)
      }
    },
  }
}

</script>

<style lang="less">
</style>

直接复制该组件

<!-- MyCropper -->
<!-- 这个图片剪裁插件,兼容ios与安卓 -->
 
<template>
  <div class="vue-box">
    <img :src="imgUrl" alt="" id="img" v-show="false">
  </div>
</template>
<script>
// returnCrop
import Cropper from 'cropperjs'
import Exif from 'exif-js'
export default {
  name: 'MyCropper',
  props: {
    // 定义的宽高比
    widthRate: {
      type: Number,
      default: 1,
    },
    // 定义的宽高比
    heightRate: {
      type: Number,
      default: 1
    },
    imgUrl: {
      type: String,
      default: ''
    },
    // ----
    aspectRatioH: {
      type: Number,
      default: null
    },
    initialAspectRatioH: {
      type: Number,
      default: 1
    },
    guidesH: {
      type: Boolean,
      default: true
    },
    zoomOnWheelH: {
      type: Boolean,
      default: true
    },
    zoomOnTouchH: {
      type: Boolean,
      default: true
    },
    zoomableH: {
      type: Boolean,
      default: true
    },
    cropBoxMovableH: {
      type: Boolean,
      default: true
    },
    cropBoxResizableH: {
      type: Boolean,
      default: true
    },
    movableH: {
      type: Boolean,
      default: true
    },
    viewModeH: {
      type: Number,
      default: 2
    },
    autoCropAreaH: {
      type: Number,
      default: 0.5
    },



  },

  methods: {
    change(event) {
      let image = document.getElementById('img'); //预览对象
      this.clip(event, {
        resultObj: image,
        aspectWithRatio: 1,
        aspectHeightRatio: 1
      })

    },
    //初始化方法
    initilize(opt) {
      let self = this;
      this.options = opt;
      //创建dom
      this.createElement();
      this.resultObj = opt.resultObj;
      //初始化裁剪对象

      this.cropper = new Cropper(this.preview, {
        // aspectRatio: opt.aspectWithRatio / opt.aspectHeightRatio,
        aspectRatio: this.aspectRatioH, // 裁剪框是否规定比例
        initialAspectRatio: this.initialAspectRatioH, // 裁剪框默认比例
        // aspectRatio: 1/1,
        autoCropArea: this.autoCropAreaH,//设置裁剪区域占图片的大小 值为 0-1 默认 0.5 表示 80%的区域
        // 0 无限制,1 限制裁剪框不能超出图片的范围,2 限制裁剪框不能超出图片的范围 且图片填充模式为 cover 最长边填充,3 限制裁剪框不能超出图片的范围 且图片填充模式为 contain 最短边填充
        viewMode: this.viewModeH,
        guides: this.guidesH,//是否显示裁剪框的虚线 默认true
        cropBoxResizable: this.cropBoxResizableH, //是否通过拖动来调整剪裁框的大小
        cropBoxMovable: this.cropBoxMovableH, //是否通过拖拽来移动剪裁框。
        dragCrop: false,
        dragMode: "move",//‘crop’: 可以产生一个新的裁剪框3 ‘move’: 只可以移动3 ‘none’: 什么也不处理
        center: true,
        zoomable: this.zoomableH, //是否允许放大图像。
        zoomOnTouch: this.zoomOnTouchH,//是否可以通过拖动触摸来放大图像。
        scalable: true,
        background: false,
        checkOrientation: true,
        checkCrossOrigin: true,
        movable: this.movableH, //图片是否可以拖动
        zoomOnWheel: this.zoomOnWheelH, // 是否可以通过鼠标滚轮缩放图片 默认true
        center: false,
        toggleDragModeOnDblclick: false,
        ready: function () {

          if (opt.aspectRatio == 'Free') {
            let cropBox = self.cropper.cropBox;
            cropBox.querySelector('span.cropper-view-box').style.outline = 'none';
            self.cropper.disable();
          }
        }
      });
    },
    //创建一些必要的DOM,用于图片裁剪
    createElement() {
      //初始化图片为空对象
      this.preview = null;
      let str = '<div><img id="clip_image" src=""></div><button type="button" id="cancel_clip">取消</button><button type="button" id="clip_button">确定</button>';
      str += '<div class="crop_loading"><div class="crop_content"><div class="crop_text">图片修剪中...</div></div></div>';
      str += '<div class="crop_success"><div class="crop_success_text">上传成功</div></div></div>';

      let body = document.getElementsByTagName('body')[0];
      this.reagion = document.createElement('div');
      this.reagion.id = 'clip_container';
      this.reagion.className = 'container';
      this.reagion.innerHTML = str;
      //添加创建好的DOM元素
      body.appendChild(this.reagion);
      this.preview = document.getElementById('clip_image');

      //绑定一些方法
      this.initFunction();
    },
    //初始化一些函数绑定
    initFunction() {
      let self = this;
      this.clickBtn = document.getElementById('clip_button');
      this.cancelBtn = document.getElementById('cancel_clip');
      //确定事件
      this.addEvent(this.clickBtn, 'click', function () {

        self.crop();
      })
      //取消事件
      this.addEvent(this.cancelBtn, 'click', function () {
        self.destoried();
      })

    },
    //外部接口,用于input['file']对象change时的调用
    clip(e, opt) {


      //调用初始化方法
      this.initilize(opt);

      //获取图片文件资源
      this.picValue = e;
      if (typeof e == 'object') {
        //调用方法转成url格式
        this.originUrl = this.getObjectURL(this.picValue);
      } else {
        this.originUrl = e
      }
      //每次替换图片要重新得到新的url
      if (this.cropper) {
        this.cropper.replace(this.originUrl);
      }

    },
    //图片转码方法
    getObjectURL(file) {
      let url = null;
      if (window.createObjectURL != undefined) { // basic
        url = window.createObjectURL(file);
      } else if (window.URL != undefined) { // mozilla(firefox)
        url = window.URL.createObjectURL(file);
      } else if (window.webkitURL != undefined) { // webkit or chrome
        url = window.webkitURL.createObjectURL(file);
      }

      return url;
    },
    //点击确定进行裁剪
    crop() {

      let self = this;
      let image = new Image();
      let croppedCanvas;
      let roundedCanvas;

      // Crop
      document.querySelector('.crop_loading').style.display = 'block';

      setTimeout(function () {
        croppedCanvas = self.cropper.getCroppedCanvas();
        // Round
        roundedCanvas = self.getRoundedCanvas(croppedCanvas);

        let imgData = roundedCanvas.toDataURL();
        image.src = imgData;
        //  裁剪后的数据


        self.resultObj.src = imgData;
        self.$emit('returnCrop', {
          baseb4Data: imgData,
          blobData: self.dataURItoBlob(imgData)
        })
        self.destoried()
      }, 20)
    },
    //获取裁剪图片资源
    getRoundedCanvas(sourceCanvas) {

      let canvas = document.createElement('canvas');
      let context = canvas.getContext('2d');
      let width = sourceCanvas.width;
      let height = sourceCanvas.height;

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

      context.imageSmoothingEnabled = true;
      context.drawImage(sourceCanvas, 0, 0, width, height);
      context.globalCompositeOperation = 'destination-in';
      context.beginPath();
      context.rect(0, 0, width, height);
      context.fill();

      return canvas;
    },
    //销毁原来的对象
    destoried() {
      let self = this;
      //移除事件
      this.removeEvent(this.clickBtn, 'click', null);
      this.removeEvent(this.cancelBtn, 'click', null);

      //移除裁剪框
      this.reagion.parentNode.removeChild(this.reagion);

      //销毁裁剪对象
      this.cropper.destroy();
      this.cropper = null;
    },

    //图片旋转
    rotateImg(img, direction, canvas) {
      //最小与最大旋转方向,图片旋转4次后回到原方向
      const min_step = 0;
      const max_step = 3;
      if (img == null) return;
      //img的高度和宽度不能在img元素隐藏后获取,否则会出错
      let height = img.height;
      let width = img.width;
      let step = 2;
      if (step == null) {
        step = min_step;
      }
      if (direction == 'right') {
        step++;
        //旋转到原位置,即超过最大值
        step > max_step && (step = min_step);
      } else {
        step--;
        step < min_step && (step = max_step);
      }
      //旋转角度以弧度值为参数
      let degree = step * 90 * Math.PI / 180;
      let ctx = canvas.getContext('2d');
      switch (step) {
        case 0:
          canvas.width = width;
          canvas.height = height;
          ctx.drawImage(img, 0, 0);
          break;
        case 1:
          canvas.width = height;
          canvas.height = width;
          ctx.rotate(degree);
          ctx.drawImage(img, 0, -height);
          break;
        case 2:
          canvas.width = width;
          canvas.height = height;
          ctx.rotate(degree);
          ctx.drawImage(img, -width, -height);
          break;
        case 3:
          canvas.width = height;
          canvas.height = width;
          ctx.rotate(degree);
          ctx.drawImage(img, -width, 0);
          break;
      }
    },

    //添加事件
    addEvent(obj, type, fn) {
      if (obj.addEventListener) {
        obj.addEventListener(type, fn, false);
      } else {
        obj.attachEvent('on' + type, fn);
      }
    },
    //移除事件
    removeEvent(obj, type, fn) {
      if (obj.removeEventListener) {
        obj.removeEventListener(type, fn, false);
      } else {
        obj.detachEvent('on' + type, fn);
      }
    },
    dataURItoBlob(dataURI) {
      var base64Arr = dataURI.split(",");
      var imgtype = "";
      var base64String = "";
      if (base64Arr.length > 1) {
        //如果是图片base64,去掉头信息
        base64String = base64Arr[1];
        imgtype = base64Arr[0].substring(
          base64Arr[0].indexOf(":") + 1,
          base64Arr[0].indexOf(";")
        );
      }
      // 将base64解码
      var bytes = atob(base64String);
      //var bytes = base64;
      var bytesCode = new ArrayBuffer(bytes.length);
      // 转换为类型化数组
      var byteArray = new Uint8Array(bytesCode);

      // 将base64转换为ascii码
      for (var i = 0; i < bytes.length; i++) {
        byteArray[i] = bytes.charCodeAt(i);
      }

      // 生成Blob对象(文件对象)
      return new Blob([bytesCode], { type: imgtype });
    }


  }
}
</script>
<style scoped>
.vue-box {
  position: relative;
  width: 0;
  height: 0;

}

img {

  width: 100%;
  height: 100%;
}
</style>
<style>
#clip_button {
  position: absolute;
  right: 10%;
  bottom: 20px;
  width: 80px;
  height: 40px;
  border: none;
  border-radius: 2px;
  background: #1AAD19;
  color: #fff;
}

#cancel_clip {
  position: absolute;
  left: 10%;
  bottom: 20px;
  width: 80px;
  height: 40px;
  border: none;
  border-radius: 2px;
  color: #fff;
  background: #E64340;
}

#clip_container.container {
  z-index: 99999;
  position: fixed;
  padding-top: 60px;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 1);
}

#clip_container.container>div {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 50%;
  left: 50%;
  -webkit-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
}

#clip_image {
  max-width: 100%;
}

.cropper-container {
  font-size: 0;
  line-height: 0;
  position: relative;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  direction: ltr;
  -ms-touch-action: none;
  touch-action: none
}

.crop_loading,
.crop_success {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9;
}

.crop_loading .crop_content {
  position: absolute;
  top: 50%;
  left: 50%;
  text-align: center;
  background: #000;
  opacity: 0.9;
  height: 66px;
  width: 140px;
  vertical-align: middle;
  color: #fff;
  padding-top: 20px;
  font-size: 16px;
  -webkit-border-radius: 3px;
  border-radius: 3px;
  -webkit-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
}

.crop_loading .crop_content img {
  margin-top: 15px;
  margin-bottom: 10px;
}

.crop_success .crop_success_text {
  position: absolute;
  top: 50%;
  left: 50%;
  text-align: center;
  background: #000;
  opacity: 0.9;
  width: 120px;
  height: 30px;
  color: #fff;
  line-height: 30px;
  font-size: 16px;
  -webkit-border-radius: 3px;
  border-radius: 3px;
  -webkit-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
}

.cropper-container img {
  /* Avoid margin top issue (Occur only when margin-top <= -height) */
  display: block;
  min-width: 0 !important;
  max-width: none !important;
  min-height: 0 !important;
  max-height: none !important;
  width: 100%;
  height: 100%;
  image-orientation: 0deg
}

.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.cropper-wrap-box {
  overflow: hidden;
}

.cropper-drag-box {
  opacity: 0;
  background-color: #fff;
}

.cropper-modal {
  opacity: .5;
  background-color: #000;
}

.cropper-view-box {
  display: block;
  overflow: hidden;

  width: 100%;
  height: 100%;

  outline: 1px solid #39f;
  outline-color: rgba(51, 153, 255, 0.75);
}

.cropper-dashed {
  position: absolute;

  display: block;

  opacity: .5;
  border: 0 dashed #eee
}

.cropper-dashed.dashed-h {
  top: 33.33333%;
  left: 0;
  width: 100%;
  height: 33.33333%;
  border-top-width: 1px;
  border-bottom-width: 1px
}

.cropper-dashed.dashed-v {
  top: 0;
  left: 33.33333%;
  width: 33.33333%;
  height: 100%;
  border-right-width: 1px;
  border-left-width: 1px
}

.cropper-center {
  position: absolute;
  top: 50%;
  left: 50%;

  display: block;

  width: 0;
  height: 0;

  opacity: .75
}

.cropper-center:before,
.cropper-center:after {
  position: absolute;
  display: block;
  content: ' ';
  background-color: #eee
}

.cropper-center:before {
  top: 0;
  left: -3px;
  width: 7px;
  height: 1px
}

.cropper-center:after {
  top: -3px;
  left: 0;
  width: 1px;
  height: 7px
}

.cropper-face,
.cropper-line,
.cropper-point {
  position: absolute;

  display: block;

  width: 100%;
  height: 100%;

  opacity: .1;
}

.cropper-face {
  top: 0;
  left: 0;

  background-color: #fff;
}

.cropper-line {
  background-color: #39f
}

.cropper-line.line-e {
  top: 0;
  right: -3px;
  width: 5px;
  cursor: e-resize
}

.cropper-line.line-n {
  top: -3px;
  left: 0;
  height: 5px;
  cursor: n-resize
}

.cropper-line.line-w {
  top: 0;
  left: -3px;
  width: 5px;
  cursor: w-resize
}

.cropper-line.line-s {
  bottom: -3px;
  left: 0;
  height: 5px;
  cursor: s-resize
}

.cropper-point {
  width: 5px;
  height: 5px;

  opacity: .75;
  background-color: #39f
}

.cropper-point.point-e {
  top: 50%;
  right: -3px;
  margin-top: -3px;
  cursor: e-resize
}

.cropper-point.point-n {
  top: -3px;
  left: 50%;
  margin-left: -3px;
  cursor: n-resize
}

.cropper-point.point-w {
  top: 50%;
  left: -3px;
  margin-top: -3px;
  cursor: w-resize
}

.cropper-point.point-s {
  bottom: -3px;
  left: 50%;
  margin-left: -3px;
  cursor: s-resize
}

.cropper-point.point-ne {
  top: -3px;
  right: -3px;
  cursor: ne-resize
}

.cropper-point.point-nw {
  top: -3px;
  left: -3px;
  cursor: nw-resize
}

.cropper-point.point-sw {
  bottom: -3px;
  left: -3px;
  cursor: sw-resize
}

.cropper-point.point-se {
  right: -3px;
  bottom: -3px;
  width: 20px;
  height: 20px;
  cursor: se-resize;
  opacity: 1
}

@media (min-width: 768px) {

  .cropper-point.point-se {
    width: 15px;
    height: 15px
  }
}

@media (min-width: 992px) {

  .cropper-point.point-se {
    width: 10px;
    height: 10px
  }
}

@media (min-width: 1200px) {

  .cropper-point.point-se {
    width: 5px;
    height: 5px;
    opacity: .75
  }
}

.cropper-point.point-se:before {
  position: absolute;
  right: -50%;
  bottom: -50%;
  display: block;
  width: 200%;
  height: 200%;
  content: ' ';
  opacity: 0;
  background-color: #39f
}

.cropper-invisible {
  opacity: 0;
}

.cropper-bg {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMzTjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
}

.cropper-hide {
  position: absolute;

  display: block;

  width: 0;
  height: 0;
}

.cropper-hidden {
  display: none !important;
}

.cropper-move {
  cursor: move;
}

.cropper-crop {
  cursor: crosshair;
}

.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
  cursor: not-allowed;
}
</style>