From 8711f967a15f5d57f6ad01bfdbd8d33f9ee96abc Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Thu, 29 Jan 2026 21:40:41 +0100 Subject: [PATCH] fix: use actual photo aspect ratios in masonry columns mode (#146) Previously, the Pinterest-style columns mode assigned random heights to photos, causing landscape images to be cropped into portrait slots. Now the height is calculated based on the photo's actual aspect ratio and the column width, preserving natural proportions. --- .../gallery/layouts/MasonryGalleryLayout.tsx | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/gallery/layouts/MasonryGalleryLayout.tsx b/frontend/src/components/gallery/layouts/MasonryGalleryLayout.tsx index 8154f4ea..3d9043f5 100644 --- a/frontend/src/components/gallery/layouts/MasonryGalleryLayout.tsx +++ b/frontend/src/components/gallery/layouts/MasonryGalleryLayout.tsx @@ -34,6 +34,8 @@ interface MasonryPhotoProps { requireNameEmail?: boolean; }; onQuickComment?: () => void; + // Column width for calculating proper aspect-ratio-based height + columnWidth?: number; } const MasonryPhoto: React.FC = ({ @@ -48,19 +50,28 @@ const MasonryPhoto: React.FC = ({ feedbackEnabled = false, slug, feedbackOptions, - onQuickComment + onQuickComment, + columnWidth = 300 }) => { - const [imageHeight, setImageHeight] = useState(200); const [showIdentityModal, setShowIdentityModal] = useState(false); const [pendingAction, setPendingAction] = useState(null); const [savedIdentity, setSavedIdentity] = useState<{ name: string; email: string } | null>(null); - // Generate random heights for masonry effect - useEffect(() => { - const heights = [200, 250, 300, 350, 400]; - const randomHeight = heights[Math.floor(Math.random() * heights.length)]; - setImageHeight(randomHeight); - }, [photo.id]); + // Calculate height based on actual photo aspect ratio + // This preserves the photo's natural proportions in the masonry layout + const imageHeight = useMemo(() => { + const photoWidth = photo.width || 800; + const photoHeight = photo.height || 600; + const aspectRatio = photoWidth / photoHeight; + + // Calculate height based on column width and aspect ratio + // Clamp to reasonable min/max heights for visual consistency + const calculatedHeight = columnWidth / aspectRatio; + const minHeight = 150; + const maxHeight = 500; + + return Math.max(minHeight, Math.min(maxHeight, calculatedHeight)); + }, [photo.width, photo.height, columnWidth]); return (
= ({ }); } + // Calculate approximate column width for aspect ratio calculations + const columnWidth = useMemo(() => { + if (containerWidth <= 0 || columns <= 0) return 300; + // Account for gaps between columns + const totalGaps = (columns - 1) * gutter; + return (containerWidth - totalGaps) / columns; + }, [containerWidth, columns, gutter]); + // ROWS MODE - Google Photos style justified layout if (mode === 'rows') { // Show loading state while measuring container width @@ -744,6 +763,7 @@ export const MasonryGalleryLayout: React.FC = ({ slug={slug} feedbackOptions={feedbackOptions} onQuickComment={() => onOpenPhotoWithFeedback && onOpenPhotoWithFeedback(originalIndex)} + columnWidth={columnWidth} /> ); })}