在现代 Web 应用中,图片处理是一个常见需求。上篇文章上手 cropperjs v2:一个优雅的图片裁剪解决方案简单介绍了 CropperJS 的图片裁剪方案,本文将介绍如何基于 CropperJS 构建一个功能丰富的图片编辑器,支持裁剪、滤镜、蒙层等功能。
编辑器核心功能
1. 组件结构
vue
<template>
<div class="image-editor">
<div class="editor-container" :style="containerStyle">
<cropper-canvas ref="canvas" :background="!showCanvasBg && !bgImage"
:initial-width="pos.width" :initial-height="pos.height">
<cropper-image :style="{ filter: filterAttr }" ref="image"
:scalable="scale" translatable :src="src"></cropper-image>
<cropper-selection initial-coverage="1">
<cropper-grid bordered covered></cropper-grid>
<cropper-crosshair centered></cropper-crosshair>
<!-- 蒙层系统 -->
<div v-if="mask" class="editor-mask"
:style="{ backgroundImage: `url(${mask})` }"></div>
<component v-if="maskComponent"
:is="maskComponent"
:color="color">
</component>
</cropper-selection>
</cropper-canvas>
</div>
<!-- 控制面板 -->```
<el-form class="img-attr" label-width="auto">
<el-form-item label="背景色:" v-if="showCanvasBg"><el-color-picker v-model="canvasBg" /></el-form-item>
<!-- 更多控制项... -->
</el-form-item>
</el-form>
<div class="action-buttons">
<button @click="confirm">确认</button>
<button @click="reset">重置</button>
<button @click="cancel">取消</button>
</div>
</div>
</div>
</template>
2. 滤镜系统实现
javascript
const filterAttr = computed(() => {
const blurAttr = props?.editOpt?.blur ? `blur(${blur.value}px)` : ""
const opaAttr = props?.editOpt?.opa && modeCom.value == 1 ? `opacity(${opa.value / 100})` : ""
const brightnessAttr = props?.editOpt?.brightness ? `brightness(${brightness.value / 100})` : ""
const saturationAttr = props?.editOpt?.saturation ? `saturate(${saturation.value / 100})` : ""
const contrastAttr = props?.editOpt?.contrast ? `contrast(${contrast.value / 100})` : ""
const grayAttr = props?.editOpt?.gray && gray.value ? `grayscale(${grayscale.value / 100})` : ""
const hueAttr = props?.editOpt?.hue ? `hue-rotate(${hue.value}deg)` : ""
const invertAttr = props?.editOpt?.invert ? `invert(${invert.value / 100})` : ""
const sepiaAttr = props?.editOpt?.sepia ? `sepia(${sepia.value / 100})` : ""
return `${blurAttr} ${opaAttr} ${brightnessAttr} ${saturationAttr} ${contrastAttr} ${grayAttr} ${hueAttr} ${invertAttr} ${sepiaAttr}`
})
3. 双模式处理逻辑
javascript
// 裁剪模式处理
const confirmClipMode = async () => {
const originalCanvas = await canvasRef.value.$toCanvas()
const offScreenCanvas = document.createElement("canvas")
offScreenCanvas.width = originalCanvas.width
offScreenCanvas.height = originalCanvas.height
const ctx = offScreenCanvas.getContext("2d")
// 由于需要储存的是jpeg文件,这里根据选择的背景色做下处理,否则默认是个黑底
ctx.fillStyle = canvasBg.value
ctx.fillRect(0, 0, offScreenCanvas.width, offScreenCanvas.height)
ctx.drawImage(originalCanvas, 0, 0)
const dataURL = offScreenCanvas.toDataURL("image/jpeg")
return {
dataURL
}
}
// 画布模式处理
const confirmCanvasMode = async () => {
// 获取画布布局
const canvasElement = canvasRef.value
const canvasRect = canvasElement.getBoundingClientRect()
// 获取画布和图片的尺寸
const imgElement = imageRef.value.querySelector("img") || imageRef.value.children[0] || imageRef.value.shadowRoot.querySelector("img")
const imgRect = imgElement.getBoundingClientRect()
const imgWidth = imgElement.naturalWidth
const imgHeight = imgElement.naturalHeight
// 图片尺寸
const scaledWidth = imgRect.width
const scaledHeight = imgRect.height
// 最终坐标(相对于画布左上角)
const x = imgRect.left - canvasRect.left
const y = imgRect.top - canvasRect.top
// 生成图片dataURL
const canvas = document.createElement("canvas")
canvas.width = scaledWidth
canvas.height = scaledHeight
const ctx = canvas.getContext("2d")
ctx.filter = filterAttr.value
ctx.drawImage(imgElement, 0, 0, imgWidth, imgHeight, 0, 0, scaledWidth, scaledHeight)
const dataURL = canvas.toDataURL("image/jpeg")
return {
dataURL,
x: Math.round(x),
y: Math.round(y),
width: Math.round(scaledWidth),
height: Math.round(scaledHeight)
}
}
与函数式弹窗集成
依照之前文章仿 ElementPlus 的函数式弹窗调用创建弹窗使用。
1. 创建编辑器弹窗
vue
<!-- ImageEditorDialog.vue -->
<template>
<div class="editor-dialog">
<ImageEditor
:src="src"
:pos="pos"
:options="options"
@confirm="handleConfirm"
@cancel="handleCancel"
/>
</div>
</template>
<script setup>
import ImageEditor from './ImageEditor.vue'
const props = defineProps({
src: String,
pos: Object,
options: Object
})
const emit = defineEmits(['confirm', 'cancel'])
const handleConfirm = (data) => {
emit('confirm', data)
}
const handleCancel = () => {
emit('cancel')
}
</script>
2. 函数式调用接口
javascript
// imageEditor.js
import { createDialog } from './dialogManager'
import ImageEditorDialog from './ImageEditorDialog.vue'
export async function openImageEditor(src, options = {}) {
const defaultOptions = {
pos: { width: 800, height: 600 },
modeList: ['crop', 'canvas'],
editOptions: {
blur: true,
brightness: true,
saturation: true
}
}
const result = await createDialog(ImageEditorDialog, {
src,
...defaultOptions,
...options
})
return result
}
3. 业务中使用
javascript
// 在业务组件中使用
import { openImageEditor } from './imageEditor'
const handleEditImage = async (imageFile) => {
try {
const imageUrl = URL.createObjectURL(imageFile)
const result = await openImageEditor(imageUrl, {
pos: { width: 400, height: 300 },
editOptions: {
brightness: true,
contrast: true
},
mask: require('./assets/mask.png')
})
if (result.action === 'confirm') {
// 处理编辑后的图片
await saveEditedImage(result.data)
}
// 清理资源
URL.revokeObjectURL(imageUrl)
} catch (error) {
console.error('图片编辑失败:', error)
}
}
总结
这个基于 cropperjs v2 的图片编辑器展示了如何将简单的裁剪功能扩展为全面的图片处理解决方案。通过双模式设计、丰富的滤镜系统、灵活的蒙层支持和优雅的弹窗管理,我们创建了一个既强大又易用的编辑工具。