Vue 实现图片裁剪功能:基于 vue-cropper 和 Element Plus 的完整解决方案
在现代 Web 应用开发中,图片裁剪功能是一个常见需求,无论是用户头像上传、商品图片处理还是社交媒体内容发布,都需要对图片进行裁剪以满足特定的展示要求。本文将介绍如何使用 Vue.js 结合 vue-cropper 插件和 Element Plus UI 库实现一个功能完善的图片裁剪组件。
功能概述
我们实现的图片裁剪组件具有以下功能:
-
支持图片上传( jpg/png 格式)
-
可视化图片裁剪界面(600x600 像素)
-
自定义裁剪框比例(可通过 props 配置)
-
图片放大 / 缩小功能
-
图片旋转功能(左右旋转)
-
裁剪后图片数据处理(转换为 File 对象)
技术栈
本组件使用了以下技术和库:
-
Vue 3.x(Composition API)
-
Element Plus(UI 组件库)
-
vue-cropper(图片裁剪插件@1.0.5版本)
组件实现解析
模板部分解析
模板部分主要包含三个核心区域:图片上传区域(因暂未对接后台接口,只测试功能,通过change事件来实现)、裁剪预览区域和操作按钮区域。
<template><div class="w-full h-full flex-col items-center justify-center"><!-- action="#" #在实战中替换为实际的后台接口 --><ElUploadv-model:file-list="fileList"class="upload-demo"action="#"multiple:limit="1"accept="image/*"list-type="picture-card":on-change="onChange":auto-upload="false"><el-button type="primary">Click to upload</el-button><template #tip><div class="el-upload__tip">xxxxxxxxxxxxxxxxxxxxx</div></template></ElUpload><div :style="{ height: '600px', width: '600px', marginBottom: '30px' }"><VueCropperref="cropper":info="false":infoTrue="options.infoTrue":img="options.img":autoCrop="options.autoCrop":autoCropWidth="options.autoCropWidth":autoCropHeight="options.autoCropHeight":fixedBox="options.fixedBox":mode="options.cropperMode":centerBox="options.centerBox":enlarge="options.enlarge":fixedNumber="options.fixedNumber"outputType="png"@realTime="realTime"/></div><ElRow :style="{ textAlign: 'center' }"><ElCol :span="6"><ElButton type="default" :icon="ZoomOut" @click="changeScale(1)">放大</ElButton></ElCol><ElCol :span="6"><ElButton type="default" :icon="RefreshRight" @click="rotateLeft">左旋转</ElButton></ElCol><ElCol :span="6"><ElButton type="default" :icon="RefreshLeft" @click="rotateRight">右旋转</ElButton></ElCol><ElCol :span="6"><ElButton :icon="ZoomIn" type="default" @click="changeScale(-1)">缩小</ElButton></ElCol></ElRow><div><ElButton @click="handleSubmit()">提交事件</ElButton></div></div> </template>
逻辑部分核心功能解析
组件初始化与配置
组件使用了 Vue 3 的 Composition API,通过
reactive
和ref
来管理响应式数据:图片上传与处理
图片上传使用了 Element Plus 的
ElUpload
组件,通过onChange
事件处理图片选择,通过getImageData
函数计算裁剪框的初始尺寸:图片比例计算核心函数
getImageData
函数是整个组件的核心,它根据原图尺寸和裁剪比例计算出合适的裁剪框尺寸:裁剪结果处理
提交事件处理函数获取裁剪后的图片数据,并将其转换为 File 对象以便上传到服务器:
<script setup lang="ts"> import "vue-cropper/dist/index.css"; import { VueCropper } from "vue-cropper"; import { ref, reactive, onMounted } from "vue"; import { ElButton, ElRow, ElCol, ElUpload } from "element-plus"; import {RefreshRight,RefreshLeft,ZoomIn,ZoomOut, } from "@element-plus/icons-vue";const previews = ref(); const cropper = ref(); const fileName = ref(); const options = reactive({img: "", //裁剪图片的地址autoCrop: true, //是否默认生成截图框autoCropWidth: undefined, //默认生成截图框宽度autoCropHeight: undefined, //默认生成截图框高度enlarge: undefined, // 图片根据截图框输出比例倍数fixedBox: true, //是否固定截图框大小 不允许改变previewsCircle: false, //预览图是否是原圆形centerBox: true, //截图框是否被限制在图片里面fixedNumber: [1, 1], // 截图框的宽高比例infoTrue: true,title: "修改图片",cropperMode: "contain", });//上传图片组件数据 const fileList = ref<any>([]);interface CropperProps {action?: string; //上传接口fixedWidthNumber?: any; // 截图框的宽高比例fixedHeightNumber?: any; // 截图框的宽高比例 } const props = withDefaults(defineProps<CropperProps>(), {action: "后台接口",fixedWidthNumber: 1,fixedHeightNumber: 1, });// //图片上传前回调 const onChange = (file: any, list: any) => {console.log("🚀 ~ beforeUpload ~ file:any, fileList:any:", file, list);if (file) {fileList.value[0] = file;options.img = file.url;fileName.value = file.name;const imgData: any = getImageData(600, file?.raw, [props.fixedWidthNumber,props.fixedHeightNumber,]);options.autoCropWidth = imgData.width;options.autoCropHeight = imgData.height;options.enlarge = imgData.ratio;options.fixedNumber = [props.fixedWidthNumber, props.fixedHeightNumber];} };/*** @Description: 计算原图与裁剪框显示的图片的比例,裁剪框的大小(需要传入原图的文件!!!, 裁剪弹框的高度)* 实现的功能:* 1.如果没有传截图框的比例,那么默认是1:1,那一边短那一边被当成截图框的长度。* 2.传入截图框比例,有六种情况,处理原则为截图框比例不会改变的情况下截取原图一边。* 三个重要核心:* 1.原图与裁剪弹框的比例。* 2.原图宽高比例。* 3.截图框宽高比例。*/ function getImageData(maxHeight: number, file: any, fixed: number[]) {console.log("🚀 ~ getImageData ~ file:", file);return new Promise((resolve, reject) => {// 读取文件内容 new FileReader()const reader = new FileReader();// readAsDataURL: 方法可以将读取到的文件编码成DataURL (这里的reader.result是base64格式)reader.readAsDataURL(file);// onload:文件读取成功时触发reader.onload = () => {// 创建一个Image对象const image: any = new Image();// 定义Image对象的src: image.src = reader.result; 这样做就相当于给浏览器缓存了一张图片。image.src = reader.result;console.log("🚀 ~ returnnewPromise ~ reader.result:", reader.result);// onload:Image对象创建成功时触发image.onload = () => {let Ratio = 1;// 获取原图的宽和高const w = image.width;const h = image.height;// 获取原图的比例const imgRatio = w / h;//图片长的是那一边const imgLong = imgRatio > 1 ? "width" : "height";// 计算出原图被缩放到裁剪框缩小的比例const wRatio = w / maxHeight;const hRatio = h / maxHeight;// 按照功能需要,长的一边需要与裁剪弹框一样长,所以比例取长的一边Ratio = wRatio >= hRatio ? wRatio : hRatio;// 计算截图框的比例const fixedRatio = fixed[0] / fixed[1];// 截图比例长的是那一边const fixedLong = fixedRatio > 1 ? "width" : "height";// 初始情况即没有传入裁剪弹框比例const imgWidth = w > h ? h / Ratio : w / Ratio;let imgData = {ratio: Ratio,width: imgWidth,height: imgWidth,};// 传入裁剪框比例的六种情况if (imgLong === fixedLong) {if (imgLong === "width") {if (imgRatio > fixedRatio) {// 情况1 图的比例和裁剪框的比例都是宽大,但是图片比例大于截图比例。// 情况处理:在以一边为裁剪时,图放得下裁剪框,不用换边为裁剪框的基数,以高为基数。imgData = {ratio: Ratio,width: (h / Ratio) * fixedRatio,height: h / Ratio,};} else {// 情况2 图的比例和裁剪框的比例都是宽大,但是图片比例小于截图比例。// 情况处理:在以一边为裁剪时,图放不下裁剪框,换边为裁剪框的基数,以宽为基数。imgData = {ratio: Ratio,width: w / Ratio,height: (h / Ratio) * (w / Ratio / ((h / Ratio) * fixedRatio)),};}} else {if (imgRatio < fixedRatio) {// 情况3 图的比例和裁剪框的比例都是高大,但是图片比例小于于截图比例。(w/h 小于即大于)// 情况处理:在以一边为裁剪时,图放得下裁剪框,不用换边为裁剪框的基数,以宽为基数。imgData = {ratio: Ratio,width: w / Ratio,height: w / Ratio / fixedRatio,};} else {// 情况4 图的比例和裁剪框的比例都是高大,但是图片比例大于截图比例。(w/h 小于即大于)// 情况处理:在以一边为裁剪时,图放不下裁剪框,换边为裁剪框的基数,以高为基数。imgData = {ratio: Ratio,width: (w / Ratio) * (h / Ratio / (w / Ratio / fixedRatio)),height: h / Ratio,};}}} else {if (imgLong === "width") {// 情况5 图的比例大于1,裁剪框的比例小于1。// 情况处理:在以一边为裁剪时,图放得下裁剪框,不用换边为裁剪框的基数,以高为基数。imgData = {ratio: Ratio,width: (h / Ratio) * fixedRatio,height: h / Ratio,};} else {// 情况5 图的比例小于1,裁剪框的比例大于1。// 情况处理:在以一边为裁剪时,图放得下裁剪框,不用换边为裁剪框的基数,以宽为基数。imgData = {ratio: Ratio,width: w / Ratio,height: w / Ratio / fixedRatio,};}}resolve(imgData);};image.onerror = () => {reject(new Error("Image对象创建失败"));};};}); }//移动框的事件 const realTime = (data: any) => {previews.value = data; };//图片缩放 function changeScale(num: number) {num = num || 1;cropper.value.changeScale(num); } //向左旋转 function rotateLeft() {cropper.value.rotateLeft(); } //向右旋转 function rotateRight() {cropper.value.rotateRight(); }// 提交事件 async function handleSubmit() {// 提交事件cropper.value.getCropData(async (data: any) => {//拿到裁剪后的原始数据const file = dataURLtoFile(data, fileName.value);//调用接口--传递给后台--从后台再读取新的图片链接并赋值即可// const result = await 后台接口({ file });}); }// base 64 转成二进制文件流 const dataURLtoFile = (dataurl: any, filename: any) => {var arr = dataurl.split(","),mime = arr[0].match(/:(.*?);/)[1],bstr = atob(arr[1]),n = bstr.length,u8arr = new Uint8Array(n);while (n--) {u8arr[n] = bstr.charCodeAt(n);}return new File([u8arr], filename, { type: mime }); }; </script>
注意
getImageData中file字段传入的数据类型示例(也是提交事件中处理后的数据类型)
- 实际使用需对接接口,并组件化封装拓展,本文章仅供参考,逻辑已通!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dtcms.com/a/252869.html
如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!