vue3 实现裁剪图片问题
·
vue3
使用vue-cropper。与 elelment-ui 的el-upload 组件组合
我们实现的效果


我们经过裁剪 可以获取到这个 图片的base64 因为目前后端存储的都是 base64 图片
这样其实这个功能反而方便了
我们封装了一个裁剪的图片 我现在来贴上代码
子组件
<template>
<div class="cover-upload-container">
<!-- 上传按钮 -->
<el-upload
v-model:file-list="fileList"
class="upload-demo"
action=""
:auto-upload="false"
:show-file-list="false"
:on-change="handleFileChange"
accept="image/*"
>
<el-button type="default" round>选择封面</el-button>
<template #tip>
<div class="el-upload__tip">封面尺寸为600*800像素</div>
</template>
</el-upload>
<!-- 裁剪对话框 -->
<el-dialog
v-model="showCropDialog"
title="封面裁剪"
width="900px"
:before-close="handleCancelCrop"
>
<div class="crop-container">
<div class="crop-area">
<vue-cropper
ref="cropperRef"
:src="tempImageUrl"
:aspect-ratio="3/4"
:view-mode="1"
:guides="true"
:background="false"
:auto-crop-area="0.8"
:movable="true"
:zoomable="true"
:rotatable="true"
preview=".preview"
class="cropper-instance"
/>
</div>
<div class="preview-area">
<div class="preview-title">预览效果</div>
<div class="preview"></div>
<div class="preview-tip">600×800像素</div>
</div>
</div>
<div class="crop-actions">
<el-button @click="rotate(-90)" size="small">
<el-icon><RefreshLeft /></el-icon>
向左旋转
</el-button>
<el-button @click="rotate(90)" size="small">
<el-icon><RefreshRight /></el-icon>
向右旋转
</el-button>
<el-button @click="reset" size="small">重置</el-button>
<el-button @click="zoom(0.1)" size="small">放大</el-button>
<el-button @click="zoom(-0.1)" size="small">缩小</el-button>
</div>
<div class="crop-tips">
<h4>封面裁剪规范</h4>
<p>1. 图片质量:请提供高分辨率、清晰无水印的官方封面图片。</p>
<p>2. 内容完整:请通过鼠标拖动或缩放,确保书名、作者、出版社等所有关键信息完整地置于裁剪框内,且无明显拉伸变形</p>
</div>
<template #footer>
<el-button @click="handleCancelCrop">取消</el-button>
<el-button type="primary" @click="handleConfirmCrop">确定裁剪</el-button>
</template>
</el-dialog>
<!-- 裁剪结果预览 -->
<!-- <div v-if="croppedImageUrl" class="result-preview">
<div class="preview-header">
<span>封面预览</span>
<el-button type="danger" text @click="handleRemoveCover" size="small">删除</el-button>
</div>
<img :src="croppedImageUrl" class="cover-image" alt="封面预览">
</div> -->
</div>
</template>
<script setup>
import { ref, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import { RefreshLeft, RefreshRight } from '@element-plus/icons-vue';
import VueCropper from 'vue-cropperjs';
import 'cropperjs/dist/cropper.css';
// 响应式数据
const fileList = ref([]);
const showCropDialog = ref(false);
const tempImageUrl = ref('');
const croppedImageUrl = ref('');
const cropperRef = ref(null);
const emit =defineEmits(["cover-cropped"])
// 处理文件选择
const handleFileChange = (file) => {
// 验证文件类型
if (!file.raw.type.startsWith('image/')) {
ElMessage.error('请选择图片文件');
return;
}
// 验证文件大小(5MB)
if (file.raw.size > 5 * 1024 * 1024) {
ElMessage.error('图片大小不能超过5MB');
return;
}
// 创建文件读取器
const reader = new FileReader();
reader.onload = (e) => {
tempImageUrl.value = e.target.result;
showCropDialog.value = true;
// 确保DOM更新后初始化
nextTick(() => {
// 可以在这里添加一些初始化后的操作
});
};
reader.readAsDataURL(file.raw);
};
// 旋转图片
const rotate = (degree) => {
if (cropperRef.value) {
cropperRef.value.rotate(degree);
}
};
// 缩放图片
const zoom = (ratio) => {
if (cropperRef.value) {
cropperRef.value.relativeZoom(ratio);
}
};
// 重置裁剪
const reset = () => {
if (cropperRef.value) {
cropperRef.value.reset();
}
};
// 取消裁剪
const handleCancelCrop = () => {
showCropDialog.value = false;
tempImageUrl.value = '';
fileList.value = []; // 清空文件列表
};
// 确认裁剪
const handleConfirmCrop = () => {
if (!cropperRef.value) {
ElMessage.error('裁剪器未初始化');
return;
}
// 获取裁剪后的 base64
const base64 = cropperRef.value.getCroppedCanvas().toDataURL('image/jpeg', 0.9);
// 也可以获取 blob 用于上传
cropperRef.value.getCroppedCanvas().toBlob((blob) => {
// 这里可以处理 blob 上传到服务器
console.log('裁剪后的blob:', blob);
// 显示裁剪结果
croppedImageUrl.value = base64;
showCropDialog.value = false;
tempImageUrl.value = '';
fileList.value = []; // 清空文件列表
ElMessage.success('封面裁剪完成');
// 可以在这里触发上传或传递给父组件
emit('cover-cropped', { base64, blob });
}, 'image/jpeg', 0.9);
};
// 删除封面
const handleRemoveCover = () => {
croppedImageUrl.value = '';
ElMessage.info('已删除封面');
};
// 暴露方法给父组件(可选)
defineExpose({
getCoverData: () => {
return croppedImageUrl.value;
},
clearCover: () => {
croppedImageUrl.value = '';
}
});
</script>
<style scoped>
.cover-upload-container {
width: 100%;
}
.upload-demo {
margin-bottom: 20px;
}
.el-upload__tip {
margin-top: 8px;
font-size: 12px;
color: #909399;
}
/* 裁剪容器 */
.crop-container {
display: flex;
gap: 30px;
margin-bottom: 20px;
align-items: flex-start;
}
.crop-area {
flex: 1;
min-height: 400px;
max-height: 500px;
display: flex;
justify-content: center;
align-items: center;
background: #f8f9fa;
border-radius: 6px;
padding: 10px;
}
/* 修复 vue-cropper 样式 */
.cropper-instance {
width: 100% !important;
height: 400px !important;
max-width: 100%;
}
:deep(.cropper-container) {
direction: ltr !important;
font-size: 0;
line-height: 0;
position: relative;
touch-action: none;
user-select: none;
}
:deep(.cropper-wrap-box) {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
}
:deep(.cropper-canvas) {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.preview-area {
width: 180px;
text-align: center;
flex-shrink: 0;
}
.preview-title {
font-size: 14px;
color: #606266;
margin-bottom: 12px;
font-weight: 500;
}
.preview {
width: 135px;
height: 180px;
border: 1px solid #e4e7ed;
border-radius: 6px;
overflow: hidden;
margin: 0 auto;
background: #f5f7fa;
}
.preview-tip {
font-size: 12px;
color: #909399;
margin-top: 8px;
}
/* 裁剪操作区 */
.crop-actions {
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 20px;
flex-wrap: wrap;
padding: 15px;
background: #f8f9fa;
border-radius: 6px;
}
/* 提示信息 */
.crop-tips {
padding: 16px;
background: #f0f9ff;
border: 1px solid #e1f5fe;
border-radius: 6px;
font-size: 13px;
color: #606266;
line-height: 1.6;
}
.crop-tips h4 {
margin: 0 0 10px 0;
color: #1890ff;
font-size: 14px;
font-weight: 600;
}
.crop-tips p {
margin: 4px 0;
}
/* 结果预览 */
.result-preview {
margin-top: 20px;
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 16px;
background: #fafafa;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
color: #303133;
font-weight: 500;
}
.cover-image {
width: 150px;
height: 200px;
object-fit: cover;
border-radius: 6px;
border: 2px solid #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 响应式设计 */
@media (max-width: 768px) {
.crop-container {
flex-direction: column;
gap: 20px;
}
.crop-area {
min-height: 300px;
}
.cropper-instance {
height: 300px !important;
}
.preview-area {
width: 100%;
}
.preview {
width: 120px;
height: 160px;
}
.crop-actions {
flex-direction: column;
align-items: center;
}
.crop-actions .el-button {
width: 160px;
}
}
</style>
父组件使用
<cover-upload @cover-cropped="handleCoverCropped" />
const handleCoverCropped=(e)=>{
console.log(e,"base64");
defaultCover.value=e.base64
}
这样的话就能够实现
不过不要忘了安装 vue-cropper
这个小伙伴们应该都会npm 吧 我就不多说了 我目前写这个文章的目的是为了记录自己做过的功能 以及为了方便大家开发遇到问题 帮助大家快速完成需求 因为现在不是前端的时代 学习技术固然重要 但是在这个环境下 前端已经抄底的情况下
我希望大家不要纠结 代码学习怎么样 主要完成任务 拿到属于自己的报酬
能捞就捞
更多推荐



所有评论(0)