Using cropper with a fixed aspect ratio and a specific display size of 150x200. Once an image is drawn to the canvas, the canvas (or rather its container) seems to expand to an even square or a 1:1 ratio. In my case, it becomes 200x200. There's an added space in one dimension that I do not want. (see the red areas in the pic link). The cropper box also doesn't appear to stick to the aspect ratio set. In the first example, the max crop box is 133x200, when it should be 150x200. In the second example its 105x158 which does match the the 2:3 ratio.
I've tried setting dimensions on .cropper-container and .cropper-bg, but the image looks like its drawn with an offset on the canvas to center it within those square dimensions. So setting those containers really just ruins the UI, as the image becomes cropped yet the crop tool itself can expand beyond what you see. Is there a setting to control this?
$(function() {
var canvas = $("#canvas"),
context = canvas.get(0).getContext("2d"),
$result = $('#result');
$('#fileInput').on( 'change', function(){
if (this.files && this.files[0]) {
if ( this.files[0].type.match(/^image\//) ) {
var reader = new FileReader();
reader.onload = function(evt) {
var img = new Image();
img.onload = function() {
context.canvas.height = img.height;
context.canvas.width = img.width;
context.drawImage(img, 0, 0);
var cropper = canvas.cropper({
aspectRatio: 2/3,
viewMode: 2,
dragMode: 'move',
autoCropArea:1,
responsive: true,
});
$('#btnCrop').click(function() {
// Get a string base 64 data url
var croppedImageDataURL = canvas.cropper('getCroppedCanvas').toDataURL("image/png");
$result.append( $('<img>').attr('src', croppedImageDataURL) );
});
$('#btnRestore').click(function() {
canvas.cropper('reset');
$result.empty();
});
};
img.src = evt.target.result;
};
reader.readAsDataURL(this.files[0]);
}
else {
alert("Invalid file type! Please select an image file.");
}
}
else {
alert('No file(s) selected.');
}
});
});
img { max-width: 100%; }
#canvas {
height: 200px;
width: 150px;
cursor: default;
}
.cropper-bg{background-image:none !important;background-color:red;}
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropper/4.1.0/cropper.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropper/4.1.0/cropper.css" />
</head>
<body>
<p>
<!-- Below are a series of inputs which allow file selection and interaction with the cropper api -->
<input type="file" id="fileInput" accept="image/*" />
<input type="button" id="btnCrop" value="Crop" />
<input type="button" id="btnRestore" value="Restore" />
</p>
<div style="width:150px;height:200px;background:green;">
<canvas id="canvas">Your browser does not support the HTML5 canvas element.</canvas>
</div>
<div id="result"></div>
</body>
</html>
This happens because Cropper.Js calculates container dimensions from the parent element's computed size and the canvas scales to fit within that space, creating extra space when image and container aspect ratio differ.
Documentation directly says:
The size of the cropper inherits from the size of the image's parent element (wrapper), so be sure to wrap the image with a visible block element
When you set the canvas dimentions dynamically to match uploaded image dimensions, the croppers container does not use those canvas dimensions directly. Instead it reads the computed CSS dimensions of the parent wrapper at the initialization time.
First Container .cropper-container inherits from parent wrapper's computed dimensions.
Then Canvas cropper-canvas scales to fit within container based on viewMode
FInally Crop Box .cropper-crop-box contained by aspectRatio option.
So the 200 x 200 result likely occurs because either the parent element has equal width/height computed values, or the scaling calculation creates a square bounding box. The default container dimensions are 200 x 100. So may be seeing 200 as the width is expected behavior when no explicit smaller dimension is set.
The viewMode settings fundamentally changes how the canvas relates to the container.
With viewMode:2 and a non-square image in a 150 x 200 container, the canvas calculates it size to fit entirely within that space while maintaining the image's native aspect ratio. If you upload 300 x 200 landscape image (3:2 ratio), the canvas will scale to fit the 150px width, resulting in a canvas height of only 100px leaving 100px of extra space vertically. The container may then expand or display this as empty space.
For a fixed 150 x 200 container where you want the canvas to fill the entire space, use viewMode: 3. However this means part of the source image may not be visible.
The aspectRatio only controls the crop box shape. Not the container dimensions
Define the fixed aspect ratio of the crop box.
The correct approach combines CSS on the parent wrapper with JavaScript options that override defaults:
<div class="cropper-wrapper">
<img id="crop-image" src="uploaded-image.jpg">
</div>
and CSS
.cropper-wrapper {
width: 150px;
height: 200px;
overflow: hidden; /* Critical for clipping any overflow */
}
.cropper-wrapper img {
display: block; /* Required by Cropper.js */
max-width: 100%; /* Documented as "very important" */
}
$('#crop-image').cropper({
viewMode: 3, // Fill container completely
aspectRatio: 2/3, // Crop box aspect ratio (150/200 = 0.75)
minContainerWidth: 150, // Override default 200
minContainerHeight: 200, // Override default 100
autoCropArea: 1,
dragMode: 'move',
cropBoxMovable: false,
cropBoxResizable: false
});
Correct implementation would be:
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.css" />
<style>
/* The wrapper element controls the cropper's container size.
Cropper.js inherits dimensions from this parent, not from the canvas. */
.cropper-wrapper {
width: 150px;
height: 200px;
overflow: hidden; /* Clips any visual overflow from canvas scaling */
}
/* Required by Cropper.js documentation */
.cropper-wrapper img {
display: block;
max-width: 100%;
}
#result img {
max-width: 100%;
margin-top: 10px;
}
</style>
</head>
<body>
<p>
<input type="file" id="fileInput" accept="image/*" />
<input type="button" id="btnCrop" value="Crop" />
<input type="button" id="btnRestore" value="Restore" />
</p>
<!-- The wrapper div is what Cropper.js reads for container dimensions -->
<div class="cropper-wrapper">
<img id="cropImage" src="" alt="Upload an image">
</div>
<div id="result"></div>
<script>
$(function() {
var $image = $('#cropImage');
var $result = $('#result');
var cropper = null;
$('#fileInput').on('change', function() {
if (!this.files || !this.files[0]) {
alert('No file selected.');
return;
}
if (!this.files[0].type.match(/^image\//)) {
alert('Invalid file type. Please select an image.');
return;
}
var reader = new FileReader();
reader.onload = function(evt) {
// Destroy any existing cropper instance before reinitializing.
// This prevents memory leaks and stale state.
if (cropper) {
cropper.destroy();
cropper = null;
}
// Set the image source. The image element retains its natural dimensions
// internally, but the cropper will scale it to fit the container.
$image.attr('src', evt.target.result);
// Initialize cropper after image loads to ensure dimensions are available.
$image.one('load', function() {
cropper = new Cropper(this, {
// viewMode: 3 forces the canvas to FILL the container completely.
// This eliminates letterboxing but may hide parts of the source image.
// Use viewMode: 2 if you need to see the entire image (letterboxing acceptable).
viewMode: 3,
// aspectRatio controls the crop box shape, matching your 150:200 = 2:3 ratio.
aspectRatio: 2 / 3,
// Override the defaults (200x100) to match your desired container size.
// These are minimums, but combined with the CSS wrapper they enforce exact dimensions.
minContainerWidth: 150,
minContainerHeight: 200,
autoCropArea: 1,
dragMode: 'move',
responsive: true,
ready: function() {
// Verify container dimensions match expectations.
var containerData = cropper.getContainerData();
console.log('Container dimensions:', containerData);
// Should log {width: 150, height: 200}
}
});
});
};
reader.readAsDataURL(this.files[0]);
});
$('#btnCrop').on('click', function() {
if (!cropper) {
alert('Please upload an image first.');
return;
}
// getCroppedCanvas returns the cropped region at its natural resolution.
// Optionally pass dimensions to scale the output: { width: 150, height: 200 }
var croppedCanvas = cropper.getCroppedCanvas();
var croppedImageDataURL = croppedCanvas.toDataURL('image/png');
$result.empty().append($('<img>').attr('src', croppedImageDataURL));
});
$('#btnRestore').on('click', function() {
if (cropper) {
cropper.reset();
}
$result.empty();
});
});
</script>
</body>
</html>