Using cropper-selection to limit boundaries in Vue3+Vite will render the width and height set by cropper-selection ineffective
Author: xxwangkaiminCreated Apr 20, 2026Updated Apr 20, 2026
Cropper version
2.1.1
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-7p1pknsl?file=src%2FApp.vue
Steps to reproduce
<script setup lang="ts">
import { ref } from "vue"
import "cropperjs"
import type { CropperCanvas, CropperImage, CropperSelection, CropperHandle, CropperGrid, CropperCrosshair, CropperShade } from 'cropperjs';
import type { Selection } from "@cropper/element-selection"
const fileInputRef = ref<HTMLInputElement | null>(null)
const cropperImageDom = new Image()
const ready = ref(false)
const cropperImageSrc = ref("")
const cropperCanvasRef = ref<InstanceType<typeof CropperCanvas>>()
const cropperImageRef = ref<InstanceType<typeof CropperImage>>()
const cropperSelectionRef = ref<InstanceType<typeof CropperSelection>>()
const handleSelectFile = () => {
if (fileInputRef.value) {
fileInputRef.value.value = ""
fileInputRef.value.click()
}
}
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement
const file: File = (target.files as FileList)[0]
if (!file) {
return
}
cropperImageSrc.value = URL.createObjectURL(file)
cropperImageDom.src = URL.createObjectURL(file)
ready.value = true
}
const inSelection = (selection: Selection, maxSelection: Selection) => {
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)
)
}
function onSelectionChange(event: CustomEvent) {
// Prevent the default behavior of the selection change event
// to avoid the cropper from applying the selection immediately.
// We want to apply our own constraints instead.
if (!cropperCanvasRef.value || !cropperImageRef.value || !cropperSelectionRef.value) {
return;
}
const selection = event.detail as Selection
const cropperCanvasRect = cropperCanvasRef.value.getBoundingClientRect();
const cropperImageRect = cropperImageRef.value.getBoundingClientRect();
const width = Math.min(selection.width, cropperImageRect.width);
const height = Math.min(selection.height, cropperImageRect.height);
const minX = cropperImageRect.x - cropperCanvasRect.x;
const minY = cropperImageRect.y - cropperCanvasRect.y;
const maxX = minX + cropperImageRect.width - width;
const maxY = minY + cropperImageRect.height - height;
const x = Math.max(minX, Math.min(selection.x, maxX));
const y = Math.max(minY, Math.min(selection.y, maxY));
cropperSelectionRef.value.x = x;
cropperSelectionRef.value.y = y;
cropperSelectionRef.value.width = width;
cropperSelectionRef.value.height = height;
cropperSelectionRef.value.$render();
const maxSelection: Selection = {
x: cropperImageRect.left - cropperCanvasRect.left,
y: cropperImageRect.top - cropperCanvasRect.top,
width: cropperImageRect.width,
height: cropperImageRect.height
}
if (!inSelection(selection, maxSelection)) {
event.preventDefault()
}
}
</script>
<template>
<input ref="fileInputRef" type="file" accept="image/*" style="display: none" @change="handleFileChange">
<button @click="handleSelectFile">选择图片</button>
<div class="cropper-container">
<cropper-canvas v-if="ready" ref="cropperCanvasRef">
<cropper-image
ref="cropperImageRef"
alt="Picture"
:src="cropperImageSrc"
rotatable
scalable
skewable
translatable
/>
<cropper-shade />
<cropper-handle action="select" plain />
<cropper-selection
ref="cropperSelectionRef"
initial-coverage="0.5"
movable
resizable
outlined
precise
width="160"
height="90"
@change="onSelectionChange"
>
<cropper-grid role="grid" covered />
<cropper-crosshair centered />
<cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)" />
<cropper-handle action="n-resize" />
<cropper-handle action="e-resize" />
<cropper-handle action="s-resize" />
<cropper-handle action="w-resize" />
<cropper-handle action="ne-resize" />
<cropper-handle action="nw-resize" />
<cropper-handle action="se-resize" />
<cropper-handle action="sw-resize" />
</cropper-selection>
</cropper-canvas>
</div>
</template>
<style lang="scss" scoped>
.cropper-container {
border: 1px solid var(--vp-c-divider);
border-radius: 0.375rem;
margin: 1rem 0;
padding: 1.25rem 1.5rem;
:deep(cropper-canvas) {
height: 400px;
}
}
</style>
What is expected?
Cropper-selection is displayed according to the configured width and height
What is actually happening?
Cropper-selection only displays a small portion in the middle
System Info
System:
OS: Windows 10 10.0.19045
CPU: (8) x64 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
Memory: 3.26 GB / 15.73 GB
Binaries:
Node: 22.15.0 - C:\development\nodejs\node.EXE
npm: 10.9.2 - C:\development\nodejs\npm.CMD
pnpm: 10.9.0 - C:\development\nodejs\pnpm.CMD
Browsers:
Chrome: 147.0.7727.102
Edge: Chromium (140.0.3485.66)
Internet Explorer: 11.0.19041.5794
npmPackages:
cropperjs: ^2.1.1 => 2.1.1Any additional comments?
No response
Source: fengyuanchen/cropperjs