vue3 + el-upload +VuePictureCropper

组件下载

npm install vue-picture-cropper

组件文档

https://cropper.chengpeiquan.com/zh/quick-start.html

弹窗:裁剪组件(ImgCorpper.vue)

<template>
  <el-dialog v-model="isShowModal" title="Image Cropping" width="50%" :close-on-press-escape="false"
    :close-on-click-modal="false" @close="cancel" align-center>
    <div class="modal-content">
      <VuePictureCropper :img="pic" @ready="ready" :boxStyle="corpBoxStyle" :options="corpOptions"
        :presetMode="corpPresetMode" />
    </div>
    <div class="m-t-10 btns">
      <el-button class="default-btn" @click="cancel">Cancel</el-button>
      <el-button v-if="showClear" class="default-btn" @click="clear">Clear</el-button>
      <el-button v-if="showReset" class="default-btn" @click="reset">Reset</el-button>
      <el-button class="default-btn primary" @click="getResult">Confirm</el-button>
    </div>
  </el-dialog>
</template>

<script setup>
import { reactive, ref, watch, computed } from 'vue'
// 组件文档:https://cropper.chengpeiquan.com/zh/quick-start.html
import VuePictureCropper, { cropper } from 'vue-picture-cropper'
import { PhotoCompress } from "@utils/compressUtil.js"

const props = defineProps({
  visible: {
    type: Boolean
  },
  image: {
    type: [String, File]
  },
  presetMode: {
    type: Object
  },
  options: {
    type: Object
  },
  boxStyle: {
    type: Object
  },
  showClear: { // 是否显示 Clear 按钮
    type: Boolean
  },
  showReset: { // 是否显示 Reset 按钮
    type: Boolean
  },
  isCompress: { // 是否压缩裁剪后的图片大小
    type: Boolean
  },
  compressSize: { // 压缩图片大小限制
    type: Object,
    default: {
      maxSize: 0.3 * 1024 * 1024, // 300k
      minSize: 0.03 * 1024 * 1024// 30k
    }
  }
})

/** 裁剪组件属性配置 */
const corpOptions = computed(() => {
  return {
    viewMode: 1, // 裁剪框范围 1-只能在图片区域内活动
    dragMode: 'move', // 可选值 crop(可绘制裁剪框) | move(仅移动)
    aspectRatio: 1 / 1,// 裁剪框的宽高比
    cropBoxResizable: false, // 是否可以调整裁剪区的大小
    ...props.options
  }
})
/** 裁剪区域的样式 */
const corpBoxStyle = computed(() => {
  return {
    width: 'auto',
    height: '400px',
    backgroundColor: '#f8f8f8',
    margin: 'auto',
    ...props.boxStyle
  }
})

/** 预设模式配置 */
const corpPresetMode = computed(() => {
  return {
    mode: 'fixedSize', width: 300, height: 300,
    ...props.presetMode
  }
})
const emits = defineEmits(["close"])

watch(() => props.visible, (val) => {
  isShowModal.value = val
  if (!val) {
    pic.value = ''
    result.dataURL = ''
    result.blobURL = ''
    reset()
  }
})

watch(() => props.image, (val) => {
  pic.value = val
})

const isShowModal = ref(false)
const pic = ref('')
const result = reactive({
  dataURL: '',
  blobURL: '',
})

/**
 * Get cropping results
 */
async function getResult() {
  if (!cropper) return
  const base64 = cropper.getDataURL()
  const blob = await cropper.getBlob()
  if (!blob) return

  let file = await cropper.getFile({
    fileName: "locales.fileName",
  })

  result.dataURL = base64
  result.blobURL = URL.createObjectURL(blob)
  const { minSize, maxSize } = props.compressSize
  // console.log(file.size, maxSize)
  // 是否限制文件大小
  if (props.isCompress && (file.size > maxSize)) {
    // console.log("compress")
    const photoCompress = new PhotoCompress(minSize, maxSize);
    photoCompress.compress(file, function (f) {
      const r = new FileReader()
      r.readAsDataURL(f)
      r.onload = function (e) {
        emits("close", { result, file: f })
      }
    });
  } else {
    // console.log("crop")
    emits("close", { result, file })
  }
}

/**
 * Clear the crop box
 */
function clear() {
  if (!cropper) return
  cropper.clear()
}

/**
 * Reset the default cropping area
 */
function reset() {
  if (!cropper) return
  cropper.reset()
}

/**
 * The ready event passed to Cropper.js
 */
function ready() {
  // console.log('Cropper is ready.')
}

function cancel() {
  emits("close")
}
</script>

<style lang="scss" scoped>
.modal-wrap {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  z-index: 99;
}

.btns {
  display: flex;
  justify-content: flex-end;
}
</style>

