Мне нужно реализовать функцию «Сжимать по размеру». В отличие от стандартной обрезки (которая заполняет область), функция «Сжать по размеру» должна обеспечивать видимость всего изображения внутри области печати без какой-либо обрезки. Если пропорции не совпадают, это должно привести к появлению «белых рамок» (наложение больше изображения в одном измерении).
Настройка:
- У меня есть тег , отображаемый на экране. Я могу получить его размеры с помощью getBoundingClientRect() (назовем это imgBox).
- У меня выбран размер печати (например, widthIn: 6, heightIn: 4).
- Мне нужно нарисовать (наложение) по центру изображения.
Требование:
- Наложение должно поддерживать соотношение сторон выбранного размера печати.
- Все изображение должно помещаться внутри наложения.
- Наложение должно быть центрировано относительно центра изображения точка.
Текущая логика (которая не может правильно вычислить логику «Содержать»):
import React, { useState, useEffect, useRef } from "react";
import { sizes } from "./sizes";
function PrintOrderScreen({ selectedPhotos = [], onBack, startIndex = 0, quantities, setQuantities, photoOverlays, setPhotoOverlays = () => { }, croppedImages = {}, setCroppedImages = () => { }, }) {
const [currentIndex, setCurrentIndex] = useState(0);
const [imageResolution, setImageResolution] = useState(null);
const imgRef = useRef(null);
const [imgBox, setImgBox] = useState({ width: 0, height: 0 });
const [showSelectedOnly, setShowSelectedOnly] = useState(false);
const allSizes = sizes.flatMap((cat) => cat.items);
const startResize = useRef(null);
const [activeCategory, setActiveCategory] = useState("");
const sizesContainerRef = useRef(null);
const categoryRefs = useRef({});
const categoryScrollRef = useRef(null);
const resizeState = useRef(null);
const PRINT_SCALE = 80; // px per inch
const getDpiFrame = (size) => {
if (!size) return { width: 0, height: 0 };
// 1 inch = 150 px at 150dpi (or 300 if you prefer)
const requiredW = size.widthIn * 150;
const requiredH = size.heightIn * 150;
return {
width: requiredW,
height: requiredH
};
};
const getOverlayForCurrent = () => {
const current = selectedPhotos[currentIndex];
if (!current) return null;
return photoOverlays[current.name]?.[current.selectedSize] || null;
};
const handleShrinkToFit = () => {
const printRatio = selectedSize.widthIn / selectedSize.heightIn;
const imageRatio = imgBox.width / imgBox.height;
let overlayW, overlayH;
// Need help here: How to calculate overlayW and overlayH
// so the image is fully contained within the print ratio?
if (printRatio > imageRatio) {
// ???
} else {
// ???
}
// Then center it
const left = imgBox.left + (imgBox.width - overlayW) / 2;
const top = imgBox.top + (imgBox.height - overlayH) / 2;
};
const ensureOverlayForSize = (size) => {
const current = selectedPhotos[currentIndex];
if (!current) return;
const sizeLabel = size.label;
if (photoOverlays[current.name]?.[sizeLabel]) return;
const box = getRenderedImageBox();
if (!box.width || !box.height) return;
const frame = getCenteredOverlayFrame(size, box);
setPhotoOverlays(prev => ({
...prev,
[current.name]: {
...prev[current.name],
[sizeLabel]: {
fitMode: "crop",
rotation: 0,
scale: 1,
frame,
initialFrame: frame,
},
},
}));
};
const handleSelectSize = (size) => {
const current = selectedPhotos[currentIndex];
if (!current) return;
current.selectedSize = size.label;
const existing = photoOverlays[current.name]?.[size.label];
if (existing) {
updateOverlayForCurrent(existing);
return;
}
const box = getRenderedImageBox();
if (!box.width || !box.height) return;
const frame = getCenteredOverlayFrame(size, box);
updateOverlayForCurrent({
fitMode: "crop",
rotation: 0,
scale: 1,
frame,
initialFrame: frame,
});
};
const getPrintFrameFromSize = (size) => {
return {
width: size.widthIn * PRINT_SCALE,
height: size.heightIn * PRINT_SCALE,
};
};
const getCenteredOverlayFrame = (size, imageBox) => {
const printFrame = getPrintFrameFromSize(size);
const left =
imageBox.left + (imageBox.width - printFrame.width) / 2;
const top =
imageBox.top + (imageBox.height - printFrame.height) / 2;
return {
width: printFrame.width,
height: printFrame.height,
left,
top,
};
};
const hasSelectedSize =
Object.values(quantities[currentPhoto.name] || {}).some(q => q > 0);
return (
{/* LEFT SIDE */}
{currentIndex + 1} of {selectedPhotos.length} photos
{currentPhoto.name}
Done
{/*
{selectedPhotos.length > 1 && (
)}
{/*
ref={imgRef}
key={currentPhoto.name}
src={currentPhoto.url}
alt={currentPhoto.name}
className="print-preview"
style={{
objectFit: "contain",
objectPosition: "center",
width: "70%",
height: "auto",
display: "block",
filter: getFilterStyle(currentPhoto.filterApplied),
}}
draggable={false}
/>
{/*
{currentOverlay?.frame &&
Object.values(quantities[currentPhoto.name] || {}).some(
(qty) => qty > 0
) && (
{/* Resize Handle */}
handleResizeStart(e)}
>
)}
{/* ⚠ Low Resolution Warning */}
{allSizes.some(
(s) =>
(quantities[currentPhoto.name]?.[s.label] || 0) > 0 &&
!checkSizeFit(s)
) && ⚠ Low Resolution}
{/*
{selectedPhotos.length > 1 && (
)}
Center Shrink to Fit
Rotate Reset
{/* RIGHT SIDE PANEL */}
{/* HEADER */}
{showSelectedOnly ? (
className="fa-solid fa-arrow-left"
style={{ cursor: "pointer", fontSize: "16px", color: "#333" }}
title="Show all sizes"
onClick={handleToggleallSizes}
>
Selected Print Sizes
) : (
"Select Print Sizes"
)}
{Object.values(quantities[currentPhoto.name] || {}).some((q) => q > 0) && (
{showSelectedOnly ? "Show All Sizes" : "Show Selected Sizes"}
)}
{/* HORIZONTAL CATEGORY BAR */}
{!showSelectedOnly && (
{
categoryScrollRef.current.scrollBy({ left: -150, behavior: "smooth" });
}}
>
{sizes.map((cat) => (
{
const el = categoryRefs.current[cat.category];
if (el) {
el.scrollIntoView({ behavior: "smooth", block: "start" });
}
}}
>
{cat.category}
))}
{
categoryScrollRef.current.scrollBy({ left: 150, behavior: "smooth" });
}}
>
)}
{/* ───────────────────────────────────
BUILD CATEGORY LIST SAFELY
─────────────────────────────────── */}
{(() => {
// 1. full category structure from sizes.js
const allCategories = sizes;
// 2. build filtered structure safely
const structured = showSelectedOnly
? allCategories
.map((cat) => ({
...cat,
items: (cat.items || []).filter(
(s) => (quantities[currentPhoto.name]?.[s.label] || 0) > 0
),
}))
.filter((cat) => cat.items.length > 0)
: allCategories;
// 3. handle empty case
if (!structured.length) {
return (
No sizes selected yet.
);
}
// 4. final safe mapping
return structured.map((cat) => (
className="size-category-block"
data-category={cat.category}
ref={(el) => (categoryRefs.current[cat.category] = el)}>
{/* CATEGORY BUTTON / HEADER */}
{cat.category}
{/* CATEGORY ITEMS */}{(cat.items || []).map((s) => {
const qty = quantities[currentPhoto.name]?.[s.label] || 0;
const ok = checkSizeFit(s);
return (
handleSelectSize(s)}
style={{
backgroundColor: qty > 0 ? "#2962FF" : "transparent",
color: qty > 0 ? "white" : "inherit",
borderRadius: "6px",
padding: "8px 12px",
marginBottom: "6px",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
transition: "0.2s ease",
}}
>
{s.label} – ${s.price.toFixed(2)} each{" "}
{!ok && (
className="fa-solid fa-triangle-exclamation"
style={{
color: "yellow",
textShadow: "0 0 2px black",
marginLeft: "8px",
}}
>
)}
0 ? "active" : ""}
onClick={(e) => {
e.stopPropagation();
updateQuantity(s.label, -1);
}}
>
-
{qty}
0 ? "active" : ""}
onClick={(e) => {
e.stopPropagation();
updateQuantity(s.label, 1);
}}
>
+
);
})}
));
})()}
);
}
export default PrintOrderScreen;
Подробнее здесь: https://stackoverflow.com/questions/798 ... -for-an-im
Мобильная версия