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

vue3中图片裁切组件封装

以 vue-cropper 为基础封装图片裁切组件,UI框架用的 Naive UI,实现效果如下:
效果图

安装vue-cropper

参考:vue-cropper - npm

安装:

npm install vue-cropper@next

使用方式

import 'vue-cropper/dist/index.css'
import { VueCropper }  from "vue-cropper";

定义vue-cropper基本参数

更多参数可查看:vue-cropper - npm

const option = {outputSize: 0.7, // 裁剪生成图片的质量outputType: "png", // 裁剪生成图片的格式 (jpg 需要传入jpeg)autoCrop: true, // 是否默认生成截图框fixedBox: false, // 固定截图框大小autoCropWidth: 300, // 默认生成截图框宽度autoCropHeight: 100, // 默认生成截图框高度fixed: true, // 是否开启截图框宽高固定比例fixedNumber: [3, 1], // 截图框的宽高比例, 开启fixed生效enlarge: 4, // 图片根据截图框输出比例倍数centerBox: false, // 截图框是否被限制在图片里面infoTrue: true, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高canMoveBox: true, // 截图框能否拖动mode: "100% auto", // 图片默认渲染方式
};

文件选择

<!-- 上传图片:使用 v-show 隐藏起来,触发元素自定义 -->
<inputv-show="false"type="file"accept="image/*"@change="handleFileSelect"class="file-input"id="file-input"
/>
// 处理文件选择(将图片转为 base64)
const handleFileSelect = (e: Event) => {const file = (e.target as HTMLInputElement).files?.[0];if (!file) return;// 验证文件类型if (!file.type.startsWith("image/")) {window.$message?.warning("请选择图片文件");(e.target as HTMLInputElement).value = ""; // 清空选择return;}// 保存原始文件名(不含扩展名,用于后续生成裁切文件名)originalFileName.value = file.name.replace(/\.[^.]+$/, "");// 读取文件为 base64const reader = new FileReader();reader.onload = (e) => {imageUrl.value = e.target?.result as string;};reader.readAsDataURL(file);
};
// 触发文件选择
const fileUpload = () => {(document.querySelector("#file-input") as HTMLInputElement).click();
};

按钮功能(旋转、缩放)

<NButton:disabled="operateDisabled"quaternarycircle@click="rotateLeft"
><template #icon><SvgIcon icon="material-symbols:rotate-left-rounded" /></template>
</NButton>
<NButton:disabled="operateDisabled"quaternarycircle@click="rotateRight"
><template #icon><SvgIcon icon="material-symbols:rotate-right-rounded" /></template>
</NButton>
<!-- 数值:正数-放大 负数-缩小 数值越大放大的比例越大 -->
<NButton :disabled="operateDisabled" quaternary circle @click="zoom(3)"><template #icon><SvgIcon icon="iconamoon:zoom-in" /></template>
</NButton>
<NButton:disabled="operateDisabled"quaternarycircle@click="zoom(-3)"
><template #icon><SvgIcon icon="iconamoon:zoom-out" /></template>
</NButton>
// 旋转图片
const rotateLeft = () => {cropperRef.value.rotateLeft(); // 向左旋转 90 度
};
const rotateRight = () => {cropperRef.value.rotateRight(); // 向右旋转 90 度
};// 缩放图片
const zoom = (val: number) => {cropperRef.value.changeScale(val); // 正数放大,负数缩小
};

裁剪结果预览

<NButtonsize="small"strongsecondarytype="info":disabled="operateDisabled"@click="getCroppedImage"
>确认裁切
</NButton><!-- 裁切结果预览 -->
<div class="preview"><h3>裁切结果:</h3><imgv-if="croppedImage":src="croppedImage"alt="裁切结果"class="preview-img":style="{width: `${props.width}px`,height: `${props.height}px`,}"/>
</div>
// 获取裁切后的图片(支持多种格式:base64/blob)
const getCroppedImage = async () => {// base64cropperRef.value.getCropData((data: any) => {croppedImage.value = data;});// blob// cropperRef.value.getCropBlob
};

裁剪后的图片上传

