lottie-react
Animation

Seeking and segments

Move the playhead, play part of the file, and wire up a scrub gesture.

seek moves the playhead without touching playback: playing stays playing, paused stays paused.

import { Lottie, type LottieHandle } from "lottie-react";import { useRef } from "react";export function SeekUnits() {  const handle = useRef<LottieHandle>(null);  return (    <>      <Lottie src="/anim.json" autoplay loop lottieRef={handle} />      <div>        <button type="button" onClick={() => handle.current?.seek(135)}>          frame 135        </button>        <button          type="button"          onClick={() => handle.current?.seek({ percent: 50 })}        >          50%        </button>        <button          type="button"          onClick={() => handle.current?.seek({ seconds: 1 })}        >          1s        </button>        <button          type="button"          onClick={() => handle.current?.seek({ marker: "middle" })}        >          marker        </button>      </div>    </>  );}

The units

A plain number is a frame. Every other unit names itself:

lottie.seek(90);
lottie.seek({ frame: 90 });
lottie.seek({ percent: 50 });
lottie.seek({ seconds: 1.5 });
lottie.seek({ marker: "intro" });

One unit per call: combining two is a compile error. percent and seconds measure the currently playable range, and out-of-range frames clamp to it. A marker resolves to the frame the designer placed it on; a marker outside the range clamps and warns, because that is an authoring mistake worth naming.

Play part of the file

playSegments narrows playback to a range and plays it:

playable frames: 0

import { LottieDisplay, useLottie } from "lottie-react";export function PlaySegments() {  const lottie = useLottie({ src: "/anim.json", autoplay: true, loop: true });  return (    <>      <LottieDisplay lottie={lottie} />      <p>playable frames: {lottie.playableFrames}</p>      <div>        <button type="button" onClick={() => lottie.playSegments([0, 90])}>          first half        </button>        <button type="button" onClick={() => lottie.playSegments([90, 180])}>          second half        </button>        <button          type="button"          onClick={() => lottie.playSegments({ marker: "middle" })}        >          marker span        </button>        <button type="button" onClick={() => lottie.resetSegments()}>          whole file        </button>      </div>    </>  );}

A range is [first, last], or { marker: "intro" } to use a marker's own duration; a marker without a duration is refused with a warning, because it is a point rather than a span. playableFrames and playableDuration follow the active range, as the readout above shows. resetSegments() returns playback to the whole file.

Pass { queue: true } as the second argument and the new range waits for the current pass to finish. With nothing playing there is nothing to wait for, and the range applies at once.

Scrubbing

A drag gesture is three calls: scrubStart() once, scrubTo(frame) per move, scrubEnd() once. scrubEnd resumes playback if it was playing when the drag began, and it is the only call that clears that memory, so an unfinished drag is a missing call rather than a silent surprise.

On this page