Skip to content

Commit c0e0f4d

Browse files
committed
Set aspect ratio of containers to prevent CLS
1 parent eb1af88 commit c0e0f4d

File tree

3 files changed

+47
-8
lines changed

3 files changed

+47
-8
lines changed

dotcom-rendering/src/components/Card/Card.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,7 @@ export const Card = ({
10011001
width={media.mainMedia.width}
10021002
videoStyle={media.mainMedia.videoStyle}
10031003
posterImage={media.mainMedia.image ?? ''}
1004+
containerAspectRatio={5 / 4}
10041005
fallbackImage={media.mainMedia.image ?? ''}
10051006
fallbackImageSize={mediaSize}
10061007
fallbackImageLoading={imageLoading}

dotcom-rendering/src/components/SelfHostedVideo.importable.tsx

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,43 @@ import type {
3030
import { SelfHostedVideoPlayer } from './SelfHostedVideoPlayer';
3131
import { ophanTrackerWeb } from './YoutubeAtom/eventEmitters';
3232

33-
const videoAndBackgroundStyles = (isCinemagraph: boolean) => css`
33+
const videoContainerStyles = (
34+
isCinemagraph: boolean,
35+
videoAspectRatio: number,
36+
aspectRatio?: number, // The aspect ratio of the container
37+
) => css`
3438
position: relative;
3539
display: flex;
3640
justify-content: space-around;
3741
background-color: ${palette('--video-background')};
3842
${!isCinemagraph && `z-index: ${getZIndex('video-container')}`};
43+
44+
/**
45+
* If the video and its containing slot have different dimensions, the slot will use the aspect
46+
* ratio of the video on mobile, so that the video can take up the full width of the screen.
47+
*
48+
* From tablet breakpoints, the aspect ratio of the slot is maintained, for consistency with other content.
49+
* This will result in grey bars on either side of the video if the video is narrower than the slot.
50+
*/
51+
aspect-ratio: ${videoAspectRatio};
52+
${from.tablet} {
53+
${typeof aspectRatio === 'number' && `aspect-ratio: ${aspectRatio};`}
54+
}
3955
`;
4056

41-
const videoContainerStyles = (width: number, height: number) => css`
57+
const figureStyles = (aspectRatio: number) => css`
4258
position: relative;
59+
aspect-ratio: ${aspectRatio};
4360
height: 100%;
4461
max-height: 100vh;
4562
max-height: 100svh;
4663
max-width: 100%;
4764
${from.tablet} {
48-
max-width: ${(width / height) * 80}%;
65+
/**
66+
* The value "80" is derived from the aspect ratio of the 5:4 slot.
67+
* When other slots are used for self-hosted videos, this will need to be adjusted.
68+
*/
69+
max-width: ${aspectRatio * 80}%;
4970
}
5071
`;
5172

@@ -113,6 +134,12 @@ type Props = {
113134
width: number;
114135
videoStyle: VideoPlayerFormat;
115136
posterImage: string;
137+
/**
138+
* The desired aspect ratio of the container of the video, which can differ from the
139+
* aspect ratio of the video itself. Only applied from the tablet breakpoint, as below this
140+
* breakpoint, the video always takes up the full width of the screen.
141+
*/
142+
containerAspectRatio?: number;
116143
fallbackImage: CardPictureProps['mainImage'];
117144
fallbackImageSize: CardPictureProps['imageSize'];
118145
fallbackImageLoading: CardPictureProps['loading'];
@@ -132,6 +159,7 @@ export const SelfHostedVideo = ({
132159
width: expectedWidth,
133160
videoStyle,
134161
posterImage,
162+
containerAspectRatio,
135163
fallbackImage,
136164
fallbackImageSize,
137165
fallbackImageLoading,
@@ -656,17 +684,25 @@ export const SelfHostedVideo = ({
656684
}
657685
};
658686

687+
const aspectRatio = width / height;
688+
659689
const AudioIcon = isMuted ? SvgAudioMute : SvgAudio;
660690

661691
const optimisedPosterImage = showPosterImage
662692
? getOptimisedPosterImage(posterImage)
663693
: undefined;
664694

665695
return (
666-
<div css={videoAndBackgroundStyles(isCinemagraph)}>
696+
<div
697+
css={videoContainerStyles(
698+
isCinemagraph,
699+
aspectRatio,
700+
containerAspectRatio,
701+
)}
702+
>
667703
<figure
668704
ref={setNode}
669-
css={videoContainerStyles(width, height)}
705+
css={figureStyles(aspectRatio)}
670706
className={`video-container ${videoStyle.toLocaleLowerCase()}`}
671707
data-component="gu-video-loop"
672708
>

dotcom-rendering/src/components/SelfHostedVideoPlayer.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { VideoProgressBar } from './VideoProgressBar';
2323

2424
export type SubtitleSize = 'small' | 'medium' | 'large';
2525

26-
const videoStyles = (width: number, height: number) => css`
26+
const videoStyles = (aspectRatio: number) => css`
2727
position: relative;
2828
display: block;
2929
height: auto;
@@ -32,7 +32,7 @@ const videoStyles = (width: number, height: number) => css`
3232
max-height: 100svh;
3333
cursor: pointer;
3434
/* Prevents CLS by letting the browser know the space the video will take up. */
35-
aspect-ratio: ${width} / ${height};
35+
aspect-ratio: ${aspectRatio};
3636
`;
3737

3838
const subtitleStyles = (subtitleSize: SubtitleSize | undefined) => css`
@@ -191,13 +191,15 @@ export const SelfHostedVideoPlayer = forwardRef(
191191
? sources
192192
: filterOutHlsSources(sources);
193193

194+
const aspectRatio = width / height;
195+
194196
return (
195197
<>
196198
{/* eslint-disable-next-line jsx-a11y/media-has-caption -- Not all videos require captions. */}
197199
<video
198200
id={videoId}
199201
css={[
200-
videoStyles(width, height),
202+
videoStyles(aspectRatio),
201203
showSubtitles && subtitleStyles(subtitleSize),
202204
]}
203205
crossOrigin="anonymous"

0 commit comments

Comments
 (0)