interface CropFileInfo {id: string;name: string;file: File;
}
const upload = async (): Promise<any> => {// 1. 防止重复提交if (operateDisabled.value) return;operateDisabled.value = true;try {// 2. 前置校验:检查图片是否已选择if (!imageUrl.value) {window.$message?.warning("请选择图片");throw new Error("请选择图片");}// 3. 异步获取裁切后的 Blobconst cropBlob = await new Promise<Blob>((resolve, reject) => {cropperRef.value.getCropBlob((blob: Blob | null) => {if (blob) {resolve(blob);} else {reject(new Error("裁切失败,无法获取图片数据"));}});});// 4. 生成文件名const baseName = originalFileName.value || "cropped_image"; // 处理原始文件名可能为空的情况const fileName = `${baseName}.png`;// 5. 转换为 File 对象并包装为 FileInfo,使用UUID生成唯一的图片idconst croppedFile: CropFileInfo = {id: getUUID(),name: fileName,file: new File([cropBlob], fileName, { type: "image/png" }),};// 6. 调用上传接口并返回结果const fileItem = await uploadFile(croppedFile, props.prefix);return fileItem;} catch (error) {// 7. 统一错误处理const errorMsg =error instanceof Error ? error.message : "上传失败,请重试";window.$message?.error(errorMsg);throw error; // 抛出错误让上层捕获} finally {// 8. 确保状态重置,无论成功失败operateDisabled.value = false;}
};
// 向外部暴露:由外部触发图片上传
defineExpose({upload,
});

组件调用

组件使用:

<ImageCropperref="imageCropperef"tip="(仅支持 .png 格式,且最多一张)"
/>

触发上传功能:

const fileItem = await imageCropperef.value.upload();

组件封装完整代码

<template><div class="image-cropper flex items-start gap-20px"><!-- 上传图片区域 --><inputv-show="false"type="file"accept="image/*"@change="handleFileSelect"class="file-input"id="file-input"/><!-- 裁切区域 --><div class="cropper-container"><!-- 裁切组件 --><vue-cropperref="cropperRef":img="imageUrl":outputSize="option.outputSize":outputType="option.outputType":autoCrop="option.autoCrop":fixedBox="option.fixedBox":autoCropWidth="option.autoCropWidth":autoCropHeight="option.autoCropHeight":centerBox="option.centerBox":canMoveBox="option.canMoveBox":enlarge="option.enlarge":mode="option.mode":fixedNumber="option.fixedNumber":fixed="option.fixed"class="cropper"/><!-- 操作按钮 --><div class="cropper-actions"><NButton:disabled="operateDisabled"size="small"strongsecondary@click="fileUpload">选择图片</NButton><NButton:disabled="operateDisabled"quaternarycircle@click="rotateLeft"><template #icon><SvgIcon icon="material-symbols:rotate-left-rounded" /></template></NButton><NButton:disabled="operateDisabled"quaternarycircle@click="rotateRight"><template #icon><SvgIcon icon="material-symbols:rotate-right-rounded" /></template></NButton><NButton :disabled="operateDisabled" quaternary circle @click="zoom(3)"><template #icon><SvgIcon icon="iconamoon:zoom-in" /></template></NButton><NButton:disabled="operateDisabled"quaternarycircle@click="zoom(-3)"><template #icon><SvgIcon icon="iconamoon:zoom-out" /></template></NButton><NButtonsize="small"strongsecondarytype="info":disabled="operateDisabled"@click="getCroppedImage">确认裁切</NButton></div><div class="tip text-gray-400">{{ props.tip }}</div></div><!-- 裁切结果预览 --><div class="preview"><h3>裁切结果:</h3><imgv-if="croppedImage":src="croppedImage"alt="裁切结果"class="preview-img":style="{width: `${props.width}px`,height: `${props.height}px`,}"/></div></div>
</template><script setup lang="ts">
import { ref, onMounted } from "vue";
import { VueCropper } from "vue-cropper";
import "vue-cropper/dist/index.css"; // 引入样式
import { useMinio } from "@自己的路径";
import { getUUID } from "@自己的路径";const { uploadFile } = useMinio();const props = defineProps({tip: {type: String,default: "",},prefix: {type: String,default: "",},width: {type: Number,default: 300,},height: {type: Number,default: 100,},
});// https://www.npmjs.com/package/vue-cropper
const option = {outputSize: 0.7, // 裁剪生成图片的质量outputType: "png", // 裁剪生成图片的格式 (jpg 需要传入jpeg)autoCrop: true, // 是否默认生成截图框fixedBox: false, // 固定截图框大小autoCropWidth: 300, // 默认生成截图框宽度autoCropHeight: 100, // 默认生成截图框高度fixed: true, // 是否开启截图框宽高固定比例fixedNumber: [3, 1], // 截图框的宽高比例, 开启fixed生效enlarge: 4, // 图片根据截图框输出比例倍数centerBox: false, // 截图框是否被限制在图片里面infoTrue: true, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高canMoveBox: true, // 截图框能否拖动mode: "100% auto", // 图片默认渲染方式
};// 图片相关变量
const imageUrl = ref(""); // 原始图片的 base64 或 blob 地址
const croppedImage = ref(""); // 裁切后的图片地址
const originalFileName = ref("");
const cropperRef = ref<any>(null); // 裁切组件实例
const operateDisabled = ref(false);// 处理文件选择(将图片转为 base64)
const handleFileSelect = (e: Event) => {const file = (e.target as HTMLInputElement).files?.[0];if (!file) return;// 验证文件类型if (!file.type.startsWith("image/")) {window.$message?.warning("请选择图片文件");(e.target as HTMLInputElement).value = ""; // 清空选择return;}// 保存原始文件名(不含扩展名,用于后续生成裁切文件名)originalFileName.value = file.name.replace(/\.[^.]+$/, "");// 读取文件为 base64const reader = new FileReader();reader.onload = (e) => {imageUrl.value = e.target?.result as string;};reader.readAsDataURL(file);
};const fileUpload = () => {(document.querySelector("#file-input") as HTMLInputElement).click();
};// 旋转图片
const rotateLeft = () => {cropperRef.value.rotateLeft(); // 向左旋转 90 度
};
const rotateRight = () => {cropperRef.value.rotateRight(); // 向右旋转 90 度
};// 缩放图片
const zoom = (val: number) => {cropperRef.value.changeScale(val); // 正数放大,负数缩小
};// 获取裁切后的图片(支持多种格式:base64/blob)
const getCroppedImage = async () => {cropperRef.value.getCropData((data: any) => {croppedImage.value = data;});
};interface CropFileInfo {id: string;name: string;file: File;
}
const upload = async (): Promise<any> => {// 1. 防止重复提交if (operateDisabled.value) return;operateDisabled.value = true;try {// 2. 前置校验:检查图片是否已选择if (!imageUrl.value) {window.$message?.warning("请选择图片");throw new Error("请选择图片");}// 3. 异步获取裁切后的 Blobconst cropBlob = await new Promise<Blob>((resolve, reject) => {cropperRef.value.getCropBlob((blob: Blob | null) => {if (blob) {resolve(blob);} else {reject(new Error("裁切失败,无法获取图片数据"));}});});// 4. 生成文件名const baseName = originalFileName.value || "cropped_image"; // 处理原始文件名可能为空的情况const fileName = `${baseName}.png`;// 5. 转换为 File 对象并包装为 FileInfoconst croppedFile: CropFileInfo = {id: getUUID(),name: fileName,file: new File([cropBlob], fileName, { type: "image/png" }),};// 6. 调用上传接口并返回结果const fileItem = await uploadFile(croppedFile, props.prefix);return fileItem;} catch (error) {// 7. 统一错误处理const errorMsg =error instanceof Error ? error.message : "上传失败,请重试";window.$message?.error(errorMsg);throw error; // 抛出错误让上层捕获} finally {// 8. 确保状态重置,无论成功失败operateDisabled.value = false;}
};defineExpose({upload,
});onMounted(() => {});
</script><style scoped>
.cropper-container {width: 400px;border: 1px solid #eee;padding: 10px;border-radius: 8px;
}.cropper {width: 100%;height: 300px;
}.cropper-actions {margin-top: 20px;display: flex;justify-content: space-between;align-items: center;
}.preview-img {border: 1px solid #eee;padding: 10px;border-radius: 8px;
}
</style>
http://www.dtcms.com/a/470280.html

