Prerequisites
Install the necessary dependencies to handle image manipulation and EXIF data:
npm install cropperjs exif-js
Plugin Architecture
Create a dedicated module to encapsulate cropping logic. This approach attaches methods to the Vue prototype, making them accessible globally.
File location: src/utils/imageClipper.js
import Cropper from 'cropperjs'
import Exif from 'exif-js'
class ImageCropper {
constructor () {
this.preview = null
this.cropperInstance = null
this.targetElement = null
this.options = {}
this.fileReference = null
}
install (Vue) {
Vue.prototype.setupCropper = (config) => {
const self = this
self.options = config || {}
self.renderOverlay()
self.bindEvents()
}
Vue.prototype.renderOverlay = function () {
let template = `
<div id="clip_overlay">
<img id="source_image" src="placeholder.jpg">
<button type="button" id="confirm_btn">Confirm</button>
<button type="button" id="cancel_btn">Cancel</button>
<div class="loading_state"></div>
<div class="success_state"></div>
</div>
`
const container = document.createElement('div')
container.id = 'clip_wrapper'
container.classList.add('overlay-container')
container.innerHTML = template
document.body.appendChild(container)
this.preview = document.getElementById('source_image')
this.targetElement = config && config.targetElement ? config.targetElement : null
}
Vue.prototype.bindEvents = function () {
const self = this
this.confirmBtn = document.getElementById('confirm_btn')
this.cancelBtn = document.getElementById('cancel_btn')
this.confirmBtn.addEventListener('click', () => {
self.processImage()
})
this.cancelBtn.addEventListener('click', () => {
self.terminateSession()
})
}
Vue.prototype.triggerCropping = function (event, options) {
const self = this
this.fileReference = event.target.files[0]
if (!this.fileReference) return
// Initialize configuration defaults
const defaultConfig = { aspectRatio: 1, resultTarget: this.targetElement }
const config = { ...defaultConfig, ...options }
self.options = config
this.generateURL(this.fileReference)
}
Vue.prototype.generateURL = function (file) {
if (window.createObjectURL) return window.createObjectURL(file)
if (window.URL) return window.URL.createObjectURL(file)
if (window.webkitURL) return window.webkitURL.createObjectURL(file)
return null
}
Vue.prototype.startSession = function (url) {
if (this.cropperInstance) {
this.cropperInstance.replace(url)
} else {
this.cropperInstance = new Cropper(this.preview, {
aspectRatio: this.options.aspectRatio || 1,
autoCropArea: this.options.autoCropArea || 0.8,
viewMode: 1,
zoomable: false,
ready: () => {
if (this.options.freeMode) {
this.cropperInstance.disable()
}
}
})
}
}
Vue.prototype.processImage = function () {
if (!this.cropperInstance) return
const croppedCanvas = this.cropperInstance.getCroppedCanvas()
if (!croppedCanvas) return
const dataUrl = croppedCanvas.toDataURL('image/jpeg', 0.9)
this.handleUpload(dataUrl)
}
Vue.prototype.handleUpload = function (data) {
document.querySelector('.loading_state').style.display = 'block'
setTimeout(() => {
if (this.options.resultTarget) {
this.options.resultTarget.src = data
}
// Trigger custom upload handler or API call here
console.log('Processed image data:', data.substring(0, 50))
document.querySelector('.loading_state').style.display = 'none'
document.querySelector('.success_state').style.display = 'block'
setTimeout(() => {
this.terminateSession()
}, 2000)
}, 500)
}
Vue.prototype.terminateSession = function () {
if (this.cropperInstance) {
this.cropperInstance.destroy()
this.cropperInstance = null
}
const wrapper = document.getElementById('clip_wrapper')
if (wrapper && wrapper.parentNode) {
wrapper.parentNode.removeChild(wrapper)
}
this.preview = null
}
}
export default {
install (Vue) {
Vue.prototype.$ImageCropper = new ImageCropper()
}
}
Application Entry Configuration
Import the plugin and register it in the main application file.
// main.js
import Vue from 'vue'
import App from './App.vue'
import ImageClipper from '@/utils/imageClipper'
Vue.use(ImageClipper)
new Vue({
el: '#app',
render: h => h(App),
})
Component Integrasion
Utilize the injected method within a component to trigger the cropping UI upon file selection.
<template>
<div class="avatar-upload-section">
<input type="file" accept="image/*" @change="handleFileSelect" />
<img :src="userAvatar" alt="User Avatar" ref="avatarPreview" />
</div>
</template>
<script>
export default {
data () {
return {
userAvatar: null
}
},
methods: {
handleFileSelect (event) {
this.$ImageCropper.triggerCropping(event, {
targetElement: this.$refs.avatarPreview,
aspectRatio: 1
})
}
}
}
</script>
Styling Setup
Apply CSS styles to manage the overlay appearance and responsiveness. Include these rules in your global stylesheet or component SCSS file.
.overlay-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
}
#clip_wrapper {
position: relative;
width: 100%;
height: 100%;
}
#source_image {
max-width: 100%;
display: block;
}
#confirm_btn {
position: absolute;
bottom: 40px;
right: 10%;
padding: 10px 30px;
background: #1AAD19;
color: white;
border: none;
border-radius: 4px;
}
#cancel_btn {
position: absolute;
bottom: 40px;
left: 10%;
padding: 10px 30px;
background: #E64340;
color: white;
border: none;
border-radius: 4px;
}
.loading_state,
.success_state {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: none;
background: rgba(0,0,0,0.7);
color: white;
padding: 1rem;
border-radius: 8px;
}