图片压缩方法(compressUtil.js)

/**
 * 图片压缩类
 * @param minSize
 * @param maxSize
 * @constructor
 */
export function PhotoCompress(minSize, maxSize) {
  var nextQ = 0.5; // 压缩比例
  var maxQ = 1;
  var minQ = 0;

  /**
   * 将base64转换为文件
   * @param base64Codes base64编码
   * @param fileName 文件名称
   * @returns {*}
   */
  PhotoCompress.prototype.dataUrlToFile = function (base64Codes, fileName = new Date().getTime()) {
    var arr = base64Codes.split(','),
      mime = arr[0].match(/:(.*?);/)[1],
      bStr = atob(arr[1]),
      n = bStr.length,
      u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bStr.charCodeAt(n);
    }
    return new File([u8arr], fileName, { type: mime });
  }

  /**
   * 图片压缩
   * @param file 文件
   * @param callback 回调函数
   */
  PhotoCompress.prototype.compress = function (file, callback) {
    var self = this;
    self.imgBase64(file, function (image, canvas) {
      var base64Codes = canvas.toDataURL(file.type, nextQ); // y压缩
      var compressFile = self.dataUrlToFile(base64Codes, file.name.split('.')[0]); // 转成file文件
      var compressFileSize = compressFile.size; // 压缩后文件大小 k
      console.log("图片质量:" + nextQ);
      console.log("压缩后文件大小:" + compressFileSize / 1024);
      if (compressFileSize > maxSize) { // 压缩后文件大于最大值
        maxQ = nextQ;
        nextQ = (nextQ + minQ) / 2; // 质量降低
        self.compress(file, callback);
      } else if (compressFileSize < minSize) { // 压缩以后文件小于最小值
        minQ = nextQ;
        nextQ = (nextQ + maxQ) / 2; // 质量提高
        self.compress(file, callback);
      } else {
        callback(compressFile);
      }
    });
  }

  /**
   * 将图片转化为base64
   * @param file 文件
   * @param callback 回调函数
   */
  PhotoCompress.prototype.imgBase64 = function (file, callback) {
    // 看支持不支持FileReader
    if (!file || !window.FileReader) return;
    var image = new Image();
    // 绑定 load 事件处理器,加载完成后执行
    image.onload = function () {
      var canvas = document.createElement('canvas')
      var ctx = canvas.getContext('2d')
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      canvas.width = image.width * nextQ;
      canvas.height = image.height * nextQ;
      ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
      callback(image, canvas);
    };
    if (/^image/.test(file.type)) {
      // 创建一个reader
      var reader = new FileReader();
      // 将图片将转成 base64 格式
      reader.readAsDataURL(file);
      // 读取成功后的回调
      reader.onload = function () {
        // self.imgUrls.push(this.result);
        // 设置src属性,浏览器会自动加载。
        // 记住必须先绑定事件,才能设置src属性,否则会出同步问题。
        image.src = this.result;
      }
    }
  }
};

组件使用

<template>
<el-form size="large" label-position="left">
  <el-form-item prop="avatar" label="Avatar:" :required="true">
    <el-upload list-type="picture-card" :auto-upload="true" :show-file-list="false" :before-upload="beforeUpload">
      <img class="preview-img" v-if="formData.avatar" :src="formData.avatar" />
      <el-icon v-else class="avatar-uploader-icon">
        <Plus />
      </el-icon>
    </el-upload>
  </el-form-item>
  <el-button class="default-btn" type="primary" @click="saveForm" size="default">Save</el-button>
 </el-form>
<!-- 图片裁剪组件 -->
<ImgCorpper :visible="isShowCorpper" :image="selectPic" @close="hideCorpper" :isCompress="true"/>
</template>
<script setup>
import { ref } from "vue"
import ImgCorpper from '@components/ImgCorpper.vue'

let saveFile = ref()
let selectPic = ref("")
let isShowCorpper = ref(false)

/** 选择文件 */
function beforeUpload(file) {
  const reader = new FileReader()
  reader.readAsDataURL(file)
  reader.onload = () => {
    selectPic.value = String(reader.result)
    isShowCorpper.value = true
  }
  return false
}

function hideCorpper(data) {
  isShowCorpper.value = false
  if (data) {
    const { result: { dataURL }, file } = data
    formData.value.avatar = dataURL;
    saveFile.value = file
  }
}
function saveForm() {
  console.log(saveFile.value)
}
</script>

<style lang="scss" scoped>
.avatar-box {
  width: 300px;
  display: flex;
  justify-content: center;
}

.preview-img {
  width: 100%;
  height: auto;
  border-radius: 6px;
}
</style>
GitHub 加速计划 / vu / vue
87
16
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:6 个月前 )
9e887079 [skip ci] 4 个月前
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 8 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