upgrade to new cropperjs 2.0 API

This commit is contained in:
Henry Jameson 2025-03-30 17:06:19 +03:00
commit eda51ae486
4 changed files with 86 additions and 45 deletions

View file

@ -1,5 +1,4 @@
import Cropper from 'cropperjs' import 'cropperjs' // This adds all of the cropperjs's components into DOM
import 'cropperjs/dist/cropper.css'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faCircleNotch faCircleNotch
@ -19,19 +18,6 @@ const ImageCropper = {
type: Function, type: Function,
required: true required: true
}, },
cropperOptions: {
type: Object,
default () {
return {
aspectRatio: 1,
autoCropArea: 1,
viewMode: 1,
movable: false,
zoomable: false,
guides: false
}
}
},
mimes: { mimes: {
type: String, type: String,
default: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon' default: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon'
@ -48,7 +34,6 @@ const ImageCropper = {
}, },
data () { data () {
return { return {
cropper: undefined,
dataUrl: undefined, dataUrl: undefined,
filename: undefined, filename: undefined,
submitting: false submitting: false
@ -67,27 +52,30 @@ const ImageCropper = {
}, },
methods: { methods: {
destroy () { destroy () {
if (this.cropper) {
this.cropper.destroy()
}
this.$refs.input.value = '' this.$refs.input.value = ''
this.dataUrl = undefined this.dataUrl = undefined
this.$emit('close') this.$emit('close')
}, },
submit (cropping = true) { submit (cropping = true) {
this.submitting = true this.submitting = true
this.submitHandler(cropping && this.cropper, this.file)
.then(() => this.destroy()) let cropperPromise
.finally(() => { if (cropping) {
this.submitting = false cropperPromise = this.$refs.cropperSelection.$toCanvas()
}) } else {
cropperPromise = Promise.resolve()
}
cropperPromise.then(canvas => {
this.submitHandler(canvas, this.file)
.then(() => this.destroy())
.finally(() => {
this.submitting = false
})
})
}, },
pickImage () { pickImage () {
this.$refs.input.click() this.$refs.input.click()
}, },
createCropper () {
this.cropper = new Cropper(this.$refs.img, this.cropperOptions)
},
getTriggerDOM () { getTriggerDOM () {
return typeof this.trigger === 'object' ? this.trigger : document.querySelector(this.trigger) return typeof this.trigger === 'object' ? this.trigger : document.querySelector(this.trigger)
}, },
@ -103,6 +91,29 @@ const ImageCropper = {
reader.readAsDataURL(this.file) reader.readAsDataURL(this.file)
this.$emit('changed', this.file, reader) this.$emit('changed', this.file, reader)
} }
},
inSelection(selection, maxSelection) {
return (
selection.x >= maxSelection.x
&& selection.y >= maxSelection.y
&& (selection.x + selection.width) <= (maxSelection.x + maxSelection.width)
&& (selection.y + selection.height) <= (maxSelection.y + maxSelection.height)
)
},
onCropperSelectionChange(event) {
const cropperCanvas = this.$refs.cropperCanvas
const cropperCanvasRect = cropperCanvas.getBoundingClientRect()
const selection = event.detail
const maxSelection = {
x: 0,
y: 0,
width: cropperCanvasRect.width,
height: cropperCanvasRect.height,
}
if (!this.inSelection(selection, maxSelection)) {
event.preventDefault();
}
} }
}, },
mounted () { mounted () {

View file

@ -1,14 +1,43 @@
<template> <template>
<div class="image-cropper"> <div class="image-cropper">
<div v-if="dataUrl"> <div v-if="dataUrl">
<div class="image-cropper-image-container"> <cropper-canvas
<img background
ref="img" class="image-cropper-canvas"
ref="cropperCanvas"
height="25em"
>
<cropper-image
:src="dataUrl" :src="dataUrl"
alt="" alt="Picture"
@load.stop="createCropper" ref="cropperImage"
class="image-cropper-image"
translatable
scalable
/>
<cropper-shade hidden />
<cropper-handle action="select" plain />
<cropper-selection
ref="cropperSelection"
initial-coverage="1"
aspect-ratio="1"
movable
resizable
@change="onCropperSelectionChange"
> >
</div> <cropper-grid role="grid" covered></cropper-grid>
<cropper-crosshair centered></cropper-crosshair>
<cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
<cropper-handle action="n-resize"></cropper-handle>
<cropper-handle action="e-resize"></cropper-handle>
<cropper-handle action="s-resize"></cropper-handle>
<cropper-handle action="w-resize"></cropper-handle>
<cropper-handle action="ne-resize"></cropper-handle>
<cropper-handle action="nw-resize"></cropper-handle>
<cropper-handle action="se-resize"></cropper-handle>
<cropper-handle action="sw-resize"></cropper-handle>
</cropper-selection>
</cropper-canvas>
<div class="image-cropper-buttons-wrapper"> <div class="image-cropper-buttons-wrapper">
<button <button
class="button-default btn" class="button-default btn"
@ -55,20 +84,18 @@
display: none; display: none;
} }
&-image-container { &-canvas {
position: relative; height: 25em;
width: 25em;
img {
display: block;
max-width: 100%;
}
} }
&-buttons-wrapper { &-buttons-wrapper {
margin-top: 10px; display: grid;
grid-gap: 0.5em;
grid-template-columns: 1fr 1fr 1fr;
button { button {
margin-top: 5px; margin-top: 1em;
} }
} }
} }

View file

@ -216,7 +216,7 @@ const ProfileTab = {
this.submitBackground('') this.submitBackground('')
} }
}, },
submitAvatar (cropper, file) { submitAvatar (canvas, file) {
const that = this const that = this
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
function updateAvatar (avatar, avatarName) { function updateAvatar (avatar, avatarName) {
@ -232,8 +232,8 @@ const ProfileTab = {
}) })
} }
if (cropper) { if (canvas) {
cropper.getCroppedCanvas().toBlob((data) => updateAvatar(data, file.name), file.type) canvas.toBlob((data) => updateAvatar(data, file.name), file.type)
} else { } else {
updateAvatar(file, file.name) updateAvatar(file, file.name)
} }

View file

@ -97,6 +97,9 @@ export default defineConfig(async ({ mode, command }) => {
if (tag === 'pinch-zoom') { if (tag === 'pinch-zoom') {
return true return true
} }
if (tag.startsWith('cropper-')) {
return true
}
return false return false
} }
} }