h5使用VueCropper做图片裁剪

24 阅读1分钟

效果图

image.png

组件代码:CropperImg.vue

<template>
  <van-popup
    v-model:show="show"
    position="bottom"
    :lock-scroll="false"
    :style="{ height: '100vh', overflow: 'hidden' }"
  >
    <div style="width: 100vw; height: 100vh; overflow: hidden">
      <VueCropper
        ref="cropperRef"
        class="cropper"
        :img="img"
        :autoCropHeight="86"
        :autoCropWidth="86"
        :autoCrop="true"
        :centerBox="true"
        :canScale="false"
        :canMove="false"
        outputType="png"
        @realTime="realTime"
      ></VueCropper>
      <div class="preview" @click="onPreview">预览</div>
      <div class="cancel" @click="onClose"><van-icon name="cross" /></div>
      <div class="save" @click="onSave"><van-icon name="success" /></div>

      <van-popup v-model:show="showPreview">
        <div style="padding: 5px">
          <div :style="previews.div" class="show-preview">
            <img v-if="previews.url" :src="previews.url" :style="previews.img" />
          </div>
        </div>
      </van-popup>
    </div>
  </van-popup>
</template>
<script setup>
import { ref } from 'vue'
import { base64ToFile } from '@/utils'
import 'vue-cropper/dist/index.css'
import { VueCropper } from 'vue-cropper'

const emits = defineEmits('confirm')
const show = ref(false)
const img = ref('')
const cropperRef = ref()
// 打开
const onOpen = (cropperImg) => {
  img.value = cropperImg
  show.value = true
}
// 实时预览
const previews = ref('')
const realTime = (data) => {
  previews.value = data
}
const showPreview = ref(false)
const onPreview = () => {
  showPreview.value = true
}
// 取消
const onClose = () => {
  show.value = false
  previews.value = ''
}
// 提交
const onSave = () => {
  cropperRef.value.getCropData((data) => {
    emits('confirm', base64ToFile(data, '头像.png'))
  })
}
defineExpose({
  onOpen,
  onClose,
})
</script>
<style lang="less" scoped>
.cropper {
  background: rgba(0, 0, 0, 1);
}
.preview,
.cancel,
.save {
  width: 40px;
  height: 40px;
  line-height: 40px;
  text-align: center;
  position: absolute;
  color: #fff;
}
.preview {
  top: 20px;
  right: 20px;
  font-size: 16px;
}
.cancel {
  bottom: 50px;
  left: 20px;
  font-size: 26px;
}
.save {
  bottom: 50px;
  right: 20px;
  font-size: 30px;
}
.show-preview {
  overflow: hidden;
  border: 1px solid #d7d7d7;
  border-radius: 50%;
}
</style>

使用示例:

<template>
  <van-cell
    title="头像"
    label="请点击头像"
    is-link
    center
    class="ptb-14"
    value-class="flex-no line-height-0"
  >
    <template #value>
      <van-uploader :after-read="onAvatarAfterRead" max-count="1">
        <img :src="headImg" class="avatar-img" />
      </van-uploader>
    </template>
  </van-cell>
  <CropperImg ref="cropperRef" @confirm="onCropperConfirm"></CropperImg>
</template>
<script setup>
import { ref } from 'vue'
import { showLoadingToast, closeToast, showToast } from 'vant'
import { uploadFile } from '@/api/file'
import CropperImg from '../CropperImg.vue'

const headImg = ref('')
// 图片裁剪
const cropperRef = ref()
const onAvatarAfterRead = (file) => {
  if (file.file.type.indexOf('image/') == -1) {
    showToast('请上传图片类型文件!')
    return false
  }
  if (file.file.size / 1024 / 1024 > 5) {
    showToast('文件大小不能超过5MB!')
    return false
  }
  cropperRef.value.onOpen(file.objectUrl)
}
const onCropperConfirm = async (file) => {
  showLoadingToast('正在提交...')
  try {
    const { data } = await uploadFile(file)
    if (!data) {
      showToast('上传失败')
      return
    }
    // todo
  } catch (e) {
    console.error(e)
  }
  closeToast()
}
</script>
<style lang="less" scoped>
.avatar-img {
  width: 50px;
  height: 50px;
  border-radius: 25px;
}
</style>
<style lang="less">
.line-height-0 {
  line-height: 0;
}
.flex-no {
  flex: none !important;
}
</style>