perf: 优化ImageCropping

This commit is contained in:
kailong321200875 2023-12-27 16:52:33 +08:00
parent 85f71d457b
commit 069777c880
3 changed files with 215 additions and 28 deletions

View File

@ -1,12 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { nextTick, unref, ref, watch, onBeforeUnmount, onMounted } from 'vue' import { nextTick, unref, ref, watch, onBeforeUnmount, onMounted, computed } from 'vue'
import Cropper from 'cropperjs' import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.min.css' import 'cropperjs/dist/cropper.min.css'
import { ElDivider, ElUpload, UploadFile, ElMessage, ElTooltip } from 'element-plus'
// interface CropperOptions extends /* @vue-ignore */ Cropper.Options { import { useDebounceFn } from '@vueuse/core'
// imageUrl: string import { BaseButton } from '@/components/Button'
// }
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
@ -25,9 +24,63 @@ const props = defineProps({
cropBoxHeight: { cropBoxHeight: {
type: Number, type: Number,
default: 200 default: 200
},
boxWidth: {
type: [Number, String],
default: 425
},
boxHeight: {
type: [Number, String],
default: 320
},
showResult: {
type: Boolean,
default: true
},
showActions: {
type: Boolean,
default: true
} }
}) })
const getBase64 = useDebounceFn(() => {
imgBase64.value = unref(cropperRef)?.getCroppedCanvas()?.toDataURL() ?? ''
}, 80)
const resetCropBox = () => {
const containerData = unref(cropperRef)?.getContainerData()
unref(cropperRef)?.setCropBoxData({
width: props.cropBoxWidth,
height: props.cropBoxHeight,
left: (containerData?.width || 0) / 2 - 100,
top: (containerData?.height || 0) / 2 - 100
})
imgBase64.value = unref(cropperRef)?.getCroppedCanvas()?.toDataURL() ?? ''
}
const getBoxStyle = computed(() => {
return {
width: `${props.boxWidth}px`,
height: `${props.boxHeight}px`
}
})
const getCropBoxStyle = computed(() => {
return {
width: `${props.cropBoxWidth}px`,
height: `${props.cropBoxHeight}px`
}
})
//
const getScaleSize = (scale: number) => {
return {
width: props.cropBoxWidth * scale + 'px',
height: props.cropBoxHeight * scale + 'px'
}
}
const imgBase64 = ref('')
const imgRef = ref<HTMLImageElement>() const imgRef = ref<HTMLImageElement>()
const cropperRef = ref<Cropper>() const cropperRef = ref<Cropper>()
const intiCropper = () => { const intiCropper = () => {
@ -37,22 +90,61 @@ const intiCropper = () => {
aspectRatio: 1, aspectRatio: 1,
viewMode: 1, viewMode: 1,
dragMode: 'move', dragMode: 'move',
cropBoxResizable: false, // cropBoxResizable: false,
cropBoxMovable: false, // cropBoxMovable: false,
toggleDragModeOnDblclick: false, toggleDragModeOnDblclick: false,
checkCrossOrigin: false, checkCrossOrigin: false,
ready() { ready() {
const containerData = unref(cropperRef)?.getContainerData() resetCropBox()
unref(cropperRef)?.setCropBoxData({ },
width: props.cropBoxWidth, cropmove() {
height: props.cropBoxHeight, getBase64()
left: (containerData?.width || 0) / 2 - 100, },
top: (containerData?.height || 0) / 2 - 100 zoom() {
}) getBase64()
},
crop() {
getBase64()
} }
}) })
} }
const uploadChange = (uploadFile: UploadFile) => {
//
if (uploadFile?.raw?.type.indexOf('image') === -1) {
ElMessage.error('请上传图片格式的文件')
return
}
if (!uploadFile.raw) return
// 访
const url = URL.createObjectURL(uploadFile.raw)
unref(cropperRef)?.replace(url)
}
const reset = () => {
unref(cropperRef)?.reset()
}
const rotate = (deg: number) => {
unref(cropperRef)?.rotate(deg)
}
const scaleX = ref(1)
const scaleY = ref(1)
const scale = (type: 'scaleX' | 'scaleY') => {
if (type === 'scaleX') {
scaleX.value = scaleX.value === 1 ? -1 : 1
unref(cropperRef)?.[type](unref(scaleX))
} else {
scaleY.value = scaleY.value === 1 ? -1 : 1
unref(cropperRef)?.[type](unref(scaleY))
}
}
const zoom = (num: number) => {
unref(cropperRef)?.zoom(num)
}
onMounted(() => { onMounted(() => {
intiCropper() intiCropper()
}) })
@ -60,9 +152,10 @@ onMounted(() => {
watch( watch(
() => props.imageUrl, () => props.imageUrl,
async (url) => { async (url) => {
await nextTick()
if (url) { if (url) {
unref(cropperRef)?.replace(url) unref(cropperRef)?.replace(url)
await nextTick()
resetCropBox()
} }
} }
) )
@ -72,19 +165,95 @@ onBeforeUnmount(() => {
}) })
defineExpose({ defineExpose({
cropperExpose: () => unref(cropperRef) cropperExpose: cropperRef
}) })
</script> </script>
<template> <template>
<div :class="prefixCls" class="flex justify-center items-center"> <div
<img :class="{
ref="imgRef" [prefixCls]: true,
:src="imageUrl" 'flex items-center': showResult
class="block max-w-full" }"
crossorigin="anonymous" >
alt="" <div>
srcset="" <div :style="getBoxStyle" class="flex justify-center items-center">
/> <img
v-show="imageUrl"
ref="imgRef"
:src="imageUrl"
class="block max-w-full"
crossorigin="anonymous"
alt=""
srcset=""
/>
</div>
<div v-if="showActions" class="mt-10px flex items-center">
<div class="flex items-center">
<ElTooltip content="选择文件" placement="bottom">
<ElUpload
action="''"
accept="image/*"
:auto-upload="false"
:show-file-list="false"
:on-change="uploadChange"
>
<BaseButton size="small" type="primary" class="mt-2px"
><Icon icon="ep:upload-filled"
/></BaseButton>
</ElUpload>
</ElTooltip>
</div>
<div class="flex items-center justify-end flex-1">
<ElTooltip content="重置" placement="bottom">
<BaseButton size="small" type="primary" @click="reset"
><Icon icon="ep:refresh"
/></BaseButton>
</ElTooltip>
<ElTooltip content="逆时针旋转" placement="bottom">
<BaseButton size="small" type="primary" @click="rotate(-45)"
><Icon icon="ant-design:rotate-left-outlined"
/></BaseButton>
</ElTooltip>
<ElTooltip content="顺时针旋转" placement="bottom">
<BaseButton size="small" type="primary" @click="rotate(45)"
><Icon icon="ant-design:rotate-right-outlined"
/></BaseButton>
</ElTooltip>
<ElTooltip content="水平翻转" placement="bottom">
<BaseButton size="small" type="primary" @click="scale('scaleX')"
><Icon icon="vaadin:arrows-long-h"
/></BaseButton>
</ElTooltip>
<ElTooltip content="垂直翻转" placement="bottom">
<BaseButton size="small" type="primary" @click="scale('scaleY')"
><Icon icon="vaadin:arrows-long-v"
/></BaseButton>
</ElTooltip>
<ElTooltip content="放大" placement="bottom">
<BaseButton size="small" type="primary" @click="zoom(0.1)"
><Icon icon="ant-design:zoom-in-outlined"
/></BaseButton>
</ElTooltip>
<ElTooltip content="缩小" placement="bottom">
<BaseButton size="small" type="primary" @click="zoom(-0.1)"
><Icon icon="ant-design:zoom-out-outlined"
/></BaseButton>
</ElTooltip>
</div>
</div>
</div>
<div v-if="imgBase64 && showResult" class="ml-20px">
<div class="flex justify-center items-center">
<img :src="imgBase64" class="rounded-[50%]" :style="getCropBoxStyle" />
</div>
<ElDivider />
<div class="flex justify-center items-center">
<img :src="imgBase64" class="rounded-[50%]" :style="getScaleSize(0.2)" />
<img :src="imgBase64" class="rounded-[50%] ml-20px" :style="getScaleSize(0.25)" />
<img :src="imgBase64" class="rounded-[50%] ml-20px" :style="getScaleSize(0.3)" />
<img :src="imgBase64" class="rounded-[50%] ml-20px" :style="getScaleSize(0.35)" />
</div>
</div>
</div> </div>
</template> </template>

