vue-cropper图片裁剪,无需安装

创建公用文件 cropper.vue 摘自github vue-cropper组件,节省了安装的步骤

<template>
  <div class="vue-cropper" ref="cropper">
    <div class="cropper-box">
      <div class="cropper-box-canvas"
        v-show="!loading"
        :style="{
          'width': trueWidth + 'px',
          'height': trueHeight + 'px',
          'transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ x / scale + 'px,' + y / scale + 'px,' + '0)'
          + 'rotateZ('+ rotate * 90 +'deg)'
          }"
          >
        <img
          :src="imgs"
          alt="cropper-img"
          ref="cropperImg"
          />
      </div>
    </div>
    <div
      class="cropper-drag-box"
      :class="{'cropper-move': move && !crop, 'cropper-crop': crop, 'cropper-modal': cropping}"
      @mousedown="startMove"
      >
    </div>
      <div
        v-show="cropping"
        class="cropper-crop-box"
        :style="{
          'width': cropW + 'px',
          'height': cropH + 'px',
          'transform': 'translate3d('+ cropOffsertX + 'px,' + cropOffsertY + 'px,' + '0)'
        }">
        <span class="cropper-view-box">
          <img
          :style="{
            'width': trueWidth + 'px',
            'height': trueHeight + 'px',
            'transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ (x - cropOffsertX) / scale  + 'px,' + (y - cropOffsertY) / scale + 'px,' + '0)'
            + 'rotateZ('+ rotate * 90 +'deg)'
            }"
            :src="imgs"
            alt="cropper-img"
            />
        </span>
        <span
          class="cropper-face cropper-move"
          @mousedown="cropMove"
        ></span>
        <span class="crop-info" v-if="info" :style="{'top': cropInfo}">{{  this.cropW > 0 ? this.cropW : 0 }} × {{ this.cropH > 0 ? this.cropH : 0 }}</span>
        <span v-if="!fixedBox">
          <span class="crop-line line-w" @mousedown="changeCropSize($event, false, true, 0, 1)"></span>
          <span class="crop-line line-a" @mousedown="changeCropSize($event, true, false, 1, 0)"></span>
          <span class="crop-line line-s" @mousedown="changeCropSize($event, false, true, 0, 2)"></span>
          <span class="crop-line line-d" @mousedown="changeCropSize($event, true, false, 2, 0)"></span>
          <span class="crop-point point1" @mousedown="changeCropSize($event, true, true, 1, 1)"></span>
          <span class="crop-point point2" @mousedown="changeCropSize($event, false, true, 0, 1)"></span>
          <span class="crop-point point3" @mousedown="changeCropSize($event, true, true, 2, 1)"></span>
          <span class="crop-point point4" @mousedown="changeCropSize($event, true, false, 1, 0)"></span>
          <span class="crop-point point5" @mousedown="changeCropSize($event, true, false, 2, 0)"></span>
          <span class="crop-point point6" @mousedown="changeCropSize($event, true, true, 1, 2)"></span>
          <span class="crop-point point7" @mousedown="changeCropSize($event, false, true, 0, 2)"></span>
          <span class="crop-point point8" @mousedown="changeCropSize($event, true, true, 2, 2)"></span>
        </span>
    </div>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      // 容器高宽
      w: 0,
      h: 0,
      // 图片缩放比例
      scale: 1,
      // 图片偏移x轴
      x: 0,
      // 图片偏移y轴
      y: 0,
      // 图片加载
      loading: true,
      // 图片真实宽度
      trueWidth: 0,
      // 图片真实高度
      trueHeight: 0,
      move: true,
      // 移动的x
      moveX: 0,
      // 移动的y
      moveY: 0,
      // 开启截图
      crop: false,
      // 正在截图
      cropping: false,
      // 裁剪框大小
      cropW: 0,
      cropH: 0,
      cropOldW: 0,
      cropOldH: 0,
      // 判断是否能够改变
      canChangeX: false,
      canChangeY: false,
      // 改变的基准点
      changeCropTypeX: 1,
      changeCropTypeY: 1,
      // 裁剪框的坐标轴
      cropX: 0,
      cropY: 0,
      cropChangeX: 0,
      cropChangeY: 0,
      cropOffsertX: 0,
      cropOffsertY: 0,
      // 支持的滚动事件
      support: '',
      // 图片旋转
      rotate: 0,
      orientation: 0,
      imgs: '',
      // 图片缩放系数
      coe: 0.2,
      // 是否正在多次缩放
      scaling: false,
      scalingSet: '',
      coeStatus: ''
    }
  },
  props: {
    img: {
      type: String,
      default: ''
    },
    // 输出图片压缩比
    outputSize: {
      type: Number,
      default: 1
    },
    outputType: {
      type: String,
      default: 'jpeg'
    },
    info: {
      type: Boolean,
      default: true
    },
    // 是否自成截图框
    autoCrop: {
      type: Boolean,
      default: false
    },
    autoCropWidth: {
      type: Number,
      default: 0
    },
    autoCropHeight: {
      type: Number,
      default: 0
    },
    // 是否开启固定宽高比
    fixed: {
      type: Boolean,
      default: false
    },
    // 宽高比 w/h
    fixedNumber: {
      type: Array,
      default: () => {
        return [1, 1]
      }
    },
    // 固定大小 禁止改变截图框大小
    fixedBox: {
      type: Boolean,
      default: false
    },
    // 输出截图是否缩放
    full: {
      type: Boolean,
      default: false
    },
    // 是否可以拖动图片
    canMove: {
      type: Boolean,
      default: true
    },
    // 是否可以拖动截图框
    canMoveBox: {
      type: Boolean,
      default: true
    },
    // 上传图片按照原始比例显示
    original: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    cropInfo () {
      return this.cropOffsertY > 20 ? '-20px' : '0px'
    }
  },
  watch: {
    // 如果图片改变, 重新布局
    img () {
      // 当传入图片时, 读取图片信息同时展示
      this.checkedImg()
    },
    imgs (val) {
      if (val === '') {
        return
      }
      this.reload()
    },
    cropW () {
      this.cropW = ~~(this.cropW)
      this.showPreview()
    },
    cropH () {
      this.cropH = ~~(this.cropH)
      this.showPreview()
    },
    cropOffsertX () {
      this.showPreview()
    },
    cropOffsertY () {
      this.showPreview()
    },
    scale () {
      this.showPreview()
    },
    x () {
      this.showPreview()
    },
    y () {
      this.showPreview()
    },
    rotate () {
      this.showPreview()
    }
  },
  methods: {
    // 校验图片
    checkedImg () {
      if (this.img === '') return
      this.loading = true
      this.scale = 1
      this.clearCrop()
      let canvas = document.createElement('canvas')
      let img = new Image
      let rotate = 0
      img.onload = () => {
        let width = img.width
        let height = img.height
        let ctx = canvas.getContext('2d')
        ctx.save()
          switch (this.orientation) {
            case 6:
              rotate = 1
              break
            case 8:
              rotate = -1
              break
            case 3:
              rotate = 3
              break
            default:
              rotate = 0
          }
          if (rotate === 0) {
            this.imgs = this.img
            return
          }
          switch (rotate) {
            case 0:
              canvas.width = width
              canvas.height = height
              ctx.drawImage(img, 0, 0, width, height)
            break
            case 1:
            case -3:
              // 旋转90度 或者-270度 宽度和高度对调
              canvas.width = height
              canvas.height = width
              ctx.rotate(rotate * 90  * Math.PI / 180)
              ctx.drawImage(img, 0, -height, width, height)
              break
            case 2:
            case -2:
              canvas.width = width
              canvas.height = height
              ctx.rotate(rotate * 90  * Math.PI / 180)
              ctx.drawImage(img, -width, -height, width, height)
            break
            case 3:
            case -1:
              canvas.width = height
              canvas.height = width
              ctx.rotate(rotate * 90  * Math.PI / 180)
              ctx.drawImage(img, -width, 0, width, height)
              break
            default:
              canvas.width = width
              canvas.height = height
              ctx.drawImage(img, 0, 0, width, height)
          }
          ctx.restore()
          canvas.toBlob((blob) => {
            let data = URL.createObjectURL(blob)
            this.imgs = data
          }, 'image/' + this.outputType, 1)
      }
      img.onerror = () => {
        this.$emit('imgLoad', 'error')
      }
      img.crossOrigin = '*'
      img.src = this.img
    },
    // 当按下鼠标键
    startMove (e) {
      e.preventDefault()
      // 如果move 为true 表示当前可以拖动
      if (this.move && !this.crop) {
        if (!this.canMove) {
          return false
        }
        // 开始移动
        this.moveX = e.clientX - this.x
        this.moveY = e.clientY - this.y
        window.addEventListener('mousemove', this.moveImg)
        window.addEventListener('mouseup', this.leaveImg)
      } else {
        // 截图ing
        this.cropping = true
        // 绑定截图事件
        window.addEventListener('mousemove', this.createCrop)
        window.addEventListener('mouseup', this.endCrop)
        this.cropOffsertX = e.offsetX;
        this.cropOffsertY = e.offsetY;
        this.cropX = e.clientX;
        this.cropY = e.clientY;

        this.cropChangeX = this.cropOffsertX;
        this.cropChangeY = this.cropOffsertY;
        this.cropW = 0
        this.cropH = 0
      }
    },
    // 移动图片
    moveImg (e) {
      e.preventDefault()
      var nowX = e.clientX;
      var nowY = e.clientY;
      this.$nextTick(() => {
        this.x = ~~(nowX - this.moveX)
        this.y = ~~(nowY - this.moveY)
      })
    },
    // 移动图片结束
    leaveImg (e) {
      window.removeEventListener('mousemove', this.moveImg);
      window.removeEventListener('mouseup', this.leaveImg);
    },
    // 修改图片大小函数
    changeScale (num) {
      num = num || 1
      var coe = 20
      coe = coe / this.trueWidth > coe / this.trueHeight ? coe / this.trueHeight : coe / this.trueWidth
      num = num * coe
      num > 0 ? this.scale += Math.abs(num) : this.scale > Math.abs(num) ? this.scale -= Math.abs(num) : this.scale
    },
    // 创建截图框
    createCrop (e) {
      e.preventDefault()
      // 移动生成大小
      var nowX = e.clientX ? e.clientX : 0
      var nowY = e.clientY ? e.clientY : 0
      this.$nextTick(() => {
        var fw = ~~(nowX - this.cropX)
        var fh = ~~(nowY - this.cropY)
        if (fw > 0) {
          this.cropW = fw + this.cropChangeX > this.w ? this.w - this.cropChangeX : fw
          this.cropOffsertX = this.cropChangeX
        } else {
          this.cropW = (this.w - this.cropChangeX + Math.abs(fw)) > this.w ? this.cropChangeX : Math.abs(fw)
          this.cropOffsertX = this.cropChangeX  + fw > 0 ? this.cropChangeX + fw : 0
        }
        if (!this.fixed) {
          if (fh > 0) {
            this.cropH = fh + this.cropChangeY > this.h ? this.h - this.cropChangeY : fh
            this.cropOffsertY = this.cropChangeY
          } else {
            this.cropH = (this.h - this.cropChangeY + Math.abs(fh)) > this.h ? this.cropChangeY : Math.abs(fh)
            this.cropOffsertY = this.cropChangeY  + fh > 0 ? this.cropChangeY + fh : 0
          }
        } else {
          var fixedHeight = ~~(this.cropW / this.fixedNumber[0] * this.fixedNumber[1])
          if (fixedHeight + this.cropOffsertY > this.h) {
            this.cropH = this.h - this.cropOffsertY
            this.cropW = ~~(this.cropH / this.fixedNumber[1] * this.fixedNumber[0])
            if (fw > 0) {
              this.cropOffsertX = this.cropChangeX
            } else {
              this.cropOffsertX = this.cropChangeX - this.cropW
            }
          } else {
            this.cropH = fixedHeight
          }
          this.cropOffsertY = this.cropOffsertY
        }
      })
    },
    // 改变截图框大小
    changeCropSize (e, w, h, typeW, typeH) {
      e.preventDefault()
      window.addEventListener('mousemove', this.changeCropNow)
      window.addEventListener('mouseup', this.changeCropEnd)
      this.canChangeX = w;
      this.canChangeY = h;
      this.changeCropTypeX = typeW;
      this.changeCropTypeY = typeH;
      this.cropX = e.clientX;
      this.cropY = e.clientY;
      this.cropOldW = this.cropW;
      this.cropOldH = this.cropH;
      this.cropChangeX = this.cropOffsertX
      this.cropChangeY = this.cropOffsertY
      if (this.fixed) {
        if (this.canChangeX && this.canChangeY) {
          this.canChangeY = 0
        }
      }
    },
    // 正在改变
    changeCropNow (e) {
      e.preventDefault()
      var nowX = e.clientX;
      var nowY = e.clientY;
      this.$nextTick(() => {
        var fw = ~~(nowX - this.cropX)
        var fh = ~~(nowY - this.cropY)
        if (this.canChangeX) {
          if (this.changeCropTypeX === 1) {
            if (this.cropOldW - fw > 0) {
              this.cropW = this.w - this.cropChangeX - fw <= this.w ? this.cropOldW - fw : this.cropOldW + this.cropChangeX
              this.cropOffsertX = this.w - this.cropChangeX - fw <= this.w ? this.cropChangeX + fw : 0
            } else {
              console.log(fw);
              this.cropW = Math.abs(fw) + this.cropChangeX <= this.w ? Math.abs(fw) - this.cropOldW : this.w - this.cropOldW - this.cropChangeX
              this.cropOffsertX = this.cropChangeX + this.cropOldW
            }
          } else if (this.changeCropTypeX === 2) {
            if (this.cropOldW + fw > 0) {
              this.cropW = this.cropOldW + fw + this.cropOffsertX <= this.w ? this.cropOldW + fw : this.w - this.cropOffsertX
              this.cropOffsertX = this.cropChangeX
            } else {
              this.cropW = (this.w - this.cropChangeX + Math.abs(fw + this.cropOldW)) <= this.w ? Math.abs(fw + this.cropOldW) : this.cropChangeX
              this.cropOffsertX = (this.w - this.cropChangeX + Math.abs(fw + this.cropOldW)) <= this.w ? this.cropChangeX - Math.abs(fw + this.cropOldW) : 0
            }
          }
        }
        if (this.canChangeY) {
          if (this.changeCropTypeY === 1) {
            if (this.cropOldH - fh > 0) {
              this.cropH = this.h - this.cropChangeY - fh <= this.h ? this.cropOldH - fh : this.cropOldH + this.cropChangeY
              this.cropOffsertY = this.h - this.cropChangeY - fh <= this.h ? this.cropChangeY + fh : 0
            } else {
              this.cropH = Math.abs(fh) + this.cropChangeY <= this.h ? Math.abs(fh) - this.cropOldH : this.h - this.cropOldH - this.cropChangeY
              this.cropOffsertY = this.cropChangeY + this.cropOldH
            }
          } else if (this.changeCropTypeY === 2) {
            if (this.cropOldH + fh > 0) {
              this.cropH = this.cropOldH + fh + this.cropOffsertY <= this.h ? this.cropOldH + fh : this.h - this.cropOffsertY
              this.cropOffsertY = this.cropChangeY
            } else {
              this.cropH = (this.h - this.cropChangeY + Math.abs(fh + this.cropOldH)) <= this.h ? Math.abs(fh + this.cropOldH) : this.cropChangeY
              this.cropOffsertY = (this.h - this.cropChangeY + Math.abs(fh + this.cropOldH)) <= this.h ? this.cropChangeY - Math.abs(fh + this.cropOldH) : 0
            }
          }
        }
        if (this.canChangeX && this.fixed) {
          var fixedHeight = ~~(this.cropW / this.fixedNumber[0] * this.fixedNumber[1])
          if (fixedHeight + this.cropOffsertY > this.h) {
            this.cropH = this.h - this.cropOffsertY
            this.cropW = ~~(this.cropH / this.fixedNumber[1] * this.fixedNumber[0])
          } else {
            this.cropH = fixedHeight
          }
        }
        if (this.canChangeY && this.fixed) {
          var fixedWidth = ~~(this.cropH / this.fixedNumber[1] * this.fixedNumber[0])
          if (fixedWidth + this.cropOffsertX > this.w) {
            this.cropW = this.w - this.cropOffsertX
            this.cropH = ~~(this.cropW / this.fixedNumber[0] * this.fixedNumber[1])
          } else {
            this.cropW = fixedWidth
          }
        }
      })
    },
    // 结束改变
    changeCropEnd (e) {
      window.removeEventListener('mousemove', this.changeCropNow)
      window.removeEventListener('mouseup', this.changeCropEnd)
    },
    // 创建完成
    endCrop () {
      if (this.cropW === 0 && this.cropH === 0) {
        this.cropping = false
      }
      window.removeEventListener('mousemove', this.createCrop)
      window.removeEventListener('mouseup', this.endCrop)
    },
    // 开始截图
    startCrop () {
      this.crop = true
      // console.log('开始截图')
    },
    // 停止截图
    stopCrop () {
      this.crop = false
      // console.log('停止截图')
    },
    // 清除截图
    clearCrop () {
      this.cropping = false
      this.cropW = 0
      this.cropH = 0
      // console.log('清除截图')
    },
    // 截图移动
    cropMove (e) {
      e.preventDefault()
      if (!this.canMoveBox) {
        this.crop = false
        this.startMove(e)
        return false
      }
      window.addEventListener('mousemove', this.moveCrop)
      window.addEventListener('mouseup', this.leaveCrop)
      this.cropX = e.clientX - this.cropOffsertX
      this.cropY = e.clientY - this.cropOffsertY
    },
    moveCrop (e) {
      e.preventDefault()
      var nowX = e.clientX;
      var nowY = e.clientY;
      this.$nextTick(() => {
        var fw = ~~(nowX - this.cropX);
        var fh = ~~(nowY - this.cropY)
        if (fw <= 1) {
          this.cropOffsertX = 1
        } else if (~~(fw + this.cropW) > this.w) {
          this.cropOffsertX = this.w - this.cropW - 1
        } else {
          this.cropOffsertX = fw
        }
        if (fh <= 1) {
          this.cropOffsertY = 1
        } else if (~~(fh + this.cropH) > this.h) {
          this.cropOffsertY = this.h - this.cropH - 1
        } else {
          this.cropOffsertY = fh
        }
      })
    },
    leaveCrop (e) {
      window.removeEventListener('mousemove', this.moveCrop)
      window.removeEventListener('mouseup', this.leaveCrop)
    },
    // 获取转换成base64 的图片信息
    getCropData (cb) {
      let canvas = document.createElement('canvas')
      let img = new Image
      let rotate = this.rotate
      let trueWidth = this.trueWidth
      let trueHeight = this.trueHeight
      let cropOffsertX = this.cropOffsertX
      let cropOffsertY = this.cropOffsertY
      img.onload = () => {
        if (~~(this.cropW) !== 0) {
          let ctx = canvas.getContext('2d')
          let width = this.cropW
          let height = this.cropH
          let imgW = trueWidth * this.scale
          let imgH = trueHeight * this.scale
          // 图片x轴偏移
          let dx = (this.x - cropOffsertX) + this.trueWidth * (1 - this.scale) / 2
          // 图片y轴偏移
          let dy = (this.y - cropOffsertY) + this.trueHeight * (1 - this.scale) / 2
          // console.log(dx, dy)
          //保存状态
          canvas.width = width
          canvas.height = height
          ctx.save()
          switch (rotate) {
            case 0:
              if (!this.full) {
                ctx.drawImage(img, dx, dy, imgW, imgH)
              } else {
                // 输出原图比例截图
                canvas.width = width / this.scale
                canvas.height = height / this.scale
                ctx.drawImage(img, dx / this.scale, dy / this.scale, imgW / this.scale, imgH / this.scale)
              }
              break
            case 1:
            case -3:
              if (!this.full) {
                // 换算图片旋转后的坐标弥补
                dx = dx + (imgW - imgH) / 2
                dy = dy + (imgH - imgW) / 2
                ctx.rotate(rotate * 90  * Math.PI / 180)
                ctx.drawImage(img, dy, -dx - imgH, imgW, imgH)
              } else {
                canvas.width = width / this.scale
                canvas.height = height / this.scale
                // 换算图片旋转后的坐标弥补
                dx = dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2
                dy = dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2
                ctx.rotate(rotate * 90  * Math.PI / 180)
                ctx.drawImage(img, dy, (-dx - imgH / this.scale), imgW / this.scale, imgH / this.scale)
              }
              break
            case 2:
            case -2:
              if (!this.full) {
                ctx.rotate(rotate * 90  * Math.PI / 180)
                ctx.drawImage(img, -dx - imgW, -dy - imgH, imgW, imgH)
              } else {
                canvas.width = width / this.scale
                canvas.height = height / this.scale
                ctx.rotate(rotate * 90  * Math.PI / 180)
                dx = dx / this.scale
                dy = dy / this.scale
                ctx.drawImage(img, -dx - imgW / this.scale, -dy - imgH / this.scale, imgW / this.scale, imgH / this.scale)
              }
            break
            case 3:
            case -1:
              if (!this.full) {
                // 换算图片旋转后的坐标弥补
                dx = dx + (imgW - imgH) / 2
                dy = dy + (imgH - imgW) / 2
                ctx.rotate(rotate * 90  * Math.PI / 180)
                ctx.drawImage(img, -dy - imgW, dx, imgW, imgH)
              } else {
                canvas.width = width / this.scale
                canvas.height = height / this.scale
                // 换算图片旋转后的坐标弥补
                dx = dx / this.scale + (imgW / this.scale - imgH / this.scale) / 2
                dy = dy / this.scale + (imgH / this.scale - imgW / this.scale) / 2
                ctx.rotate(rotate * 90  * Math.PI / 180)
                ctx.drawImage(img, -dy - imgW / this.scale, dx, imgW / this.scale, imgH / this.scale)
              }
              break
            default:
              if (!this.full) {
                ctx.drawImage(img, dx, dy, imgW, imgH)
              } else {
                // 输出原图比例截图
                canvas.width = width / this.scale
                canvas.height = height / this.scale
                ctx.drawImage(img, dx / this.scale, dy / this.scale, imgW / this.scale, imgH / this.scale)
              }
          }
          ctx.restore()
        } else {
          let width = trueWidth * this.scale
          let height = trueHeight * this.scale
          let ctx = canvas.getContext('2d')
          ctx.save()
          switch (rotate) {
            case 0:
              canvas.width = width
              canvas.height = height
              ctx.drawImage(img, 0, 0, width, height)
            break
            case 1:
            case -3:
              // 旋转90度 或者-270度 宽度和高度对调
              canvas.width = height
              canvas.height = width
              ctx.rotate(rotate * 90  * Math.PI / 180)
              ctx.drawImage(img, 0, -height, width, height)
              break
            case 2:
            case -2:
              canvas.width = width
              canvas.height = height
              ctx.rotate(rotate * 90  * Math.PI / 180)
              ctx.drawImage(img, -width, -height, width, height)
            break
            case 3:
            case -1:
              canvas.width = height
              canvas.height = width
              ctx.rotate(rotate * 90  * Math.PI / 180)
              ctx.drawImage(img, -width, 0, width, height)
              break
            default:
              canvas.width = width
              canvas.height = height
              ctx.drawImage(img, 0, 0, width, height)
          }
          ctx.restore()
        }
        let data = canvas.toDataURL('image/' + this.outputType, this.outputSize)
        cb(data)
      }
      // 判断图片是否是base64
      var s = this.img.substr(0, 4)
      if (s !== 'data') {
        img.crossOrigin = 'anonymous'
      }
      img.src = this.imgs
    },
    //转化base64 为blob对象
    getCropBlob(cb) {
      this.getCropData((data) => {
        var arr = data.split(',')
        var mime = arr[0].match(/:(.*?);/)[1]
        var bstr = atob(arr[1])
        var n = bstr.length
        var u8arr = new Uint8Array(n)
        while (n--) {
          u8arr[n] = bstr.charCodeAt(n)
        }
        cb(
          new Blob([u8arr], {
            type: mime
          }
        ))
      })
    },
    // 自动预览函数
    showPreview() {
      var obj = {}
      obj.div = {
        'width': this.cropW + 'px',
        'height': this.cropH + 'px'
      }
      obj.img = {
        'width': this.trueWidth + 'px',
        'height': this.trueHeight + 'px',
        'transform': 'scale(' + this.scale + ',' + this.scale + ') ' + 'translate3d('+ (this.x - this.cropOffsertX) / this.scale  + 'px,' + (this.y - this.cropOffsertY) / this.scale + 'px,' + '0)'
        + 'rotateZ('+ this.rotate * 90 + 'deg)'
      }
      obj.w = this.cropW
      obj.h = this.cropH
      obj.url = this.imgs
      this.$emit('realTime',  obj)
    },
    // reload 图片布局函数
    reload () {
      let img = new Image
      img.onload = () => {
        // 读取图片的信息原始信息, 解析是否需要旋转
        // 读取图片的旋转信息
        // 得到外层容器的宽度高度
        this.w =  ~~(window.getComputedStyle(this.$refs.cropper).width.replace('px', ''))
        this.h =  ~~(window.getComputedStyle(this.$refs.cropper).height.replace('px', ''))
        // 存入图片真实高度
        this.trueWidth = img.width
        this.trueHeight = img.height
        // 判断是否需要压缩大图
        if (!this.original) {
          if (this.trueWidth > this.w) {
            // 如果图片宽度大于容器宽度
            this.scale = this.w / this.trueWidth
          }
          if (this.trueHeight * this.scale > this.h) {
            this.scale = this.h / this.trueHeight
          }
        } else {
          this.scale = 1
        }
        this.$nextTick(() => {
          this.x = -(this.trueWidth - this.trueWidth * this.scale) / 2 + (this.w - this.trueWidth * this.scale) / 2
          this.y = -(this.trueHeight - this.trueHeight * this.scale) / 2 + (this.h - this.trueHeight * this.scale) / 2
          this.loading = false
          // 获取是否开启了自动截图
          if (this.autoCrop) {
            this.goAutoCrop()
          }
          // 图片加载成功的回调
          this.$emit('imgLoad', 'success')
        })
      }
      img.onerror = () => {
        this.$emit('imgLoad', 'error')
      }
      img.src = this.imgs
    },
    // 自动截图函数
    goAutoCrop () {
      this.cropping = true
      // 截图框默认大小
      // 如果为0 那么计算容器大小 默认为80%
      var w = this.autoCropWidth
      var h = this.autoCropHeight
      if (w === 0 || h === 0) {
        w = this.w * 0.8
        h = this.h * 0.8
      }
      w = w > this.w ? this.w : w
      h = h > this.h ? this.h : h
      if (this.fixed) {
        h = w / this.fixedNumber[0] * this.fixedNumber[1]
      }
      // 如果比例之后 高度大于h
      if (h > this.h) {
        h = this.h
        w = h / this.fixedNumber[1] * this.fixedNumber[0]
      }
      this.changeCrop(w, h)
    },
    // 手动改变截图框大小函数
    changeCrop (w, h) {
      // 判断是否大于容器
      this.cropW = w
      this.cropH = h
      // 居中走一走
      this.cropOffsertX = (this.w - w) / 2
      this.cropOffsertY = (this.h - h) / 2
    },
    // 重置函数, 恢复组件置初始状态
    refresh () {
      // console.log('refresh')
      this.imgs = ''
      this.scale = 1
      this.crop = false
      this.rotate = 0
      this.w = 0
      this.h = 0
      this.trueWidth = 0
      this.trueHeight = 0
      this.clearCrop()
    },
    // 向左边旋转
    rotateLeft () {
      this.rotate = this.rotate <= -3 ? 0 : this.rotate - 1
    },
    // 向右边旋转
    rotateRight () {
      this.rotate = this.rotate >= 3 ? 0 : this.rotate + 1
    },
    // 清除旋转
    rotateClear () {
      this.rotate = 0
    }
  },
  mounted () {
    let that = this
    this.showPreview();
    this.checkedImg();
    // 兼容blob
    if (!HTMLCanvasElement.prototype.toBlob) {
     Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
      value: function (callback, type, quality) {
        var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
            len = binStr.length,
            arr = new Uint8Array(len)
        for (var i=0; i<len; i++ ) {
         arr[i] = binStr.charCodeAt(i)
        }
        callback( new Blob( [arr], {type: that.type || 'image/png'} ) )
      }
     })
    }
  }
}
</script>

