fix(mobile): image/video aspect-ratio so media doesn't crop/letterbox (N2)
MImage/MVideo pinned AttachmentBox to a fixed height computed for a 400px-wide layout. The box width is responsive (maxWidth:100%) but the height was frozen, so on a phone the box narrows below 400px while keeping desktop height -> images crop (object-fit:cover) and videos letterbox (object-fit:contain). Drive the box by `aspect-ratio: w/h` when intrinsic dimensions are known, so the height tracks the responsive width. On desktop the box stays 400px wide, so the aspect-ratio yields the identical height (algebraically 400*h/w = scaleYDimension(w,400,h)) — pixel-identical. Falls back to the fixed height when dimensions are unknown; the 48px floor and 600px cap are preserved. Uses the same pattern already shipped in this codebase (TwitchThumbnailWrapper, GalleryTile). Two review passes, one empirically measuring the rendered image in Chromium: desktop unchanged, narrow widths keep correct aspect, no collapse. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -417,6 +417,22 @@ type RenderImageContentProps = {
|
|||||||
markedAsSpoiler?: boolean;
|
markedAsSpoiler?: boolean;
|
||||||
spoilerReason?: string;
|
spoilerReason?: string;
|
||||||
};
|
};
|
||||||
|
// Media frame sizing. When intrinsic width/height are known, drive the box by
|
||||||
|
// aspect-ratio so its height tracks the responsive (maxWidth:100%) width — a
|
||||||
|
// fixed pixel height computed for a 400px-wide layout otherwise crops (images,
|
||||||
|
// object-fit:cover) or letterboxes (videos, object-fit:contain) on phones where
|
||||||
|
// the box narrows below 400px. On desktop the box stays 400px wide, so the
|
||||||
|
// aspect-ratio yields the identical height. Falls back to the fixed height when
|
||||||
|
// dimensions are unknown.
|
||||||
|
const attachmentMediaStyle = (
|
||||||
|
w: number | undefined,
|
||||||
|
h: number | undefined,
|
||||||
|
fallbackHeight: number,
|
||||||
|
): CSSProperties =>
|
||||||
|
w && h
|
||||||
|
? { aspectRatio: `${w} / ${h}`, minHeight: toRem(48) }
|
||||||
|
: { height: toRem(fallbackHeight < 48 ? 48 : fallbackHeight) };
|
||||||
|
|
||||||
type MImageProps = {
|
type MImageProps = {
|
||||||
content: IImageContent;
|
content: IImageContent;
|
||||||
renderImageContent: (props: RenderImageContentProps) => ReactNode;
|
renderImageContent: (props: RenderImageContentProps) => ReactNode;
|
||||||
@@ -432,11 +448,7 @@ export function MImage({ content, renderImageContent, outlined }: MImageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Attachment outlined={outlined}>
|
<Attachment outlined={outlined}>
|
||||||
<AttachmentBox
|
<AttachmentBox style={attachmentMediaStyle(imgInfo?.w, imgInfo?.h, height)}>
|
||||||
style={{
|
|
||||||
height: toRem(height < 48 ? 48 : height),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{renderImageContent({
|
{renderImageContent({
|
||||||
body: content.body || 'Image',
|
body: content.body || 'Image',
|
||||||
info: imgInfo,
|
info: imgInfo,
|
||||||
@@ -498,11 +510,7 @@ export function MVideo({ content, renderAsFile, renderVideoContent, outlined }:
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</AttachmentHeader>
|
</AttachmentHeader>
|
||||||
<AttachmentBox
|
<AttachmentBox style={attachmentMediaStyle(videoInfo.w, videoInfo.h, height)}>
|
||||||
style={{
|
|
||||||
height: toRem(height < 48 ? 48 : height),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{renderVideoContent({
|
{renderVideoContent({
|
||||||
body: content.body || 'Video',
|
body: content.body || 'Video',
|
||||||
info: videoInfo,
|
info: videoInfo,
|
||||||
|
|||||||
Reference in New Issue
Block a user