当前位置: 首页 > article >正文

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

1. 效果

	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

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

// 默认值对象
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

<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

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

如果组件封装有不合理的地方, 或者哪里有问题, 欢迎评论与私信


http://www.kler.cn/a/331087.html

相关文章:

  • JS媒体查询之matchMedia API 实现跟随系统主题色切换效果
  • 【探花交友】SpringCache
  • 从0入门自主空中机器人-2-2【无人机硬件选型-PX4篇】
  • STM32 I2C通信协议
  • jenkins集成工具(一)部署php项目
  • 单例模式的写法
  • 在 Qt 项目中使用 spdlog 的全攻略
  • 【硬件模块】SG90舵机模块
  • Veritus netbackup 管理控制台无法连接:未知错误
  • 【力扣 | SQL题 | 每日三题】力扣1264, 1113, 1098, 1082
  • CSP-J 2023 T1小苹果 T2公路
  • 一、Spring Boot集成Spring Security之自动装配
  • Gazebo安装,ubuntu22
  • Linux云计算 |【第四阶段】RDBMS1-DAY3
  • django创建一个新的应用
  • 什么是 Angular 开发中的 Dumb components
  • PowerBI概述
  • 滚雪球学Oracle[4.3讲]:PL/SQL控制结构与循环的深入解析与优化
  • python三局两胜游戏
  • 点评项目-3-登录成功后加载登录页面
  • Java String底层源码分析
  • 项目管理系统如何实现项目申报流程自动化?
  • 【redis-04】Redisson实现分布式锁实战和源码剖析
  • 基于ESP8266—AT指令连接阿里云+MQTT透传数据(3)
  • 2022浙江省赛G I M
  • el-table按照查询条件再对应行数据进行高亮,并可以定位到某行