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.
This commit is contained in:
Paul Nothaft
2026-01-29 21:40:41 +01:00
parent 608bbd50e7
commit 8711f967a1
@@ -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<MasonryPhotoProps> = ({
@@ -48,19 +50,28 @@ const MasonryPhoto: React.FC<MasonryPhotoProps> = ({
feedbackEnabled = false,
slug,
feedbackOptions,
onQuickComment
onQuickComment,
columnWidth = 300
}) => {
const [imageHeight, setImageHeight] = useState<number>(200);
const [showIdentityModal, setShowIdentityModal] = useState(false);
const [pendingAction, setPendingAction] = useState<null | { type: 'like'; photoId: number }>(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 (
<div
@@ -350,6 +361,14 @@ export const MasonryGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
});
}
// 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<BaseGalleryLayoutProps> = ({
slug={slug}
feedbackOptions={feedbackOptions}
onQuickComment={() => onOpenPhotoWithFeedback && onOpenPhotoWithFeedback(originalIndex)}
columnWidth={columnWidth}
/>
);
})}