<style scoped>
  .vue-cropper {
    position: relative;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    direction: ltr;
  }

  .cropper-box,
  .cropper-box-canvas,
  .cropper-drag-box,
  .cropper-crop-box,
  .cropper-face {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    user-select: none;
  }

  .cropper-box-canvas img {
    position: relative;
    max-width: none;
    max-height: none;
    transform: none;
    user-select: none;
  }

  .cropper-box {
    overflow: hidden;
  }

  .cropper-move {
    cursor: move;
  }

  .cropper-crop {
    cursor: crosshair;
  }

  .cropper-modal {
    background: rgba(0, 0, 0, 0.5);
  }

  .cropper-view-box {
    display: block;
    width: 100%;
    height: 100%;
    overflow: hidden;
    outline: 1px solid #ccc;
    outline-color: rgba(51, 153, 255, 0.75);
    user-select: none;
  }

  .cropper-view-box img {
    max-width: none;
    max-height: none;
    user-select: none;
  }

  .cropper-face {
    top: 0;
    left: 0;
    background-color: #fff;
    opacity: 0.1;
  }

  .crop-info {
    position: absolute;
    left: 0;
    min-width: 65px;
    font-size: 12px;
    line-height: 20px;
    color: white;
    text-align: center;
    background-color: rgba(0, 0, 0, 0.8);
  }

  .crop-line {
    position: absolute;
    display: block;
    width: 100%;
    height: 100%;
    opacity: 0.1;
  }

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

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

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

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

  .crop-point {
    position: absolute;
    width: 8px;
    height: 8px;
    background-color: #39f;
    border-radius: 100%;
    opacity: 0.75;
  }

  .point1 {
    top: -4px;
    left: -4px;
    cursor: nw-resize;
  }

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

  .point3 {
    top: -4px;
    right: -4px;
    cursor: ne-resize;
  }

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

  .point5 {
    top: 50%;
    right: -4px;
    margin-top: -3px;
    cursor: w-resize;
  }

  .point6 {
    bottom: -5px;
    left: -4px;
    cursor: sw-resize;
  }

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

  .point8 {
    right: -4px;
    bottom: -5px;
    cursor: nw-resize;
  }

  @media screen and (max-width: 500px) {
    .crop-point {
      position: absolute;
      width: 20px;
      height: 20px;
      background-color: #39f;
      border-radius: 100%;
      opacity: 0.45;
    }

    .point1 {
      top: -10px;
      left: -10px;
    }

    .point2,
    .point4,
    .point5,
    .point7 {
      display: none;
    }

    .point3 {
      top: -10px;
      right: -10px;
    }

    .point4 {
      top: 0;
      left: 0;
    }

    .point6 {
      bottom: -10px;
      left: -10px;
    }

    .point8 {
      right: -10px;
      bottom: -10px;
    }
  }
