Skip to content

Commit 3e72807

Browse files
authored
Merge pull request #13843 from guardian/doml/loop-video-prefers-reduced-motion
Don't autoplay if user prefers reduced motion
2 parents 88db1a5 + 5324f24 commit 3e72807

File tree

3 files changed

+38
-22
lines changed

3 files changed

+38
-22
lines changed

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

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,29 @@ import { useShouldAdapt } from '../lib/useShouldAdapt';
88
import { useConfig } from './ConfigContext';
99
import { LoopVideoPlayer } from './LoopVideoPlayer';
1010

11-
const videoContainerStyles = (height: number, width: number) => css`
11+
const videoContainerStyles = css`
1212
z-index: ${getZIndex('loop-video-container')};
1313
position: relative;
14-
height: ${height}px;
15-
width: ${width}px;
1614
`;
1715

1816
type Props = {
1917
src: string;
2018
videoId: string;
2119
width?: number;
2220
height?: number;
21+
thumbnailImage: string;
22+
fallbackImageComponent: JSX.Element;
2323
hasAudio?: boolean;
24-
fallbackImage: JSX.Element;
2524
};
2625

2726
export const LoopVideo = ({
2827
src,
2928
videoId,
3029
width = 600,
3130
height = 360,
31+
thumbnailImage,
32+
fallbackImageComponent,
3233
hasAudio = true,
33-
fallbackImage,
3434
}: Props) => {
3535
const adapted = useShouldAdapt();
3636
const { renderingTarget } = useConfig();
@@ -39,6 +39,7 @@ export const LoopVideo = ({
3939
const [isPlaying, setIsPlaying] = useState(false);
4040
const [isMuted, setIsMuted] = useState(true);
4141
const [currentTime, setCurrentTime] = useState(0);
42+
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
4243
/**
4344
* Keep a track of whether the video has been in view. We only want to
4445
* pause the video if it has been in view.
@@ -57,11 +58,17 @@ export const LoopVideo = ({
5758
if (!vidRef.current) return;
5859

5960
if (isInView) {
60-
if (!hasBeenInView) {
61-
// When the video first comes into view, it should autoplay
62-
setIsPlaying(true);
63-
void vidRef.current.play();
61+
// We only want to autoplay the first time the video comes into view.
62+
if (hasBeenInView) return;
63+
64+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
65+
setPrefersReducedMotion(true);
66+
return;
6467
}
68+
69+
setIsPlaying(true);
70+
void vidRef.current.play();
71+
6572
setHasBeenInView(true);
6673
}
6774

@@ -73,7 +80,7 @@ export const LoopVideo = ({
7380

7481
if (renderingTarget !== 'Web') return null;
7582

76-
if (adapted) return fallbackImage;
83+
if (adapted) return fallbackImageComponent;
7784

7885
const handleClick = (event: React.SyntheticEvent) => {
7986
event.preventDefault();
@@ -141,18 +148,14 @@ export const LoopVideo = ({
141148
const AudioIcon = isMuted ? SvgAudioMute : SvgAudio;
142149

143150
return (
144-
<div
145-
className="loop-video-container"
146-
ref={setNode}
147-
css={videoContainerStyles(height, width)}
148-
>
151+
<div ref={setNode} css={videoContainerStyles}>
149152
<LoopVideoPlayer
150153
src={src}
151154
videoId={videoId}
152155
width={width}
153156
height={height}
154157
hasAudio={hasAudio}
155-
fallbackImage={fallbackImage}
158+
fallbackImageComponent={fallbackImageComponent}
156159
currentTime={currentTime}
157160
setCurrentTime={setCurrentTime}
158161
ref={vidRef}
@@ -166,6 +169,9 @@ export const LoopVideo = ({
166169
handleKeyDown={handleKeyDown}
167170
onError={onError}
168171
AudioIcon={AudioIcon}
172+
thumbnailImage={
173+
prefersReducedMotion ? thumbnailImage : undefined
174+
}
169175
/>
170176
</div>
171177
);

dotcom-rendering/src/components/LoopVideo.stories.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ export const Default = {
2323
videoId: 'test-video-1',
2424
height: 337.5,
2525
width: 600,
26-
fallbackImage: (
26+
thumbnailImage:
27+
'https://media.guim.co.uk/9bdb802e6da5d3fd249b5060f367b3a817965f0c/0_0_1800_1080/master/1800.jpg',
28+
fallbackImageComponent: (
2729
<CardPicture
28-
mainImage="https://i.guim.co.uk/img/media/13dd7e5c4ca32a53cd22dfd90ac1845ef5e5d643/91_0_1800_1080/master/1800.jpg?width=465&dpr=1&s=none&crop=5%3A4"
30+
mainImage="https://media.guim.co.uk/9bdb802e6da5d3fd249b5060f367b3a817965f0c/0_0_1800_1080/master/1800.jpg"
2931
imageSize="large"
3032
loading="eager"
3133
/>

dotcom-rendering/src/components/LoopVideoPlayer.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type Props = {
4242
width: number;
4343
height: number;
4444
hasAudio: boolean;
45-
fallbackImage: JSX.Element;
45+
fallbackImageComponent: JSX.Element;
4646
isPlayable: boolean;
4747
setIsPlayable: Dispatch<SetStateAction<boolean>>;
4848
isPlaying: boolean;
@@ -55,6 +55,12 @@ type Props = {
5555
handleKeyDown: (event: React.KeyboardEvent<HTMLVideoElement>) => void;
5656
onError: (event: SyntheticEvent<HTMLVideoElement>) => void;
5757
AudioIcon: (iconProps: IconProps) => JSX.Element;
58+
/**
59+
* We ONLY show a thumbnail image when the user has indicated that they do
60+
* not want videos to play automatically, e.g. prefers reduced motion. Otherwise,
61+
* we do not bother downloading the image, as the video will be autoplayed.
62+
*/
63+
thumbnailImage?: string;
5864
};
5965

6066
/**
@@ -69,7 +75,8 @@ export const LoopVideoPlayer = forwardRef(
6975
width,
7076
height,
7177
hasAudio,
72-
fallbackImage,
78+
fallbackImageComponent,
79+
thumbnailImage,
7380
isPlayable,
7481
setIsPlayable,
7582
isPlaying,
@@ -93,12 +100,13 @@ export const LoopVideoPlayer = forwardRef(
93100
<video
94101
id={loopVideoId}
95102
ref={ref}
96-
preload="none"
103+
preload={thumbnailImage ? 'metadata' : 'none'}
97104
loop={true}
98105
muted={isMuted}
99106
playsInline={true}
100107
height={height}
101108
width={width}
109+
poster={thumbnailImage ?? undefined}
102110
onPlaying={() => {
103111
setIsPlaying(true);
104112
}}
@@ -126,7 +134,7 @@ export const LoopVideoPlayer = forwardRef(
126134
performance on supported browsers. https://web.dev/articles/video-and-source-tags */}
127135
{/* <source src={webmSrc} type="video/webm"> */}
128136
<source src={src} type="video/mp4" />
129-
{fallbackImage}
137+
{fallbackImageComponent}
130138
</video>
131139
{ref && 'current' in ref && ref.current && (
132140
<>

0 commit comments

Comments
 (0)