vant+cropperjs 实现图片上传和图片截取

完整代码

<template>
  <div class="upload">
    <van-overlay :show="show"
      :z-index="9999">
      <img src=""
        ref="image"
        alt="">
      <van-button type="info"
        class="btn"
        @click="SaveCropper"
        block>确定</van-button>
    </van-overlay>
    <van-field :name="prop"
      :required="required"
      :rules="rules"
      :label="label">
      <template #input>
        <van-uploader v-model="fileList"
          :after-read="afterRead"
          :max-count="maxCount"
          :max-size="fileMaxSize"
          @delete="del"
          accept="accept"
          @oversize="onOversize" />
      </template>
    </van-field>
  </div>

</template>

<script>
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css'
export default {
  props: {
    value: {
      type: [String, Array],
      default: () => []
    },
    label: {
      type: String,
      default: () => ''
    },
    accept: {
      type: String,
      default: () => 'png, jpeg, jpg, pdf, zip, docx, doc, xls, xlsx'
    },
    prop: {
      type: String,
      default: () => ''
    },
    // 上传个数限制
    maxCount: {
      type: Number,
      default: () => 1
    },
    // 最大上传大小的限制 默认为1
    maxSize: {
      type: Number,
      default: () => 1
    },
    // 是否时必填项
    required: {
      type: Boolean,
      default: () => false
    },
    // 如果是必填项 则需要传入验证规则
    rules: {
      type: Array,
      default: () => []
    },
    isCutting: {
      type: Boolean, // 是否需要图片截取
      default: () => false
    }
  },

  data() {
    return {
      showPicker: false,
      fileList: [],
      show: false
    }
  },
  computed: {
    fileMaxSize: function () {
      return this.maxSize * 1024 * 1024
    }
  },
  methods: {
    /**
     * 上传附件转base64
     * @param {File} file 文件流
     */
    fileByBase64(file, callback) {
      if (!file) {
        return
      }
      const reader = new FileReader()
      // 传入一个参数对象即可得到基于该参数对象的文本内容
      reader.readAsDataURL(file)
      reader.onload = function (e) {
        // target.result 该属性表示目标对象的DataURL
        if (callback) {
          callback(e.target.result)
        } else {
          console.error('必须传入回调函数')
        }
      }
    },
    afterRead() {
      // 此时可以自行将文件上传至服务器
      this.$nextTick(() => {
        const reader = new FileReader()
        const files = this.fileList[0]
        const _this = this
        if (files.file.type === 'image/png' || files.file.type === 'image/jpeg') {
          // 在此判断是否需要图片截取
          if (this.isCutting) {
            const { file } = files
            this.show = true
            this.$nextTick(() => {
              this.cropper = new Cropper(this.$refs.image, {
                aspectRatio: 1 / 1,
                viewMode: 1,
                background: false,
                zoomable: true,
                scalable: false,
                highlight: false,
                dragMode: 'none',
                ready: function () {
                  self.croppable = true
                }
              })
              this.fileByBase64(file, (res) => {
                this.panel = true
                this.picValue = file

                this.url = res
                // 每次替换图片要重新得到新的url
                if (this.cropper) {
                  this.cropper.replace(this.url)
                }
                this.panel = true
              })
            })
          }
          // 判断是否需要图片压缩
          if (files.file.size / 1024 <= 200) {
            this.$emit('input', [files])
            return
          }
          reader.readAsDataURL(files.file)
          reader.onload = function () {
            files.file = _this.compress(this.result, files.file)
          }
        }
        this.$emit('input', [files])
      })
    },
    onOversize(file) {
      this.$toast(`文件大小超出限制,上传文件大小不能超过 ${this.maxSize}M`)
    },
    del() {
      this.$emit('input', this.fileList)
    },
    compress(base64, file) {
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      const img = new Image()
      img.src = base64
      let width = img.width
      let height = img.height
      const tCanvas = document.createElement('canvas')
      const tctx = tCanvas.getContext('2d')
      // 如果图片大于四百万像素,计算压缩比并将大小压至400万以下
      let ratio
      // 判断图片像素是否是四百万
      if ((ratio = (width * height) / 4000000) > 1) {
        ratio = Math.sqrt(ratio)
        width /= ratio
        height /= ratio
      } else {
        ratio = 1
      }
      canvas.width = width
      canvas.height = height
      // 铺底色
      ctx.fillStyle = '#fff'
      ctx.fillRect(0, 0, canvas.width, canvas.height)
      // 如果图片像素大于100万则使用瓦片绘制
      let count
      if ((count = (width * height) / 1000000) > 1) {
        count = ~~(Math.sqrt(count) + 1) // 计算要分成多少块瓦片
        //  计算每块瓦片的宽和高
        const nw = ~~(width / count)
        const nh = ~~(height / count)
        tCanvas.width = nw
        tCanvas.height = nh
        for (let i = 0; i < count; i++) {
          for (let j = 0; j < count; j++) {
            tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh)

            ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh)
          }
        }
      } else {
        ctx.drawImage(img, 0, 0, width, height)
      }

      // 判断图片大小是否超过1M 超过1M按最小比压缩
      let ndata = null
      if (file.size / 1024 / 1024 < 1) {
        ndata = canvas.toDataURL('image/jpeg', 0.5)
      } else {
        ndata = canvas.toDataURL('image/jpeg', 0.1)
      }
      tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0
      const files = this.dataURLtoFile(ndata, file.name, file.type)
      return files
    },
    // 将base64转换为file对象
    dataURLtoFile(dataurl, filename, filetype) {
      const arr = dataurl.split(',')
      const bstr = atob(arr[1])
      let n = bstr.length
      const u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], filename, {
        type: filetype
      })
    },
    async getAvatarImageUrl1(file) {
      const formData = new FormData()
      formData.append('file', file) // 固定格式
      // const res = await upload(formData)
      // if (res) {
      //   this.img1 = res.data[0]
      //   console.log(res)
      // }
    },
    async getAvatarImageUrl2(file) {
      const formData = new FormData()
      formData.append('file', file) // 固定格式
      // const res = await upload(formData)
      // if (res) {
      //   this.img2 = res.data[0]
      //   console.log(res)
      // }
    },
    SaveCropper() {
      const files = this.fileList[0].file
      const type = files.type
      const croppedCanvas = this.cropper.getCroppedCanvas().toDataURL(type)
      const blob = this.dataURLtoBlob(croppedCanvas)
      const name = files.name
      const file = {
        file: new window.File([blob], name, { type: blob.type }),
        content: croppedCanvas,
        message: '',
        status: ''
      }
      this.fileList = [file]
      this.$emit('input', [file])
      this.show = false
    },
    dataURLtoBlob(dataurl) {
      const arr = dataurl.split(',')
      const mime = arr[0].match(/:(.*?);/)[1]
      const bstr = atob(arr[1])
      let n = bstr.length
      const u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new Blob([u8arr], { type: mime })
    }
  },
  watch: {
    value: {
      handler(v) {
        if (Array.isArray(v)) {
          const fileList = v.map((item) => {
            if (item.url) {
              item.url = item.split(';')[0]
            }
            return item
          })
          this.$set(this, 'fileList', fileList)
          return
        }
        this.$set(this, 'fileList', [{ url: v.split(';')[0] }])
      }
    }
  }
}
</script>

<style lang="scss">
.upload {
  .portrait {
    width: 60px;
    height: 60px;
    background-color: #000;
    border-radius: 8px;
    float: right;
  }
  .van-cell__right-icon {
    line-height: 66px;
    margin-left: 22px;
  }
  .van-cell--required {
    line-height: 66px;
  }
  .btn {
    position: fixed;
    bottom: 0;
  }
  #demo #button {
    //这是截图按钮,样式可以自己自定义!!!!!
    position: absolute;
    right: 10px;
    top: 10px;
    width: 80px;
    height: 40px;
    border: none;
    border-radius: 5px;
    background: white;
  }
  #demo .container {
    //这是绘图层内容,注意这里的宽高必须百分比满屏!!!!否则绘图层只是图片的大小
    z-index: 99;
    position: fixed;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 1);
  }

  #demo #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;
  }

  .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: 0.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: 0.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: 0.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: 0.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: 0.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: 0.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');
  }

  .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>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值