</style>

在需要的文件中使用 template

<div class="header-box">
     <img :src="firePic" alt="" class="header-img" v-show="firePic">
      <input
        accept="image/jpeg, image/png, image/jpg"
        id="firePic"
        style="display:none"
        type="file"
        @change="onFileChange('firePic',$event)"
         />
        <span @click="imgClick('firePic')" class="button-header">
           替换头像
        </span>
 </div>


// 用 dialog 包含 组件 
 <el-dialog :visible.sync="cropperModel" width="330px" :close-on-click-modal="false" :show-close="false">
            <cc-cur
              :img="option.img"
              :outputSize="option.size"
              :outputType="option.outputType"
              :info="true"
              :full="option.full"
              :canMove="option.canMove"
              :canMoveBox="option.canMoveBox"
              :original="option.original"
              :autoCrop="option.autoCrop"
              :autoCropWidth="option.autoCropWidth"
              :autoCropHeight="option.autoCropHeight"
              :fixedBox="option.fixedBox"
              :fixed="option.fixed"
              :fixedNumber="option.fixedNumber"
              style="width: 300px;height: 300px;"
              ref="cropper"
            />
            <div class="footer-btn">
               <el-button @click="changeImg('add')">
                  <i class="el-icon-plus fnt"></i>
               </el-button>
               <el-button @click="changeImg('dele')">
                  <i class="el-icon-minus fnt"></i>
               </el-button>
               <el-button @click="cropperModel = false">
                  取 消
               </el-button>
               <el-button type="primary" @click="getImg">
                  确 定
               </el-button>
            </div>