View File

@ -82,7 +82,7 @@ export default {
sizeIcon: '尺寸图标', sizeIcon: '尺寸图标',
localeIcon: '多语言图标', localeIcon: '多语言图标',
tagsView: '标签页', tagsView: '标签页',
logo: '标志', logo: 'Logo',
greyMode: '灰色模式', greyMode: '灰色模式',
fixedHeader: '固定头部', fixedHeader: '固定头部',
headerTheme: '头部主题', headerTheme: '头部主题',

View File

@ -2,14 +2,22 @@
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { ImageCropping } from '@/components/ImageCropping' import { ImageCropping } from '@/components/ImageCropping'
import { ref, unref } from 'vue' import { ref, unref } from 'vue'
import { ElInput } from 'element-plus' import { ElInput, ElDivider } from 'element-plus'
const cropperExpose = ref<InstanceType<typeof ImageCropping>>() const cropperExpose = ref<InstanceType<typeof ImageCropping>>()
const base64 = ref('') const base64 = ref('')
const getBase64 = () => { const getBase64 = () => {
base64.value = unref(cropperExpose)?.cropperExpose()?.getCroppedCanvas()?.toDataURL() ?? '' base64.value = unref(cropperExpose)?.cropperExpose?.getCroppedCanvas()?.toDataURL() ?? ''
}
const cropperExpose2 = ref<InstanceType<typeof ImageCropping>>()
const base642 = ref('')
const getBase642 = () => {
base642.value = unref(cropperExpose)?.cropperExpose?.getCroppedCanvas()?.toDataURL() ?? ''
} }
</script> </script>
@ -21,5 +29,15 @@ const getBase64 = () => {
ref="cropperExpose" ref="cropperExpose"
image-url="https://hips.hearstapps.com/hmg-prod/images/%E5%AE%8B%E6%99%BA%E5%AD%9D-1597774015.jpg?crop=0.500xw:1.00xh;0.500xw,0&resize=640:*" image-url="https://hips.hearstapps.com/hmg-prod/images/%E5%AE%8B%E6%99%BA%E5%AD%9D-1597774015.jpg?crop=0.500xw:1.00xh;0.500xw,0&resize=640:*"
/> />
<ElDivider />
<BaseButton type="primary" class="mb-20px" @click="getBase642">裁剪</BaseButton>
<ElInput v-model="base642" class="mb-20px" type="textarea" />
<ImageCropping
ref="cropperExpose2"
:show-actions="false"
box-width="100%"
:show-result="false"
image-url="https://hips.hearstapps.com/hmg-prod/images/%E5%AE%8B%E6%99%BA%E5%AD%9D-1597774015.jpg?crop=0.500xw:1.00xh;0.500xw,0&resize=640:*"
/>
</ContentWrap> </ContentWrap>
</template> </template>