利用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
如果组件封装有不合理的地方, 或者哪里有问题, 欢迎评论与私信

相关推荐
类人_猿42 分钟前
ASP.NET Web(.Net Framework) Http服务器搭建以及IIS站点发布
前端·iis·asp.net·.net·http站点服务器
组态软件4 小时前
web组态软件
前端·后端·物联网·编辑器·html
前端Hardy4 小时前
HTML&CSS:MacBook Air 3D 动画跃然屏上
前端·javascript·css·3d·html
汪小白JIY5 小时前
【VUE3】VUE组合式(响应式)API常见语法
vue.js·vue3·语法
loey_ln5 小时前
观察者模式和发布订阅模式
javascript·观察者模式·react.js
cnsxjean7 小时前
SpringBoot集成Minio实现上传凭证、分片上传、秒传和断点续传
java·前端·spring boot·分布式·后端·中间件·架构
ZL_5677 小时前
uniapp中使用uni-forms实现表单管理,验证表单
前端·javascript·uni-app
沉浮yu大海7 小时前
Vue.js 组件开发:构建可重用且高效的 UI 块
前端·vue.js·ui
代码欢乐豆7 小时前
软件工程第13章小测
服务器·前端·数据库·软件工程
sunly_7 小时前
Flutter:启动屏逻辑处理02:启动页
android·javascript·flutter