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

2023-11-03

完整代码

<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>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vant+cropperjs 实现图片上传和图片截取 的相关文章

随机推荐

  • 低功耗蓝牙(BLE)

    https my oschina net tingzi blog 215008 低功耗蓝牙包括的术语及概念 如上图所示 使用低功耗蓝牙可以包括多个Profile 一个Profile中有多个Service 一个Service中有多个Chara
  • CGAL+QT5配置一路踩坑总结

    总体流程参照 CGAL 5 5 Manual Using CGAL on Windows with Visual C 11条消息 通过vcpkg安装 配置 CGAL 5 2 1 Oskar Lu的博客 CSDN博客 vcpkg配置 11条消
  • ubuntu22设置静态IP 及 无线网卡启停

    标题 查看网络状态 ifconfig 开关无线网卡 sudo ifconfig wlo1 up down 搜索并显示wifi信号 sudo nmcli device wifi rescan nmcli device wifi list 连接
  • 文盘Rust -- 生命周期问题引发的 static hashmap 锁

    2021年上半年 撸了个rust cli开发的框架 基本上把交互模式 子命令提示这些cli该有的常用功能做进去了 项目地址 https github com jiashiwen interactcli rs 春节以前看到axum已经0 4
  • 路径参数和url传参

    请求参数的三种方式 第一种路径参数 后端接收 其中 PathVariabel注解的形参名字要与路径参数形参名字相等 不相等就用 value值来与路径参数名字相等 第二种url地址传参 url xxx abc get请求只能传query参数
  • 计算机白板记录,电子白板:计算机接口介绍

    电子白板 计算机接口介绍 接口类型指的是电子白板与电脑系统采用何种方式进行连接 电子白板 什么是计算机接口 目前电子白板与电脑连接常见的接口类型 有并口 也有称之为ieee1284 centronics 和串口 也有称之为rs 232接口的
  • java进阶Kafka集群实战之原理分析及优化教程全在这里

    我不去想是否能够成功 既然选择了Java 便只顾风雨兼程 我不去想能否征服Kafka集群 既然钟情于Java 就勇敢地追随千锋 我不去想Kafka集群有多么晦涩难懂 既然目标是远方 留给世界的只能是努力拼搏的背影 我不去想未来是平坦还是泥泞
  • 10.两个链表相加求和

    题目 假设链表中每一个节点的值都在 0 9 之间 那么链表整体就可以代表一个整数 给定两个这种链表 请生成代表两个整数相加值的结果链表 思路 首先是逆序相加 故这里用到了两个栈 分别存放两个链表中的值 两数相加会涉及到进位问题 所以考虑了进
  • ZeroQuant与SmoothQuant量化总结

    ref ZeroQuant Efficient and Affordable Post Training Quantization for Large Scale Transformers SmoothQuant Accurate and
  • qt程序运行时无任何错误提示信息,但是无法进入main函数

    问题 开发环境 win7 vs2013 qt 5 9 1 64位程序 qt程序运行时无任何错误提示信息 但是无法进入main函数 原因 程序中使用了visa库 使用的库的版本有问题
  • #ifndef, #define, #endif区别和使用

    文件中的 ifndef 头件的中的 ifndef 这是一个很关键的东西 比如你有两个C文件 这两个C文件都include了同一个头文件 而编译时 这两个C文件要一同编译成一个可运行文件 于是问题来了 大量的声明冲突 还是把头文件的内容都放在
  • 专访京东孙海波:大牛架构师养成记及电商供应链中区块链技术的应用

    编者按 每个人的成长曲线不同 有的人在研究生之时就已有相当知名的产品和框架 从而在接下来的工作中一路顺风顺水 有的人缺需要经历一个又一个的坑才能成长 不管是前者的聪明高效 还是后者的笨鸟先飞 他们都是在迈着脚步不断地向前 不妨 我们停下脚步
  • VS永久配置Opencv 和 Cmake配置OpenCV_contrib扩展模块 (整体思路 + 注意事项)

    1 VS永久配置Opencv 1 1 在opencv官网下载后并安装Opencv 官网地址 https opencv org 1 2 安装完成后 配置opencv的环境变量 1 3 在VS中配置opencv的属性表 步骤如下 1 在VS新建
  • 分割字符串的方法

    1 split 将一个字符串分割为子字符串 然后将结果作为字符串数组返回 2 indexOf 返回某个指定的字符串值在字符串中首次出现的位置 从左向右 没有匹配的则返回 1 否则返回首次出现位置的字符串的下标值 3 substr start
  • adb几个常见命令

    ADB Android Debug Bridge 调试桥 1 将设备连接到电脑 命令 adb connect 127 0 0 1 62001 2 检查设备连接情况 命令 adb devices 3 发送文件到设备 命令 adb push d
  • 基于Python的大数据的人才招聘数据分析与可视化平台应聘兼职-爬虫安装项目定制算法作业代做计算机毕业设计源代码

    更多项目资源 最下方联系IT实战课堂 博主拥有多年的T技术研发项目架构和教学经验 CSDN 51CTO 腾讯课堂等平台优质作者 高级讲师 培训机构联合创始人 现专注项目定制Java 小程序 前端网页 Python App NodeJs PH
  • STL之rotate

    STL对左旋转字符串针对三种不同数据结构进行了不同的实现 对单向链表采用的是同步位移 双向链表是三次翻转 都很简单 主要看看针对随机存取的数组做的循环位移实现 STL这个版本的源码如下 cpp view plain copy templat
  • UniCode 下 CString 转 char* 的方法

    1 Unicode下CString转换为char 方法一 使用API WideCharToMultiByte进行转换 CString str T D 校内项目 QQ bmp 注意 以下n和len的值大小不同 n是按字符计算的 len是按字节
  • (java功能篇)谷歌获取地址经纬度

    package com mohe map import java io BufferedReader import java io IOException import java io InputStreamReader import ja
  • vant+cropperjs 实现图片上传和图片截取

    完整代码