A Vue.js component that provides comprehensive image cropping functionality for Vue 2 applications.
—
Comprehensive event system for responding to image loading, cropping interactions, and real-time preview updates in VueCropper.
Events fired during image loading and processing phases.
/**
* Image load completion event
* @param status - "success" for successful load, "error" for failures, or Error object
*/
@img-load: (status: 'success' | 'error' | Error) => void;
/**
* Detailed error information when image loading fails
* @param error - Error object with failure details
*/
@img-load-error: (error: Error) => void;Usage Examples:
<template>
<VueCropper
:img="imageUrl"
@img-load="handleImageLoad"
@img-load-error="handleImageError"
/>
</template>
<script>
export default {
methods: {
handleImageLoad(status) {
if (status === 'success') {
console.log('Image loaded successfully');
this.imageReady = true;
} else if (status === 'error') {
console.error('Failed to load image');
this.showErrorMessage = true;
} else if (status instanceof Error) {
console.error('Image load error:', status.message);
}
},
handleImageError(error) {
console.error('Detailed error:', error);
this.errorDetails = error.message;
this.showRetryOption = true;
}
}
};
</script>Events fired during user interactions with the image and crop box.
/**
* Image movement/dragging events
* @param data - Movement data with position information
*/
@img-moving: (data: MovingData) => void;
/**
* Crop box movement/dragging events
* @param data - Movement data with crop box position
*/
@crop-moving: (data: MovingData) => void;
/**
* Crop box size change events (modern name)
* @param data - Size change data with dimensions
*/
@change-crop-size: (data: CropSizeData) => void;
/**
* Legacy camelCase events (for compatibility)
*/
@imgLoad: (status: 'success' | 'error' | Error) => void;
@imgMoving: (data: MovingData) => void;
@realTime: (data: PreviewData) => void;
@changeCropSize: (data: CropSizeData) => void;
@cropMoving: (data: MovingData) => void;
interface MovingData {
moving: boolean;
axis: {
x1: number; // top-left x coordinate
x2: number; // top-right x coordinate
y1: number; // top-left y coordinate
y2: number; // bottom-right y coordinate
};
}
interface CropSizeData {
width: number; // crop box width
height: number; // crop box height
}Usage Examples:
<template>
<VueCropper
:img="imageUrl"
@img-moving="handleImageMove"
@crop-moving="handleCropMove"
@change-crop-size="handleCropSizeChange"
/>
<div class="status-panel">
<p>Image Moving: {{ imageMoving ? 'Yes' : 'No' }}</p>
<p>Crop Position: {{ cropPosition.x }}, {{ cropPosition.y }}</p>
<p>Crop Size: {{ cropSize.width }} × {{ cropSize.height }}</p>
</div>
</template>
<script>
export default {
data() {
return {
imageMoving: false,
cropPosition: { x: 0, y: 0 },
cropSize: { width: 0, height: 0 }
};
},
methods: {
handleImageMove(data) {
this.imageMoving = data.moving;
if (data.moving) {
console.log('Image being dragged to:', data.axis);
// Update UI to show drag state
this.showDragIndicator = true;
} else {
this.showDragIndicator = false;
}
},
handleCropMove(data) {
if (data.moving) {
this.cropPosition = {
x: data.axis.x1,
y: data.axis.y1
};
// Sync with external preview or coordinates display
this.updateExternalPreview(data.axis);
}
},
handleCropSizeChange(data) {
this.cropSize = {
width: data.width,
height: data.height
};
// Validate minimum/maximum sizes
if (data.width < 50 || data.height < 50) {
this.showSizeWarning = true;
}
}
}
};
</script>Core event for implementing live preview functionality.
/**
* Real-time preview updates during cropping operations
* @param data - Complete preview data for rendering external previews
*/
@real-time: (data: PreviewData) => void;
interface PreviewData {
url: string; // preview image URL/base64
img: string; // CSS transform styles for image element
div: string; // CSS transform styles for container element
w: number; // preview width in pixels
h: number; // preview height in pixels
}Usage Examples:
<template>
<div class="cropper-layout">
<div class="main-cropper">
<VueCropper
:img="imageUrl"
@real-time="handleRealTimePreview"
/>
</div>
<div class="preview-panels">
<!-- Large Preview -->
<div class="preview-large">
<h3>Large Preview</h3>
<div
class="preview-container"
:style="{ width: previews.w + 'px', height: previews.h + 'px' }"
>
<div :style="previews.div">
<img :src="previews.url" :style="previews.img">
</div>
</div>
</div>
<!-- Small Preview -->
<div class="preview-small">
<h3>Small Preview (50%)</h3>
<div
class="preview-container"
:style="{
width: (previews.w * 0.5) + 'px',
height: (previews.h * 0.5) + 'px',
overflow: 'hidden'
}"
>
<div :style="{
...parseCSSTransform(previews.div),
zoom: 0.5
}">
<img :src="previews.url" :style="previews.img">
</div>
</div>
</div>
<!-- Fixed Size Preview -->
<div class="preview-fixed">
<h3>Avatar Preview (100x100)</h3>
<div
class="preview-container avatar-preview"
:style="{
width: '100px',
height: '100px',
overflow: 'hidden',
borderRadius: '50%'
}"
>
<div :style="{
...parseCSSTransform(previews.div),
zoom: 100 / previews.w
}">
<img :src="previews.url" :style="previews.img">
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
imageUrl: '/path/to/image.jpg',
previews: {
url: '',
img: '',
div: '',
w: 0,
h: 0
}
};
},
methods: {
handleRealTimePreview(data) {
this.previews = data;
// Optional: emit to parent component
this.$emit('preview-updated', data);
// Optional: store for later use
this.lastPreviewData = data;
},
parseCSSTransform(cssString) {
// Helper to parse CSS transform string into object
// Implementation depends on your specific needs
return {
transform: cssString
};
}
}
};
</script>
<style scoped>
.cropper-layout {
display: flex;
gap: 20px;
}
.main-cropper {
flex: 1;
height: 400px;
}
.preview-panels {
width: 300px;
display: flex;
flex-direction: column;
gap: 15px;
}
.preview-container {
border: 1px solid #ddd;
margin: 5px;
position: relative;
}
.avatar-preview {
border: 2px solid #007bff;
}
</style>Install with Tessl CLI
npx tessl i tessl/npm-vue-cropper