Motion

Lazy video

A muted loop that loads near the viewport and plays only on screen.

stablev0.1.0 Chromium 103 ui-scale safe

Install

Copies the file into components/ds/, with its registry dependencies and npm packages. See Installation for the one-time setup.
bunx shadcn@3 add @fastrp/lazy-video

Examples

Default

Open alone
Karakterler
Satranç

API

<LazyVideo />

A muted loop that loads when it is about to be seen and plays only while it is on screen, so a page of clips costs one or two decoders at a time. It covers its box: size the parent.

PropTypeDefaultNotes
src*string—
posterstring—Shown until the clip loads, and for good if autoplay is blocked.

Dependencies

npm

  • motion@^12.40.0

Registry

Source

components/ds/lazy-video.tsxShow
'use client';

import { useInView } from 'motion/react';
import type * as React from 'react';
import { useEffect, useRef } from 'react';
import { useScrollRoot } from '@/components/ds/scroll-root';
import { cn } from '@/lib/utils';

// @fast-ds [email protected]

export type LazyVideoProps = Omit<
  React.ComponentProps<'video'>,
  'src' | 'autoPlay' | 'muted' | 'loop' | 'preload'
> & {
  src: string;
  /** Shown until the clip loads, and for good if autoplay is blocked. */
  poster?: string;
};

/**
 * A muted loop that loads when it is about to be seen and plays only while it is on screen, so a
 * page of clips costs one or two decoders at a time. It covers its box: size the parent.
 */
function LazyVideo({ src, poster, className, ...props }: LazyVideoProps) {
  const ref = useRef<HTMLVideoElement>(null);
  const root = useScrollRoot();
  const near = useInView(ref, { once: true, margin: '50% 0px 50% 0px', root });
  const visible = useInView(ref, { amount: 0.25, root });

  useEffect(() => {
    const video = ref.current;
    if (!video) return;
    // A blocked autoplay leaves the poster up; nothing to recover.
    if (visible) video.play().catch(() => {});
    else video.pause();
  }, [visible]);

  return (
    <video
      ref={ref}
      data-slot="lazy-video"
      src={near ? src : undefined}
      poster={poster}
      muted
      loop
      playsInline
      preload={near ? 'auto' : 'none'}
      className={cn('size-full object-cover', className)}
      {...props}
    />
  );
}

export { LazyVideo };