Vue3实现高效图片剪裁与上传功能,提升前端开发体验
一、项目背景与技术选型
技术选型:
- Vue3:前端框架,提供响应式数据管理和组件化开发。
- Element Plus:基于Vue3的UI组件库,提供丰富的UI组件。
- Canvas:用于前端图片处理,实现图片压缩和剪裁。
- Node.js:后端运行环境,处理文件上传和存储。
- multiparty:Node.js中间件,用于处理multipart/form-data类型的请求。
二、前端实现
1. 项目初始化
首先,我们需要创建一个新的Vue3项目,并安装必要的依赖。
vue create my-image-upload-project
cd my-image-upload-project
npm install element-plus vue-upload-cropper axios
2. 引入Element Plus
在main.js中引入Element Plus,并全局注册。
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
3. 创建图片上传与剪裁组件
<template>
<el-form :model="form" label-width="auto" style="max-width: 800px">
<el-form-item label="上传封面">
<el-upload
drag
class="avatar-uploader"
method="post"
action="/api/image"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img v-if="form.imgUrl" :src="`http://127.0.0.1:3000/getimg?url=${form.imgUrl}`" class="avatar" />
<div v-else>
<el-icon class="avatar-uploader-icon">
<Plus />
</el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</div>
</el-upload>
</el-form-item>
</el-form>
</template>
<script>
import { ref } from 'vue'
import { Plus } from '@element-plus/icons-vue'
export default {
components: {
Plus
},
setup() {
const form = ref({
imgUrl: ''
})
const handleAvatarSuccess = (response, file) => {
form.value.imgUrl = response.url
}
const beforeAvatarUpload = (file) => {
const isJPG = file.type === 'image/jpeg'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
alert('上传头像图片只能是 JPG 格式!')
}
if (!isLt2M) {
alert('上传头像图片大小不能超过 2MB!')
}
return isJPG && isLt2M
}
return {
form,
handleAvatarSuccess,
beforeAvatarUpload
}
}
}
</script>
<style scoped>
.avatar-uploader {
display: block;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar {
width: 100%;
height: auto;
display: block;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100%;
height: 100%;
line-height: 100px;
text-align: center;
}
</style>
4. 图片压缩与剪裁
<template>
<el-dialog title="图片剪裁" :visible.sync="dialogVisible">
<vue-upload-cropper
ref="cropper"
:options="cropperOptions"
@crop="handleCrop"
></vue-upload-cropper>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmCrop">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
import VueUploadCropper from 'vue-upload-cropper'
export default {
components: {
VueUploadCropper
},
data() {
return {
dialogVisible: false,
cropperOptions: {
aspectRatio: 1 / 1,
viewMode: 1,
dragMode: 'move',
cropBoxResizable: false
}
}
},
methods: {
handleCrop(data) {
this.croppedImage = data.canvas.toDataURL('image/jpeg')
},
confirmCrop() {
this.form.imgUrl = this.croppedImage
this.dialogVisible = false
}
}
}
</script>
三、后端实现
1. Node.js服务器搭建
使用Express框架搭建一个简单的Node.js服务器。
npm install express multer multiparty
const express = require('express')
const multer = require('multer')
const multiparty = require('multiparty')
const app = express()
const port = 3000
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + '.jpg')
}
})
const upload = multer({ storage: storage })
app.post('/api/image', upload.single('file'), (req, res) => {
res.json({ url: `/uploads/${req.file.filename}` })
})
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`)
})
2. 处理图片上传
app.post('/api/image', upload.single('file'), (req, res) => {
if (req.file) {
res.json({ url: `/uploads/${req.file.filename}` })
} else {
res.status(400).send('No file uploaded.')
}
})
四、前端性能优化
1. 图片压缩
const compressImage = (file, quality = 0.7) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (e) => {
const img = new Image()
img.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
canvas.toBlob((blob) => {
resolve(blob)
}, 'image/jpeg', quality)
}
img.src = e.target.result
}
reader.onerror = (e) => {
reject(e)
}
reader.readAsDataURL(file)
})
}
const beforeAvatarUpload = async (file) => {
const compressedFile = await compressImage(file)
const formData = new FormData()
formData.append('file', compressedFile, file.name)
axios.post('/api/image', formData).then((response) => {
form.value.imgUrl = response.data.url
})
}