Skip to content

Commit fab44ae

Browse files
authored
Merge pull request #21 from deco-sites/jonasjesus/deco-5283-video-formats-poster-lazy
feat(ui): Video supports WebM/MP4 sources, poster and lazy loading (DECO-5283)
2 parents a0ed77d + c9296e6 commit fab44ae

1 file changed

Lines changed: 43 additions & 2 deletions

File tree

src/components/ui/Video.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import { useEffect, useRef, useState } from "react";
2+
13
interface Props {
4+
/** Primary/fallback source — usually an MP4 (broadest browser support). */
25
src: string;
6+
/** Optional modern source (WebM); offered first when the browser supports it. */
7+
webm?: string;
38
width?: number;
49
height?: number;
510
autoPlay?: boolean;
@@ -8,12 +13,20 @@ interface Props {
813
playsInline?: boolean;
914
controls?: boolean;
1015
className?: string;
16+
/** "eager" disables lazy loading (use for above-the-fold/hero video). */
1117
loading?: "lazy" | "eager";
18+
/** Image shown before the video loads/plays. */
1219
poster?: string;
20+
/**
21+
* Load the video only when it scrolls into view (saves bandwidth on
22+
* below-the-fold videos). Defaults to true unless `loading="eager"`.
23+
*/
24+
lazy?: boolean;
1325
}
1426

1527
export default function Video({
1628
src,
29+
webm,
1730
width,
1831
height,
1932
autoPlay = true,
@@ -22,11 +35,35 @@ export default function Video({
2235
playsInline = true,
2336
controls = false,
2437
className,
38+
loading,
2539
poster,
40+
lazy,
2641
}: Props) {
42+
const isLazy = lazy ?? loading !== "eager";
43+
const ref = useRef<HTMLVideoElement>(null);
44+
// When lazy, hold back the <source> elements (and their bytes) until in view.
45+
const [visible, setVisible] = useState(!isLazy);
46+
47+
useEffect(() => {
48+
if (!isLazy || visible) return;
49+
const el = ref.current;
50+
if (!el) return;
51+
const io = new IntersectionObserver(
52+
(entries) => {
53+
if (entries.some((e) => e.isIntersecting)) {
54+
setVisible(true);
55+
io.disconnect();
56+
}
57+
},
58+
{ rootMargin: "200px" },
59+
);
60+
io.observe(el);
61+
return () => io.disconnect();
62+
}, [isLazy, visible]);
63+
2764
return (
2865
<video
29-
src={src}
66+
ref={ref}
3067
width={width}
3168
height={height}
3269
autoPlay={autoPlay}
@@ -36,6 +73,10 @@ export default function Video({
3673
controls={controls}
3774
className={className}
3875
poster={poster}
39-
/>
76+
preload={visible ? "metadata" : "none"}
77+
>
78+
{visible && webm && <source src={webm} type="video/webm" />}
79+
{visible && <source src={src} type="video/mp4" />}
80+
</video>
4081
);
4182
}

0 commit comments

Comments
 (0)