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

相关推荐
Net蚂蚁代码43 分钟前
Angular入门的环境准备步骤工作
前端·javascript·angular.js
小阮的学习笔记2 小时前
electron实现加载页(启动页)
vue.js·electron
小着3 小时前
vue项目页面最底部出现乱码
前端·javascript·vue.js·前端框架
lichenyang4536 小时前
React ajax中的跨域以及代理服务器
前端·react.js·ajax
呆呆的小草6 小时前
Cesium距离测量、角度测量、面积测量
开发语言·前端·javascript
WHOAMI_老猫6 小时前
xss注入遇到转义,html编码绕过了解一哈
javascript·web安全·渗透测试·xss·漏洞原理
一 乐7 小时前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
testleaf7 小时前
前端面经整理【1】
前端·面试
BillKu7 小时前
Vue3 + TypeScript + Element Plus 表格行按钮不触发 row-click 事件、不触发勾选行,只执行按钮的 click 事件
vue.js·elementui·typescript
好了来看下一题7 小时前
使用 React+Vite+Electron 搭建桌面应用
前端·react.js·electron