1+ import { useEffect , useRef , useState } from "react" ;
2+
13interface 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
1527export 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