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

相关推荐
前端小巷子8 分钟前
深入理解TCP协议
前端·javascript·面试
万少9 分钟前
鸿蒙外包的十大生存法则
前端·后端·面试
顽疲24 分钟前
从零用java实现 小红书 springboot vue uniapp(13)模仿抖音视频切换
java·vue.js·spring boot
开开心心就好1 小时前
电脑息屏工具,一键黑屏超方便
开发语言·javascript·电脑·scala·erlang·perl
江号软件分享1 小时前
有效保障隐私,如何安全地擦除电脑上的敏感数据
前端
web守墓人2 小时前
【前端】ikun-markdown: 纯js实现markdown到富文本html的转换库
前端·javascript·html
Savior`L2 小时前
CSS知识复习5
前端·css
许白掰2 小时前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
中微子6 小时前
🔥 React Context 面试必考!从源码到实战的完整攻略 | 99%的人都不知道的性能陷阱
前端·react.js
秋田君7 小时前
深入理解JavaScript设计模式之命令模式
javascript·设计模式·命令模式