Vue-Cropper 是一个基于 Vue.js 的图片裁剪组件库,专为 Web 应用设计。当你在网上搜索的时候发现还有一个叫cropper的库,下面是他们的区别:
特性 | cropper.js | vue-cropper |
---|---|---|
框架依赖 | 纯 JavaScript,无框架依赖 | 专为 Vue.js 设计 |
包体积 | ~200 KB (含样式) | ~45 KB (压缩后) |
API 调用方式 | 原生 DOM 操作 | Vue 组件式 API |
响应式支持 | 需手动实现 | 原生支持 Vue 响应式 |
功能完整性 | 基础裁剪+扩展插件 | 基础裁剪+常用扩展 |
学习曲线 | 较高(需熟悉原生 API) | 较低(Vue 开发者友好) |
社区生态 | GitHub 32k+ Star | GitHub 3.5k+ Star |
移动端支持 | 需额外配置 | 原生优化 |
vue-cropper的优势:
• 纯前端实现,减轻服务器压力
• 响应式设计,完美适配移动端
• 灵活的配置选项
• 实时预览功能
• 支持多种输出格式
我正在开发的项目是基于vue3的,所以就对于vue-cropper进行讲解
//npm 安装
npm install vue-cropper
//yarn 安装
yarn add vue-cropper
//Vue 3 组件内引入
npm install vue-cropper@next
import 'vue-cropper/dist/index.css'
import { VueCropper } from "vue-cropper";
//Vue3 全局引入
import VueCropper from 'vue-cropper';
import 'vue-cropper/dist/index.css'
const app = createApp(App)
app.use(VueCropper)
app.mount('#app')
<template>
<div class="container">
<input type="file" @change="handleFileChange" />
<vue-cropper
ref="cropper"
:img="imageSrc"
:auto-crop="false"
:aspect-ratio="16/9"
/>
<button @click="getResult">获取裁剪结果</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const imageSrc = ref('')
const cropper = ref(null)
const handleFileChange = (e) => {
const file = e.target.files[0]
const reader = new FileReader()
reader.onload = (e) => {
imageSrc.value = e.target.result
}
reader.readAsDataURL(file)
}
const getResult = () => {
cropper.value.getCropBlob(blob => {
const formData = new FormData()
formData.append('file', blob)
// 上传逻辑...
})
}
</script>
const options = reactive({
img: '', // 图片源地址
outputSize: 1, // 输出质量(0-1)
outputType: 'png',// 输出格式:png | jpeg | webp
info: true, // 显示裁剪信息
canScale: true, // 允许缩放
autoCrop: true, // 自动生成裁剪框
fixed: false, // 固定裁剪框尺寸
fixedNumber: [4, 3], // 固定比例 [宽, 高]
full: false // 是否全屏模式
})
固定16:9的案例:
<vue-cropper
:fixed="true"
:fixed-number="[16, 9]"
:img="imageSrc"
@realTime="handlePreview"
/>
const outputOptions = {
types: ['png', 'jpeg', 'webp'],
quality: {
jpeg: 0.8,
webp: 0.7
}
}
const handleExport = (type) => {
cropper.value.getCropData(type, (dataURL) => {
const link = document.createElement('a')
link.download = `cropped.${type}`
link.href = dataURL
link.click()
})
}
<template>
<div class="avatar-editor">
<n-upload
accept="image/*"
:before-upload="handleUpload"
>
<n-button>选择头像</n-button>
</n-upload>
<n-modal v-model:show="showCropper">
<n-card style="width: 600px">
<vue-cropper
ref="cropper"
:img="tempImage"
:fixed="true"
:fixed-number="[1, 1]"
:output-size="0.8"
/>
<div class="action-buttons">
<n-button @click="rotate(-90)">左旋转</n-button>
<n-button @click="rotate(90)">右旋转</n-button>
<n-button @click="saveAvatar">保存</n-button>
</div>
</n-card>
</n-modal>
</div>
</template>
<script setup>
// 完整业务逻辑
const showCropper = ref(false)
const tempImage = ref('')
const handleUpload = async (file) => {
const reader = new FileReader()
reader.onload = (e) => {
tempImage.value = e.target.result
showCropper.value = true
}
reader.readAsDataURL(file)
return false
}
const rotate = (degree) => {
cropper.value.rotate(degree)
}
const saveAvatar = async () => {
cropper.value.getCropBlob(async (blob) => {
const formData = new FormData()
formData.append('avatar', blob)
try {
await axios.post('/api/upload-avatar', formData)
message.success('头像更新成功')
showCropper.value = false
} catch (error) {
message.error('上传失败')
}
})
}
</script>
/* 自定义主题 */
.vue-cropper {
--crop-border: 2px dashed #f00;
--preview-bg: #f8f9fa;
.crop-box {
box-shadow: 0 0 10px rgba(0,0,0,0.5);
}
.info-item {
color: #1890ff;
font-size: 12px;
}
}
// 压缩处理函数
const optimizeImage = (blob) => {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = 800
canvas.height = (800 * img.height) / img.width
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
canvas.toBlob(resolve, 'image/webp', 0.8)
}
img.src = URL.createObjectURL(blob)
})
}
<vue-cropper
:touch="true"
:movable="false"
:zoom-on-touch="true"
:zoom-on-wheel="false"
/>
/* 移动端优化 */
@media (max-width: 768px) {
.vue-cropper {
--crop-box-width: 300px;
--crop-box-height: 300px;
.action-buttons {
flex-direction: column;
button {
margin: 5px 0;
}
}
}
}
解决方案:
// 确保同时配置
fixed: true,
fixedNumber: [3, 2],
aspectRatio: 3/2
处理方案:
const checkImage = (file) => {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
const ratio = img.width / img.height
if (Math.abs(ratio - 3/2) > 0.1) {
message.warning('建议选择3:2比例的图片')
}
resolve(true)
}
img.src = URL.createObjectURL(file)
})
}
以下是全部的参数以及取值要求
名称 | 功能 | 默认值 | 可选值 |
---|---|---|---|
img | 裁剪图片的地址 | 空 | url 地址, base64, blob |
outputSize | 裁剪生成图片的质量 | 1 | 0.1 ~ 1 |
outputType | 裁剪生成图片的格式 | jpg (需传入 jpeg) | jpeg, png, webp |
info | 裁剪框的大小信息 | true | true, false |
canScale | 图片是否允许滚轮缩放 | true | true, false |
autoCrop | 是否默认生成截图框 | false | true, false |
autoCropWidth | 默认生成截图框宽度 | 容器的 80% | 0 ~ max |
autoCropHeight | 默认生成截图框高度 | 容器的 80% | 0 ~ max |
fixed | 是否开启截图框宽高固定比例 | false | true, false |
fixedNumber | 截图框的宽高比例(开启 fixed 生效) | [1, 1] | [ 宽度 , 高度 ] |
full | 是否输出原图比例的截图 | false | true, false |
fixedBox | 固定截图框大小 | 不允许改变 | true, false |
canMove | 上传图片是否可以移动 | true | true, false |
canMoveBox | 截图框能否拖动 | true | true, false |
original | 上传图片按照原始比例渲染 | false | true, false |
centerBox | 截图框是否被限制在图片里面 | false | true, false |
high | 是否按照设备的 dpr 输出等比例图片 | true | true, false |
infoTrue | 展示真实输出图片宽高 | false | true, false |
maxImgSize | 限制图片最大宽度和高度 | 2000 | 0 ~ max |
enlarge | 图片根据截图框输出比例倍数 | 1 | 0 ~ max(建议不要太大) |
mode | 图片默认渲染方式 | contain | contain, cover, 100px, 100% auto |
limitMinSize | 裁剪框限制最小区域 | 10 | Number, Array, String |
fillColor | 导出时背景颜色填充 | 空 | #ffffff, white |