I have the following vue.js code which opens a modal that allows the user to crop an area of a selected image for upload. The problem is that I cannot adjust the size of the canvas and the image to be larger than what is currently being displayed. When I explicitly set set the width and the size of the cropper modal modal/canvas and image to be 500px by 100px using the css, the canvas and image do not conform to the specified dimension.
How can I resolve this to create the intended effect?
Attempted css solution which did not work

.cropper-modal {
height: 500px !important;
width: 1000px !important;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.7);
height: 500px !important;
width: 1000px !important;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.cropper-modal-content {
height: 500px !important;
width: 1000px !important;
background: #fff;
padding: 2rem;
border-radius: 8px;
max-width: 90vw;
max-height: 90vh;
overflow: auto;
text-align: center;
}
.cropper-modal-content img,
.cropper-modal-content .cropper-container,
.cropper-modal-content canvas{
height: 500px !important;
width: 1000px !important;
}
imageUpload.vue (existing code)

<template>
<div class="col-md-9 p-0 w-80 feedback-tabs settings-row">
<div class="p-1 p-md-4">
<div class="row mb-2 mb-md-5 mt-2">
<div class="col-md-6 my-auto">
<div class="d-flex iwon-lost">
<h2 class="mb-0 me-md-4">Settings</h2>
</div>
</div>
</div>
<ul class="nav nav-tabs border-0" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="simple-tab-0" data-bs-toggle="tab" href="#general" role="tab" aria-controls="simple-tabpanel-0" aria-selected="true">General</a>
</li>
<li class="nav-item" role="presentation" v-if="user.subscription && user.subscription.allow_branding">
<a class="nav-link" id="simple-tab-3" data-bs-toggle="tab" href="#branding" role="tab" aria-controls="simple-tabpanel-3" aria-selected="false">Branding</a>
</li>
</ul>
<div class="tab-content pt-3 ps-4 pe-4 pb-2 pb-md-4 bg-white tab-mob-pad" id="tab-content">
<div class="tab-pane active" id="general" role="tabpanel" aria-labelledby="simple-tab-0">
<div class="row pt-3 mb-3">
<div class="col-md-12">
<h4 class="mb-2 poppins-semibold font-32">Profile Details</h4>
<small>Update your profile detail here</small>
</div>
</div>
<div class="row profile-info mt-4">
<div class="col-md-6">
<div class="d-flex justify-content-center justify-content-md-start align-items-center">
<div class="user-pic position-relative photo-container">
<img v-if="profileImageFile" :src="profileImagePreview" class="mb-3"/>
<img v-else-if="user.photo && user.photo.name" :src="`${base_url}/get-uploaded-image/${user.photo?.name}`" class="mb-3"/>
<img v-else src="../../../assets/images/john-doe.png" alt="avater"/>
<label v-if="isEdit" for="upload-pic" class="cam-icon">
<img src="../../../assets/images/camera.svg" alt="pic">
<input type="file" name="upload-pic" id="upload-pic" ref="profileImage" @change="handleProfileImageUpload()">
</label>
</div>
<div class="info">
<h5 class="mb-0 poppins-semibold font-24"> {{ user.first_name + ' ' + user.last_name }} </h5>
<small>{{ user.email }}</small>
</div>
</div>
</div>
<div class="col-md-6 text-md-end my-auto mt-5 mt-md-0 place-bid">
<a v-if="!isEdit" href="#" class="btn-style p-2 ps-4 pe-4 mb-3 d-inline-block font-20" @click.prevent="isEdit = true">EDIT PROFILE</a>
<br>
<a v-if="isEdit" href="#" class="update-profile" @click.prevent="submit()">UPDATE PROFILE</a>
</div>
</div>
</div>
<div v-if="user.subscription && user.subscription.allow_branding" class="tab-pane update-profile-sec" id="branding" role="tabpanel" aria-labelledby="simple-tab-3">
<div class="row pt-3 mb-3">
<div class="col-md-12">
<h4 class="mb-2 poppins-semibold font-32">Profile Details</h4>
<small>Update your profile detail here</small>
</div>
</div>
<div class="row profile-info mt-4">
<div class="col-md-6">
<div class="d-flex justify-content-center justify-content-md-start align-items-center">
<div class="user-pic position-relative photo-container">
<img v-if="brandingProfileImageFile" :src="brandingProfileImagePreview" class="mb-3"/>
<img v-else-if="branding.photo && branding.photo.name" :src="`${base_url}/get-uploaded-image/${branding.photo?.name}`" class="mb-3"/>
<img v-else src="../../../assets/images/john-doe.png" alt="avater"/>
</div>
<div class="info">
<h5 class="mb-0 poppins-semibold font-24"> {{ branding.name }} </h5>
<small>{{ branding.email }}</small>
</div>
</div>
</div>
<div class="col-md-6 place-bid justify-content-end align-items-center d-none d-md-flex">
<a href="#" class="btn-style p-2 ps-4 pe-4 mb-3 d-inline-block font-20" @click.prevent="submitBranding()">Save Changes</a>
</div>
</div>
<div class="row border-top form py-4">
<div class="col-md-3 d-flex align-items-center">
<label class="dark-grey font-24 poppins-med">Name</label>
</div>
<div class="col-md-3">
<input class="w-100" type="text" name="" v-model="branding.name" :class="{ 'border-danger': v$.branding.name.$error }">
<p class="text-danger" v-if="v$.branding.name.$errors[0]?.$validator === 'required' && v$.branding.name.$errors[0]?.$property === 'name'">
{{v$.branding.name.required.$message}}
</p>
</div>
<div class="col-md-3 d-none d-md-flex align-items-center">
<label class="dark-grey font-24 poppins-med">Email</label>
</div>
<div class="col-md-3 d-none d-md-block">
<input class="w-100" type="email" name="" v-model="branding.email" :class="{ 'border-danger': v$.branding.email.$error }">
<p class="text-danger" v-if="v$.branding.email.$errors[0]?.$validator === 'required' && v$.branding.email.$errors[0]?.$property === 'email'">
{{v$.branding.email.required.$message}}
</p>
</div>
</div>
<div class="row border-top form py-4 d-md-none">
<div class="d-flex align-items-center">
<label class="dark-grey font-24 poppins-med">Email</label>
</div>
<div>
<input class="w-100" type="email" name="" v-model="branding.email" :class="{ 'border-danger': v$.branding.email.$error }">
<p class="text-danger" v-if="v$.branding.email.$errors[0]?.$validator === 'required' && v$.branding.email.$errors[0]?.$property === 'email'">
{{v$.branding.email.required.$message}}
</p>
</div>
</div>
<div class="row border-top form py-4">
<div class="col-md-3 d-flex align-items-center">
<label class="dark-grey font-24 poppins-med">Profile Image</label>
</div>
<div class="col-md-3">
<input class="w-100" type="file" name="upload-branding-profile" id="upload-branding-profile" ref="brandingProfileImage" @change="handleBrandingProfileImageUpload()" hidden>
<input class="w-100" type="text" name="" :value="brandingProfileImageFile?.name ?? branding.photo?.name" readonly>
</div>
<div class="col-md-6 d-flex align-items-center justify-content-end place-bid">
<a href="#" class="btn-style p-2 ps-4 pe-4 d-inline-block font-20 bg-black" @click.prevent="handleClickBrandingProfileImageUpload()">Upload File</a>
</div>
</div>
<div class="row border-top form py-4">
<div class="col-md-3 d-flex align-items-center">
<label class="dark-grey font-24 poppins-med">Brand Logo</label>
</div>
<div class="col-md-3">
<input class="w-100" type="file" name="upload-logo" id="upload-logo" ref="brandingLogoImage" @change="handleBrandingLogoImageUpload()" hidden>
<input class="w-100" type="text" name="" :value="brandingLogoImageFile?.name ?? branding.logo?.name" readonly>
</div>
<div class="col-md-4 dark-grey font-18">
The logo image aspect ratio should be 7:1
</div>
<div class="col-md-2 d-flex align-items-center justify-content-end place-bid">
<a href="#" class="btn-style p-2 ps-4 pe-4 d-inline-block font-20 bg-black" @click.prevent="handleClickBrandingLogoImageUpload()">Upload File</a>
</div>
</div>
<div class="row border-top form py-4">
<div class="col-md-3 d-flex align-items-center">
<label class="dark-grey font-24 poppins-med">Brand Banner</label>
</div>
<div class="col-md-3">
<input class="w-100" type="file" name="upload-banner" id="upload-banner" ref="brandingBannerImage" @change="handleBrandingBannerImageUpload()" hidden>
<input class="w-100" type="text" name="" :value="brandingBannerImageFile?.name ?? branding.banner?.name" readonly>
</div>
<div class="col-md-4 dark-grey font-18">
The banner image aspect ratio should be 125:20
</div>
<div class="col-md-2 d-flex align-items-center justify-content-end place-bid">
<a href="#" class="btn-style p-2 ps-4 pe-4 d-inline-block font-20 bg-black" @click.prevent="handleClickBrandingBannerImageUpload()">Upload File</a>
</div>
</div>
<div class="row border-top form py-4">
<div class="col-md-3 d-flex align-items-center">
<label class="dark-grey font-24 poppins-med">Website Link</label>
</div>
<div class="col-md-9">
<input class="w-100" type="text" name="" v-model="branding.link">
</div>
</div>
<div class="row border-top form py-4">
<div class="col-md-3 d-flex">
<label class="dark-grey font-24 poppins-med">Brand Description</label>
</div>
<div class="col-md-9">
<textarea type="text" name="" rows="6" v-model="branding.description"></textarea>
</div>
<div class="col-12 d-md-none">
<a href="#" class="btn-style p-2 ps-4 pe-4 mb-3 d-inline-block font-20 w-100" @click.prevent="submitBranding()">Save Changes</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Cropper Modal -->
<div v-if="showCropperModal" class="cropper-modal">
<div class="cropper-modal-content">
<img :src="cropperImageUrl" ref="cropperImage" style="max-width:100%;" />
<div class="mt-3">
<button @click="cropImage" class="btn btn-success">Crop & Save</button>
<button @click="closeCropper" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
</template>
<script>
import 'https://js.stripe.com/v3/'
import { GOOGLE_MAP_API_KEY } from '@/constants'
import DeleteConfirmationModal from '@/components/Modal/DeleteConfirmationModal.vue'
import ConfirmationModal from '@/components/Modal/ConfirmationModal.vue'
import { useVuelidate } from '@vuelidate/core'
import { helpers, required, email, requiredIf, sameAs, integer, minValue, minLength } from '@vuelidate/validators'
import { toast } from 'vue-sonner'
import { RepositoryFactory } from '@/repositories'
import Cropper from 'cropperjs'
const Profile = RepositoryFactory.get('profile')
const imageUploader = RepositoryFactory.get('imageUploader')
const Branding = RepositoryFactory.get('branding')
export default {
name: 'UserSettings',
components: {
DeleteConfirmationModal, ConfirmationModal
},
data() {
return {
base_url: import.meta.env.VITE_API_URL,
stripeKey: import.meta.env.VITE_STRIPE_PUBLIC_KEY,
gMapApiKey: GOOGLE_MAP_API_KEY,
pageTitle: "Add New User",
userId: this.$route.params.id ? this.$route.params.id : null,
isEdit: false,
user: {
username: "",
first_name: "",
last_name: "",
email: "",
phone_number: "",
address: "",
country: null,
photo: ""
},
branding: {
name: "",
email: "",
photo: "",
logo: "",
banner: "",
link: "",
description: "",
},
profileImageFile: null,
profileImagePreview: null,
brandingProfileImageFile: null,
brandingProfileImagePreview: null,
brandingLogoImageFile: null,
brandingBannerImageFile: null,
agentImageFile: null,
agentImagePreview: null,
showDeleteModal: false,
isImageError: false,
v$: useVuelidate(),
showCropperModal: false,
cropper: null,
cropperImageType: null, // 'logo' or 'banner'
cropperImageFile: null,
cropperImageUrl: null,
}
},
mounted() {
this.getUserProfile()
this.getBranding()
},
validations() {
return {
branding: {
name: {
required: helpers.withMessage(
'Name is required',
required
)
},
email: {
required: helpers.withMessage(
'Email is required',
required
),
email: helpers.withMessage(
'Please enter a valid email',
email
)
},
}
}
},
methods: {
getUserProfile() {
Profile.getProfile().then(response => {
if (response.status == 200) {
this.user = response.data.profile
if (this.user.platform === 'web' || this.user.platform === 'android') {
this.setupStripe()
}
}
})
},
getBranding() {
this.setLoader(true)
Branding.getBranding().then(response => {
if (response.status === 200) {
this.branding = response.data.branding
}
this.setLoader(false)
}).catch(response => {
this.setLoader(false)
})
},
async submit() {
this.v$.user.$touch()
if (!this.user.country && this.user.address) {
this.addressError = true
toast.error("Please select address from dropdown")
return
}
if (!this.v$.user.$error) {
let profileImageId = null
try {
[profileImageId] = await Promise.all([
this.profileImageFile ? this.imageUpload(this.profileImageFile) : null
]);
if (profileImageId) {
this.user.photo = profileImageId;
}
Profile.update(this.user).then(response => {
if (response.status == 200) {
toast.success(response.data.message)
let profileData = this.profile
profileData.user.photo = response.data.updated.photo
profileData.user.first_name = response.data.updated.first_name
profileData.user.last_name = response.data.updated.last_name
profileData.user.address = response.data.updated.address
profileData.user.country = response.data.updated.country
this.setProfile(profileData)
this.isEdit = false
this.isImageError = false
}
}).catch(response => {
if (profileImageId) {
imageUploader.DeleteImage(profileImageId)
}
toast.error(response.response.data.message)
})
} catch (error) {
toast.error(error);
}
}
this.setLoader(false)
},
async submitBranding() {
this.v$.branding.$touch()
if (!this.v$.branding.$error) {
let profileImageId = null
let logoImageId = null
let bannerImageId = null
try {
[profileImageId, logoImageId, bannerImageId] = await Promise.all([
this.brandingProfileImageFile ? this.imageUpload(this.brandingProfileImageFile) : null,
this.brandingLogoImageFile ? this.imageUpload(this.brandingLogoImageFile) : null,
this.brandingBannerImageFile ? this.imageUpload(this.brandingBannerImageFile) : null
]);
if (profileImageId) {
this.branding.photo = profileImageId;
}
if (logoImageId) {
this.branding.logo = logoImageId;
}
if (bannerImageId) {
this.branding.banner = bannerImageId;
}
Branding.updateBranding(this.branding).then(response => {
if (response.status === 200) {
toast.success(response.data.message)
let brandingData = this.branding
brandingData.name = response.data.updated.name
brandingData.email = response.data.updated.email
brandingData.photo = response.data.updated.photo
brandingData.logo = response.data.updated.logo
brandingData.banner = response.data.updated.banner
brandingData.link = response.data.updated.link
brandingData.description = response.data.updated.description
}
}).catch(response => {
if (logoImageId) {
imageUploader.DeleteImage(logoImageId)
}
if (bannerImageId) {
imageUploader.DeleteImage(bannerImageId)
}
toast.error(response.response.data.message)
})
} catch (error) {
toast.error(error);
}
}
this.setLoader(false)
},
handleSelectAgent() {
if (!this.agent._id) {
this.resetAgent()
} else {
this.agentImageFile = null;
this.agentImagePreview = null;
this.agent = { ...this.agents.find(agent => agent._id === this.agent._id) }
}
},
handleAgentImageUpload() {
const agentImage = this.$refs.agentImage;
if (agentImage.files.length > 0) {
const selectedFile = agentImage.files[0];
this.agentImageFile = selectedFile;
this.agentImagePreview = selectedFile;
const reader = new FileReader();
reader.onload = () => {
this.agentImagePreview = reader.result;
};
reader.readAsDataURL(selectedFile);
agentImage.value = '';
} else {
this.agentImageFile = null;
this.agentImagePreview = null;
}
},
handleClickAgentImageUpload() {
document.getElementById('upload-agent').click()
},
handleProfileImageUpload() {
const profileImage = this.$refs.profileImage;
if (profileImage.files.length > 0) {
const selectedFile = profileImage.files[0];
this.profileImageFile = selectedFile;
this.profileImagePreview = selectedFile;
const reader = new FileReader();
reader.onload = () => {
this.profileImagePreview = reader.result;
};
reader.readAsDataURL(selectedFile);
profileImage.value = '';
} else {
this.profileImageFile = null;
this.profileImagePreview = null;
}
},
handleClickBrandingProfileImageUpload() {
document.getElementById('upload-branding-profile').click()
},
handleBrandingProfileImageUpload() {
const profileImage = this.$refs.brandingProfileImage;
if (profileImage.files.length > 0) {
const selectedFile = profileImage.files[0];
this.brandingProfileImageFile = selectedFile;
this.brandingProfileImagePreview = selectedFile;
const reader = new FileReader();
reader.onload = () => {
this.brandingProfileImagePreview = reader.result;
};
reader.readAsDataURL(selectedFile);
profileImage.value = '';
} else {
this.brandingProfileImageFile = null;
this.brandingProfileImagePreview = null;
}
},
handleClickBrandingLogoImageUpload() {
document.getElementById('upload-logo').click()
},
handleBrandingLogoImageUpload() {
const logoImage = this.$refs.brandingLogoImage;
if (logoImage.files.length > 0) {
const selectedFile = logoImage.files[0];
this.openCropper(selectedFile, 'logo');
logoImage.value = '';
} else {
this.brandingLogoImageFile = null;
}
},
handleClickBrandingBannerImageUpload() {
document.getElementById('upload-banner').click()
},
handleBrandingBannerImageUpload() {
const bannerImage = this.$refs.brandingBannerImage;
if (bannerImage.files.length > 0) {
const selectedFile = bannerImage.files[0];
this.openCropper(selectedFile, 'banner');
bannerImage.value = '';
} else {
this.brandingBannerImageFile = null;
}
},
openCropper(file, type) {
this.cropperImageType = type;
this.cropperImageFile = file;
this.cropperImageUrl = URL.createObjectURL(file);
this.showCropperModal = true;
this.$nextTick(() => {
const image = this.$refs.cropperImage;
if (!image) {
console.error('Cropper target image element not found (this.$refs.cropperImage).');
this.closeCropper(); // Close modal if image ref is missing
return;
}
if (this.cropper && typeof this.cropper.destroy === 'function') {
this.cropper.destroy();
}
try {
this.cropper = new Cropper(image, {
aspectRatio: type === 'logo' ? 7 / 1 : 125 / 20,
viewMode: 1,
autoCrop: true,
responsive: true,
restore: false,
checkCrossOrigin: false,
ready: () => {
console.log('Cropper.js ready event fired.');
if (this.cropper && typeof this.cropper.getCropperCanvas === 'function') {
console.log('In Cropper ready: getCropperCanvas IS a function.');
} else {
console.error('In Cropper ready: getCropperCanvas IS NOT a function or cropper is null. this.cropper:', this.cropper);
}
}
});
if (this.cropper && typeof this.cropper.getCropperCanvas === 'function') {
console.log('Cropper instance created. getCropperCanvas method exists.');
} else {
console.error('Cropper instance created, but getCropperCanvas method DOES NOT exist or cropper is null. this.cropper:', this.cropper);
}
} catch (error) {
console.error('Error initializing Cropper.js:', error);
toast.error('Failed to initialize image cropper.');
this.closeCropper();
}
});
},
async cropImage() {
if (!this.cropper) {
console.error('Cropper not initialized (this.cropper is null or undefined).');
toast.error('Cropper is not ready.');
return;
}
const selection = this.cropper.getCropperSelection?.();
if (!selection || typeof selection.$toCanvas !== 'function') {
console.error('Cannot crop image: selection or $toCanvas is missing.');
toast.error('Cannot crop image: selection method is missing.');
return;
}
try {
const canvas = await selection.$toCanvas({
maxWidth: 4096,
maxHeight: 4096,
fillColor: '#fff',
imageSmoothingEnabled: true,
imageSmoothingQuality: 'high',
});
if (!canvas) {
console.error('Failed to get cropped canvas');
toast.error('Failed to crop image.');
return;
}
canvas.toBlob(blob => {
if (!blob) {
console.error('Failed to create blob from canvas');
toast.error('Failed to create image blob.');
return;
}
const croppedFile = new File([blob], this.cropperImageFile.name, {
type: this.cropperImageFile.type,
});
if (this.cropperImageType === 'logo') {
this.brandingLogoImageFile = croppedFile;
} else if (this.cropperImageType === 'banner') {
this.brandingBannerImageFile = croppedFile;
}
this.closeCropper();
console.log('Cropped image:', 'image');
}, this.cropperImageFile.type);
} catch (error) {
console.error('Error during crop operation:', error);
toast.error('Failed to crop image.');
}
},
closeCropper(){
if (this.cropper && typeof this.cropper.destroy === 'function') {
this.cropper.destroy();
}
this.cropper = null;
this.showCropperModal = false;
this.cropperImageUrl = null;
this.cropperImageFile = null;
this.cropperImageType = null;
},
async imageUpload(file) {
return new Promise((resolve, reject) => {
imageUploader.Upload(file)
.then(response => {
if (response.status === 200) {
resolve(response.data.document[0]);
} else {
reject('Image upload failed');
}
})
.catch((e) => {
reject('Something went wrong');
});
});
},
toggleDeleteModal(show = false) {
this.showDeleteModal = show;
}
}
}
</script>
<style>
.cam-icon {
display: none;
}
.photo-container:hover .cam-icon {
display: inline-block;
}
.text-danger {
padding-left: 0;
}
.bg-gray {
background-color: #f5f5f5;
}
.cropper-modal {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.7);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.cropper-modal-content {
background: #fff;
padding: 2rem;
border-radius: 8px;
max-width: 90vw;
max-height: 90vh;
overflow: auto;
text-align: center;
}
</style>