利用vue-capper封装一个可以函数式调用图片裁剪组件

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-uidialog, 这里也可以使用其他的dialog组件
函数传入的参数, 参照type.ts 中的 type CropOptions
如果组件封装有不合理的地方, 或者哪里有问题, 欢迎评论与私信

相关推荐
糕冷小美n19 小时前
elementuivue2表格不覆盖整个表格添加固定属性
前端·javascript·elementui
小哥不太逍遥19 小时前
Technical Report 2024
java·服务器·前端
沐墨染20 小时前
黑词分析与可疑对话挖掘组件的设计与实现
前端·elementui·数据挖掘·数据分析·vue·visual studio code
anOnion20 小时前
构建无障碍组件之Disclosure Pattern
前端·html·交互设计
threerocks20 小时前
前端将死,Agent 永生
前端·人工智能·ai编程
问道飞鱼21 小时前
【前端知识】Vite用法从入门到实战
前端·vite·项目构建
爱上妖精的尾巴21 小时前
8-10 WPS JSA 正则表达式:贪婪匹配
服务器·前端·javascript·正则表达式·wps·jsa
Zhencode21 小时前
Vue3响应式原理之ref篇
vue.js
shadow fish21 小时前
react学习记录(三)
javascript·学习·react.js
小疙瘩1 天前
element-ui 中 el-upload 多文件一次性上传的实现
javascript·vue.js·ui