</el-dialog>

script

import Curppor from "@/views/common/components/Cropper.vue";
components:{
 "cc-cur": Curppor,
}
//  ts 写法   可用js替换
  private option: any = {
        img: "", // 裁剪图片的地址  (默认:空)
        outputSize: 1, // 裁剪生成图片的质量  (默认:1)
        full: false, // 是否输出原图比例的截图 选true生成的图片会非常大  (默认:false)
        outputType: "png", // 裁剪生成图片的格式  (默认:jpg)
        canMove: false, // 上传图片是否可以移动  (默认:true)
        original: false, // 上传图片按照原始比例渲染  (默认:false)
        canMoveBox: true, // 截图框能否拖动  (默认:true)
        autoCrop: true, // 是否默认生成截图框  (默认:false)
        autoCropWidth: 120, // 默认生成截图框宽度  (默认:80%)
        autoCropHeight: 120, // 默认生成截图框高度  (默认:80%)
        fixedBox: true, // 固定截图框大小 不允许改变  (默认:false)
        fixed: true, // 是否开启截图框宽高固定比例  (默认:true)
        fixedNumber: [1, 1], // 截图框比例  (默认:[1:1])
        enlarge: 1,
     };
     private clickNum: number = 9; // 可点击缩放次数
     private btrLen: number = 0;  // 字节长度

