Vue Cropper
Vue Cropper is a lightweight and easy-to-integrate image cropping component designed for Vue.js applications. The current stable version, `1.1.1`, offers comprehensive support for both Vue 2 and Vue 3, allowing developers to implement image clipping functionalities across different project generations. It is primarily distributed via npm and follows an active, though somewhat irregular, release cadence, with updates primarily focusing on compatibility and bug fixes. Key differentiators include its straightforward API for image selection, scaling, rotation, and data extraction (base64 or blob), making it a popular choice for user profile picture uploads, content creation tools, and other scenarios requiring precise image manipulation directly within the browser. Its minimalist design aims to provide essential cropping features without excessive overhead, integrating seamlessly into existing Vue ecosystems.
Common errors
-
Cannot read properties of undefined (reading 'use')
cause Attempting to globally register the VueCropper plugin in a Vue 3 application using the Vue 2 `Vue.use()` syntax, or `Vue` is not correctly imported/defined.fixFor Vue 3, use `app.use(VueCropper)` where `app` is your Vue application instance (e.g., `const app = createApp(App); app.use(VueCropper); app.mount('#app');`). For Vue 2, ensure `import Vue from 'vue';` and then `Vue.use(VueCropper);`. -
Tainted canvases cannot be exported
cause This error occurs when `vue-cropper` tries to extract image data (e.g., `getCropData`, `getCropBlob`) from a canvas that contains content loaded from a different origin without proper CORS headers.fixConfigure the server hosting the image to send `Access-Control-Allow-Origin` headers. For development, you might use a browser extension to disable CORS, but this is not a production solution. Alternatively, load images via a proxy on the same origin as your application. -
Property 'getCropData' does not exist on type 'Vue | Element | ...'
cause This TypeScript error typically arises when using `ref` to reference the `VueCropper` component in a Vue 3 `<script setup>` block, but the ref's type is not correctly specified, or the component instance isn't available yet.fixEnsure the ref is correctly typed as the `VueCropper` component instance. For example, `const cropperRef = ref<InstanceType<typeof VueCropper> | null>(null);` or define an interface for the component's methods as shown in the quickstart. Also, ensure the component is mounted before trying to access its methods.
Warnings
- breaking The `vue-cropper` package underwent significant changes between `0.x` and `1.x` to introduce native support for Vue 3. While `0.x` was primarily for Vue 2, `1.x` is compatible with both Vue 2 and Vue 3. Migration from `0.x` to `1.x` may involve adjusting component registration methods and prop names, although the core API remains similar.
- gotcha When attempting to crop images loaded from a different origin (domain, port, or protocol), browsers enforce Cross-Origin Resource Sharing (CORS) policies. This can prevent the `vue-cropper` component from accessing image data on the canvas, leading to errors like 'Tainted canvases cannot be exported' or 'Image load failed'.
- gotcha Forgetting to import the component's CSS styles (`import 'vue-cropper/dist/index.css';`) is a common mistake, leading to a non-functional or unstyled cropper component.
- gotcha Handling large images, especially on less powerful devices or older browsers, can lead to performance issues, browser crashes, or slow responsiveness during cropping operations. The underlying `cropperjs` library may struggle with extremely high-resolution images.
- deprecated Older global API methods like `$on`, `$off`, `$once`, `$children`, and `filters` were removed in Vue 3. Applications migrating from Vue 2 to Vue 3 that heavily relied on these global methods for event communication or direct child component access will need refactoring.
Install
-
npm install vue-cropper -
yarn add vue-cropper -
pnpm add vue-cropper
Imports
- VueCropper
const VueCropper = require('vue-cropper');import VueCropper from 'vue-cropper';
- VueCropper (named)
import Cropper from 'vue-cropper';
import { VueCropper } from 'vue-cropper'; - InstanceType (for refs)
import type { InstanceType } from 'vue-cropper';import type { InstanceType } from 'vue';
Quickstart
<template>
<div class="cropper-container">
<input type="file" accept="image/*" @change="handleFileChange" />
<div class="cropper-wrapper" v-if="imgSrc">
<VueCropper
ref="cropperRef"
:img="imgSrc"
:output-size="outputSize"
:output-type="outputType"
:info="true"
:full="true"
:fixed-box="false"
:can-move="true"
:can-move-box="true"
:original="false"
:auto-crop="true"
:auto-crop-width="200"
:auto-crop-height="150"
@realTime="realTimePreview"
></VueCropper>
</div>
<button @click="cropImage" v-if="imgSrc">Crop Image</button>
<div class="preview" v-if="previewImg">
<h3>Preview:</h3>
<img :src="previewImg" alt="Cropped Image Preview" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import VueCropper from 'vue-cropper';
import 'vue-cropper/dist/index.css'; // Don't forget to import the CSS
// Define the type for the VueCropper instance
interface VueCropperInstance {
getCropData: (callback: (data: string) => void) => void;
getCropBlob: (callback: (blob: Blob) => void) => void;
// Add other methods if you plan to use them, e.g., setCropBoxData, zoom
}
const cropperRef = ref<VueCropperInstance | null>(null);
const imgSrc = ref<string | null>(null);
const previewImg = ref<string | null>(null);
const outputSize = ref(1); // Output image quality (0 to 1)
const outputType = ref('jpeg'); // Output image format (jpeg, png, webp)
const handleFileChange = (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
imgSrc.value = event.target?.result as string;
};
reader.readAsDataURL(file);
}
};
const cropImage = () => {
if (cropperRef.value) {
cropperRef.value.getCropData((data: string) => {
previewImg.value = data;
console.log('Cropped Base64:', data.substring(0, 50) + '...');
// For uploading blob:
// cropperRef.value?.getCropBlob((blob: Blob) => {
// console.log('Cropped Blob:', blob);
// // You can upload the blob to your server here
// });
});
}
};
const realTimePreview = (data: { img: string }) => {
// Optional: Update a smaller preview in real-time as the user crops
// For this quickstart, we'll just show the final crop on button click.
// If you want a live preview, you can set previewImg.value = data.img here.
};
// Watch for changes in imgSrc to reset preview when a new image is selected
watch(imgSrc, () => {
previewImg.value = null;
});
</script>
<style scoped>
.cropper-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
max-width: 600px;
margin: 20px auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.cropper-wrapper {
width: 100%;
max-width: 500px;
height: 300px;
background-color: #f8f8f8;
border: 1px dashed #ccc;
display: flex;
justify-content: center;
align-items: center;
}
.preview {
margin-top: 20px;
text-align: center;
}
.preview img {
max-width: 100%;
height: auto;
border: 1px solid #ddd;
border-radius: 4px;
}
input[type="file"] {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
button {
background-color: #42b983;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.2s ease;
}
button:hover {
background-color: #368a6f;
}
</style>