相关文章:

  • 从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 15--二次开发--封装公共方法 3
  • 做新媒体文的网站网站开发 ppt
  • 东莞市建设局网站电子商务网站建设教程 pdf
  • numpy第三方库学习(更新中)
  • 【开题答辩全过程】以 报考意向分析系统为例,包含答辩的问题和答案
  • Spring AI
  • ssm速通1(2/2)
  • Android GPS定位与行车轨迹追踪完整实战
  • [持续更新] HPC高性能计算CUDA/C++面试知识点
  • 【有源码】基于Hadoop生态的大数据共享单车数据分析与可视化平台-基于Python与大数据的共享单车多维度数据分析可视化系统
  • 上海做网站推荐做景观要用的植物网站
  • 珠海 网站建设和推广万网网站空间
  • Jasperreport 导出word 多个element重叠部分导致不显示(不支持)
  • GRU(门控循环单元) 笔记
  • 莱州网站建设哪家好做网站要会哪些知识
  • ubuntu离线安装 xl2tpd
  • 如何在百度上做网站最好用的免费建站
  • 关联网站有那些wordpress超级排版器插件
  • 熊猫比分 APP:开启体育赛事观赛新“姿势”
  • 第二章:模块的编译与运行-9 Platform Dependency
  • java多模块概念
  • 小企业网站维护什么东西互联网培训
  • 找人做网站做的很烂网站自助建设推广
  • uhttpd HTTPS 在嵌入式与 OpenWrt 上的实战部署与排查
  • 合肥网站建设正规公司抖音如何推广引流
  • [cpprestsdk] 构建HTTP消息 | http_headers.h
  • SCI论文写作:从实验设计到发表(选题、文献调研、实验设计、数据分析、论文结构及语言规范)
  • 西安哪里有做网站的网页界面ps制作步骤
  • 《彻底理解C语言指针全攻略(2)》
  • JavaScript 性能优化实战:从原理到落地