1. 效果
ts
const cropData = await wqCrop({
prop:{
img,
autoCrop: true, // 是否开启截图框
maxImgSize: 600,
autoCropWidth: 30,
canMove: true, // 图片是否可移动
canMoveBox: true, // 截图框是否可移动
fixedBox: false, // 截图框是否固定
}
});
console.log(cropData);
使用
wqCrop
会开启一个截图弹出框, 当截取完毕后会返回一个Promise<{file: File, blob:Blob, data: string} | null>
, 为截取后的图片
2. 代码
type.ts
ts
export type CropProp = {
// 裁剪图片的地址
img?: string; // 默认为空,可选值为 url 地址、base64 或 blob
// 生成图片的质量
outputSize?: number; // 默认值为 1,范围 0.1 ~ 1
// 生成图片的格式
outputType?: 'jpeg' | 'png' | 'webp'; // 默认值为 'jpg'(需传入 'jpeg')
// 是否显示裁剪框的大小信息
info?: boolean; // 默认值为 true
// 图片是否允许滚轮缩放
canScale?: boolean; // 默认值为 true
// 是否默认生成截图框
autoCrop?: boolean; // 默认值为 false
// 默认生成截图框宽度
autoCropWidth?: number; // 默认值为容器的 80%,范围 0 ~ max
// 默认生成截图框高度
autoCropHeight?: number; // 默认值为容器的 80%,范围 0 ~ max
// 是否开启截图框宽高固定比例
fixed?: boolean; // 默认值为 false
// 截图框的宽高比例,开启 `fixed` 生效
fixedNumber?: [number, number]; // 默认值为 [1, 1]
// 是否输出原图比例的截图
full?: boolean; // 默认值为 false
// 是否固定截图框大小
fixedBox?: boolean; // 默认值为 false
// 上传图片是否可以移动
canMove?: boolean; // 默认值为 true
// 截图框能否拖动
canMoveBox?: boolean; // 默认值为 true
// 上传图片是否按照原始比例渲染
original?: boolean; // 默认值为 false
// 截图框是否被限制在图片里面
centerBox?: boolean; // 默认值为 false
// 是否按照设备的 dpr 输出等比例图片
high?: boolean; // 默认值为 true
// 是否展示真实输出图片宽高
infoTrue?: boolean; // 默认值为 false
// 限制图片最大宽度和高度
maxImgSize?: number; // 默认值为 2000,范围 0 ~ max
// 图片根据截图框输出比例倍数
enlarge?: number; // 默认值为 1,范围 0 ~ max(建议不要太大)
// 图片默认渲染方式
mode?: 'contain' | 'cover' | '100px' | '100%' | 'auto'; // 默认值为 'contain'
// 裁剪框限制最小区域
limitMinSize?: number | number[] | string; // 默认值为 10
// 导出时背景颜色填充
fillColor?: string; // 默认为空,可选值为 #ffffff、white
};
export type CropEvents = {
// 实时预览事件
realTime?: (data?: { w: number; h: number }) => void;
// 图片移动事件
imgMoving?: () => void;
// 截图框移动回调函数
cropMoving?: () => void;
// 图片加载的回调, 返回结果
imgLoad?: () => void;
};
export type CropOptions = {
prop?: CropProp;
events?: CropEvents;
};
defaultOptions.ts
ts
// 默认值对象
import { CropOptions } from './type';
export const defaultCropOptions: CropOptions = {
prop: {
img: '',
outputSize: 1,
outputType: 'jpeg',
info: true,
canScale: true,
autoCrop: false,
autoCropWidth: 0,
autoCropHeight: 0,
fixed: false,
fixedNumber: [1, 1],
full: false,
fixedBox: false,
canMove: true,
canMoveBox: true,
original: false,
centerBox: false,
high: true,
infoTrue: false,
maxImgSize: 2000,
enlarge: 1,
mode: 'contain',
limitMinSize: 10,
fillColor: '',
},
events: {
// 实时预览事件
realTime: () => {},
// 图片移动事件
imgMoving: () => {},
// 截图框移动回调函数
cropMoving: () => {},
// 图片加载的回调, 返回结果
imgLoad: () => true,
},
};
crop-dialog.vue
ts
<template>
<el-dialog v-model="dialogVisible" :before-close="dialogClose" class="crop-dialog">
<div class="crop-dialog__container center">
<vue-cropper v-bind="options.prop" ref="cropper" v-on="options.events" />
</div>
<template #footer>
<el-space>
<el-button type="primary" @click="submitHandle">截取</el-button>
<el-button @click="dialogClose">取消</el-button>
</el-space>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, defineProps, defineExpose, defineEmits } from 'vue';
import { VueCropper } from 'vue-cropper/next';
import 'vue-cropper/next/dist/index.css';
import { CropOptions } from '@/components/CropDialog/type';
import { defaultCropOptions } from '@/components/CropDialog/defaultOptions';
type Prop = {
options?: CropOptions;
};
const props = withDefaults(defineProps<Prop>(), {
options: () => ({ ...defaultCropOptions }),
});
// 定义emits
const emit = defineEmits(['submit', 'closed']);
const cropper = ref();
const showForm = ref('login');
// 定义表单数据
let dialogVisible = ref(false);
// 关闭弹窗
function dialogClose() {
dialogVisible.value = false;
emit('closed');
}
const getCropBlob = (): Promise<Blob> => {
return new Promise((resolve, reject) => {
cropper.value.getCropBlob((blob: Blob) => {
resolve(blob);
});
});
};
const getCropData = (): Promise<string> => {
return new Promise((resolve, reject) => {
cropper.value.getCropData((data: string) => {
resolve(data);
});
});
};
const getCropFile = () => {
const outputType = props.options?.prop?.outputType || 'jpeg';
return new Promise((resolve, reject) => {
getCropBlob().then((blob) => {
resolve(new File([blob], `cropped-image.${outputType}`, { type: `image/${outputType}` }));
});
});
};
const submitHandle = async () => {
const file = await getCropFile();
const data = await getCropData();
const blob = await getCropBlob();
emit('submit', { file, data, blob });
dialogClose();
};
defineExpose({
showDialog: () => {
dialogVisible.value = true;
},
hideDialog: () => {
dialogVisible.value = false;
},
});
</script>
<style lang="scss" scoped>
.crop-dialog {
min-width: 314px;
background: #eeeeee;
color: #999999;
&__container {
width: 600px;
height: 600px;
position: relative;
display: flex;
justify-content: center;
}
}
</style>
wq-crop.ts
ts
import { createApp, h, ref } from 'vue';
import CropDialog from './crop-dialog.vue';
import { CropOptions } from './type';
import { defaultCropOptions } from './defaultOptions';
type SubmitData = {
data: string;
file: File;
blob: Blob;
};
export function wqCrop(options: CropOptions): Promise<SubmitData | null> {
const propOptions: CropOptions = {
prop: options.prop || defaultCropOptions.prop,
events: options.events || defaultCropOptions.events,
};
// console.log(propOptions);
return new Promise((resolve, reject) => {
const wqCropRef = ref();
const mountNode = document.createElement('div');
// const appendTo = document.querySelector('body')
document.body.appendChild(mountNode);
// 创建节点
const app = createApp({
render() {
return h(CropDialog, {
ref: wqCropRef,
options: propOptions,
onSubmit: (data: SubmitData) => {
resolve(data);
},
onClosed: () => {
mountNode.remove();
},
});
},
});
// 挂载容器,instance就是容器的实例
const instance = app.mount(mountNode);
wqCropRef.value.showDialog();
});
}
3. 最后
使用该组件需要
vue-cropper
可以使用
npm install vue-cropper
这里用到是
"vue-cropper": "^0.6.5"
版本
本组件用到了element-ui
的dialog
, 这里也可以使用其他的dialog
组件
函数传入的参数, 参照type.ts
中的type CropOptions
如果组件封装有不合理的地方, 或者哪里有问题, 欢迎评论与私信