Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/css/cp.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import './vendors/codemirror.css';
@import './vendors/cropper.css';

@import './core/animation.css';
@import './core/layout.css';
Expand Down
39 changes: 39 additions & 0 deletions resources/css/vendors/cropper.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* CROPPER
=================================================== */
/* Where the crop box touches the edge of the image, draw its border inside it so it isn't clipped, and make the handles more obvious. */
.crop-editor-canvas {
.cropper-crop-box::before {
content: '';
position: absolute;
inset: 0;
border: 0 solid #39f;
pointer-events: none;
}

&.at-top .cropper-crop-box::before { border-top-width: 2px; }
&.at-right .cropper-crop-box::before { border-right-width: 2px; }
&.at-bottom .cropper-crop-box::before { border-bottom-width: 2px; }
&.at-left .cropper-crop-box::before { border-left-width: 2px; }

&.at-top :is(.point-n, .point-ne, .point-nw),
&.at-right :is(.point-e, .point-ne, .point-se),
&.at-bottom :is(.point-s, .point-se, .point-sw),
&.at-left :is(.point-w, .point-nw, .point-sw) {
min-width: 6px;
min-height: 6px;
background-color: #39f;
box-shadow: 0 0 0 1px #fff;
opacity: 1;
}

/* Give the smaller handles _exactly_ the same drag area as the enlarged ones for consistent usability. */
.cropper-point::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 6px;
translate: -50% -50%;
}
}
31 changes: 29 additions & 2 deletions resources/js/components/assets/Editor/CropEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const showConfirmation = ref(false);
const uploading = ref(false);
const pendingCrop = ref(null);
const cropDimensions = ref(null);
const cropEdges = ref({});
const defaultQuality = Statamic.$config.get('cropQuality') || 90;

function normalizeFormat(format) {
Expand Down Expand Up @@ -123,6 +124,7 @@ function resetState() {
uploading.value = false;
pendingCrop.value = null;
cropDimensions.value = null;
cropEdges.value = {};
quality.value = initialQuality();
format.value = sourceFormat.value;
background.value = 'white';
Expand Down Expand Up @@ -160,7 +162,7 @@ function createCropper(imageElement) {
aspectRatio: NaN,
viewMode: 1,
dragMode: 'crop',
autoCropArea: 0.9,
autoCropArea: 1,
restore: false,
guides: true,
center: true,
Expand All @@ -177,6 +179,7 @@ function createCropper(imageElement) {
cropstart: onCropStart,
cropmove: onCropMove,
cropend: onCropEnd,
ready: updateCropEdges,
crop: onCrop,
});
}
Expand All @@ -186,6 +189,30 @@ function onCrop(event) {
width: Math.round(event.detail.width),
height: Math.round(event.detail.height),
};

updateCropEdges();
}

// Track which edges of the visible image the crop box touches, so the controls on those
// edges can be drawn inside the crop box instead of being clipped.
function updateCropEdges() {
if (!cropper.value) return;

const container = cropper.value.getContainerData();
const canvas = cropper.value.getCanvasData();
const box = cropper.value.getCropBoxData();

const top = Math.max(canvas.top, 0);
const left = Math.max(canvas.left, 0);
const right = Math.min(canvas.left + canvas.width, container.width);
const bottom = Math.min(canvas.top + canvas.height, container.height);

cropEdges.value = {
'at-top': box.top - top < 1,
'at-left': box.left - left < 1,
'at-right': right - (box.left + box.width) < 1,
'at-bottom': bottom - (box.top + box.height) < 1,
};
}

function onCropStart() {
Expand Down Expand Up @@ -480,7 +507,7 @@ function close() {

<!-- Content -->
<div class="bg-gray-300 p-3 inset-shadow-xs dark:bg-gray-850 flex flex-1 flex-col overflow-auto relative min-h-0 w-full items-center justify-center" role="img" :aria-label="__('Image crop area')">
<div class="h-full w-full min-h-0 flex items-center justify-center overflow-hidden">
<div :class="cropEdges" class="crop-editor-canvas h-full w-full min-h-0 flex items-center justify-center">
<img ref="image" :src="asset.preview" :crossorigin="crossOrigin" :alt="__('Image to crop')" class="max-w-full max-h-full" @error="onImageError" />
</div>
<div
Expand Down
Loading