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.
The end of a range is where playback stops rather than a frame it draws: [0, 10] plays frames 0 to 9.
The engine reads a marker's name from its comment text, and a comment shaped like key: value lines or JSON becomes data instead of a name, so a marker called loop: start is unaddressable. Keep names plain.
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.
Queued once from ready, it plays an intro and settles into a loop with no re-arming:
import { Lottie, type LottieHandle } from "lottie-react";import { useRef } from "react";export function QueueIntroThenLoop() { const handle = useRef<LottieHandle>(null); return ( <Lottie src="/anim.json" autoplay loop lottieRef={handle} subscriptions={{ ready: () => handle.current?.playSegments({ marker: "middle" }, { queue: true }), }} /> );}A queued range waits out a stop() and still applies at the end of the next play's first pass; resetSegments() discards it.
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.