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

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,通过reactiveref来管理响应式数据:

    图片上传与处理

    图片上传使用了 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

相关文章:

  • AI Agent学习 -- (2)LangChain的安装
  • 全面探索 KingbaseES 在线数据库平台:从开发到优化的全方位体验
  • ROS学习话题通信之Python实现
  • C++ 学习 多线程 2025年6月17日18:41:30
  • 基于深度学习的智能语音情感分析系统:技术与实践
  • [Think] Libuv | Node.js | nix vs docker
  • Redis 核心数据类型及典型使用场景详解
  • HTTP 请求中的 `Content-Type` 类型详解及前后端示例(Vue + Spring Boot)
  • Stripformer: Strip Transformer for Fast Image Deblurring论文阅读
  • c++学习-多态
  • 从零到一:C语言基础入门学习路线与核心知识点全解析
  • Redis的GEO详解
  • 82.多级抽取滤波器,设计抗混叠滤波器时采样频率是基于抽取之前的设计的
  • Lua基础复习之Lua元表
  • C++——基础知识
  • 论文笔记 <交通灯> IntelliLight:一种用于智能交通灯控制的强化学习方法
  • RISC-V向量扩展与GPU协处理:开源加速器设计新范式——对比NVDLA与香山架构的指令集融合方案
  • Greenplum 与 PostgreSQL 的关系
  • 005微信小程序npm包_全局数据共享和分包
  • # 我使用过的 HTML + CSS 实践总结笔记(含说明)
  • 密度泛函涨落理论在医疗人工智能中的应用与展望:多尺度物理驱动智能的新范式
  • 【Vue】Vue2/3全局属性配置全攻略
  • 实验分享|自研局部DIC-GPU算法与开源GPU算法对比实验
  • SpringBoot-Actuator依赖项的作用配置 Heapdump堆栈信息泄露
  • 微信小程序:将搜索框和表格封装成组件,页面调用组件
  • springboot项目,利用docker打包部署
  • 简说 python
  • python题库及试卷管理系统
  • java循环语句-跳转关键字break、continue
  • 企业软件架构演进:从流程驱动到知识驱动的数字化转型路径