// 对应方法

private imgClick(val: string) { // 点击替换头像按钮
        const input: any = document.getElementById(val);
        input.click();
     }

     private onFileChange(type: string, e: any) {
         const fileInput = e.target;
         const file = fileInput.files[0];
         if (fileInput.files.length === 0) {
            return;
         }
         this.cropperModel = true;
        //  将文件流文件 转成base64 放入裁剪框中
         const reader = new FileReader();
         reader.readAsDataURL(file);
         reader.onload = () => {
            this.option.img = reader.result;
         };
     }

     private changeImg(type: string) {
       const cropper: any = this.$refs.cropper;
       if (type === "add") {
          if (this.clickNum < 19) {
            cropper.changeScale(1);
            this.clickNum++;
          } else {
            this.$message.success("已放大到最大倍数");
          }
       } else {
          if (this.clickNum > 0) {
            cropper.changeScale(-1);
            this.clickNum--;
          } else {
            this.$message.success("已缩小到最小倍数");
          }
       }
     }
 private dataURLtoFile(dataurl: string) {// base64转换为文件
      const arr: any = dataurl.split(",");
      const mime = arr[0].match(/:(.*?);/)[1];
      const bstr: string = atob(arr[1]);
      this.btrLen = bstr.length;
      const u8arr = new Uint8Array(this.btrLen);
      while (this.btrLen --) {
          u8arr[this.btrLen ] = bstr.charCodeAt(this.btrLen);
      }
      return new File([u8arr], "shoplog.png", {type: mime});
    }
 private getImg() {  
        const cropper: any = this.$refs.cropper;
        cropper.getCropData((data: any) => {  // 确定后将base64文件转为文件流 传给后端拿到图片地址  
          this.oss.uploadPic(this.dataURLtoFile(data)).then((url: string) => {  //结合自身修改
            this.firePic = url;
            this.cropperModel = false;
            this.$message.success("修改店铺头像成功");
          });
        });
     }

效果预览

1
点击替换头像
修改后上传
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355