React Native
react-native-pulsar-lottie plays a Pulsar haptic pattern locked to a Lottie animation. Its HapticLottieView is a non-breaking superset of lottie-react-native’s LottieView: it accepts every prop and ref method you already use and adds haptic-only ones. With haptics omitted it behaves exactly like LottieView.
Read the Lottie SDK overview for the engine modes, timing rules and simulator notes this page assumes.
Requirements
Section titled “Requirements”- React Native 0.71+ with the New Architecture enabled
- The Pulsar React Native SDK (
react-native-pulsar1.6+) lottie-react-native7.0+react-native-reanimated4.0+ andreact-native-worklets
Installation
Section titled “Installation”Latest available version: 0.1.0
npx expo install react-native-pulsar-lottie react-native-pulsar lottie-react-native react-native-reanimatedThen run prebuild to generate the native project files:
npx expo prebuildnpm install react-native-pulsar-lottie react-native-pulsar lottie-react-native react-native-reanimated react-native-workletsEverything here is a peer dependency, so each package is installed and configured exactly as its own docs describe — this library adds no native code of its own.
HapticLottieView
Section titled “HapticLottieView”import { HapticLottieView } from 'react-native-pulsar-lottie';import type { Pattern } from 'react-native-pulsar';
const pattern: Pattern = { discretePattern: [{ time: 0, amplitude: 1, frequency: 0.5 }], continuousPattern: { amplitude: [{ time: 0, value: 0 }, { time: 400, value: 1 }, { time: 800, value: 0 }], frequency: [{ time: 0, value: 0.3 }, { time: 800, value: 0.8 }], },};
<HapticLottieView source={require('./success.json')} haptics={pattern} autoPlay style={{ width: 200, height: 200 }}/>Everything LottieView does still works — source, loop, autoPlay, style, resizeMode, onAnimationFinish and the rest. You only add haptics.
From a bundle preset
Section titled “From a bundle preset”A preset in a .pulsar bundle already pairs an animation with the pattern its author aligned to it. Pass the preset and the view takes both — no source, no haptics:
import { loadBundleSync } from '@/assets/my-pack.bundle';
const Pack = loadBundleSync();
<HapticLottieView preset={Pack.celebration} autoPlay style={{ width: 200, height: 200 }} />The preset supplies source from its Lottie, haptics from its pattern, and durationMs from its authored length — each still overridable on its own. One that carries audio plays that audio too, which is why it starts in pattern mode. See bundle presets.
The generated module embeds JSON Lotties only, and both loaders carry them. A preset authored as a dotLottie reports hasAnimation: true but carries no animation — pass source yourself there. With neither available the view renders nothing and warns once. Its audio only reaches the device on the asset-backed path: loadBundleSync() plays the embedded pattern, while loadBundleSync(true) and loadBundleAsync() hand the .pulsar to native code, sound included.
See preset bundles for how bundles are generated and loaded.
Haptic props
Section titled “Haptic props”| Prop | Type | Default | Description |
|---|---|---|---|
preset | PresetHandle | – | Bundle preset supplying source, haptics and durationMs at once. |
haptics | Pattern | () => void | – | Pattern to sync, or a preset trigger function (pattern mode only). Overrides the preset’s pattern. |
hapticMode | 'realtime' | 'pattern' | derived | 'realtime', or 'pattern' for a preset carrying audio. |
hapticOffset | number (ms) | 0 | Shift the haptics by ±ms relative to the animation. |
hapticsEnabled | boolean | true | Turn haptics off without touching the animation. |
durationMs | number | derived | Clock length in realtime mode. |
source is required as usual, unless a preset supplies it.
In realtime mode the duration resolves as: an explicit durationMs, then the preset’s authored duration, then the Lottie JSON’s own fr/ip/op — readable only when source is an inline object such as require('./success.json') — then the pattern’s length. A remote animation loaded by URL exposes none of that to JS, so pass durationMs explicitly there.
Imperative control
Section titled “Imperative control”The ref mirrors LottieView’s transport and adds stop() and setTimestamp(ms). In realtime mode these steer the shared master clock, so the animation and the haptics move together.
const ref = useRef<HapticLottieRef>(null);
<HapticLottieView ref={ref} preset={Pack.celebration} style={{ width: 200, height: 200 }} /><Button title="Replay" onPress={() => ref.current?.play()} />| Method | Description |
|---|---|
play(startFrame?, endFrame?) | Play from the start, or a frame segment. Starts the haptics too. |
pause() | Pause both animation and haptics. |
resume() | Resume from the current position. |
stop() / reset() | Rewind to the start and stop the haptics. |
setTimestamp(ms) | Seek both to ms from the start. |
useHapticLottie
Section titled “useHapticLottie”Attach haptics to a LottieView you already own, without swapping the component. This is the pattern-mode (aligned-start) path: the hook pre-parses the pattern so the engine is warm, and hands back play and stop to call next to your own transport.
import LottieView from 'lottie-react-native';import { useHapticLottie } from 'react-native-pulsar-lottie';
const lottieRef = useRef<LottieView>(null);const haptics = useHapticLottie({ preset: Pack.celebration });
const start = () => { lottieRef.current?.play(); haptics.play();};| Option | Type | Default | Description |
|---|---|---|---|
preset | PresetHandle | – | Bundle preset to fire, including its synced audio. |
haptics | Pattern | () => void | – | Pattern or preset trigger to fire. Overrides preset. |
hapticsEnabled | boolean | true | Disable firing without unwiring the hook. |
It returns play(), stop() and isReady — true once the pattern is parsed, and always true for a preset trigger.
For progress-driven realtime sync with seek and loop, use HapticLottieView instead.
Engine modes
Section titled “Engine modes”type HapticMode = 'realtime' | 'pattern';realtime(default) — a Reanimated frame callback drives the Lottieprogresson the UI thread, with no React re-renders, and samples the pattern intoRealtimeComposerevents. Honourspause,setTimestamp,loopand segments. Requires aPattern.pattern— the pattern, preset or trigger plays whole throughPatternComposer, aligned to the animation start. Best native fidelity, and the only mode that plays a preset’s audio.
Passing hapticMode="realtime" with a preset trigger function rather than a Pattern falls back to the pattern-mode path, because there is nothing to sample.
See Choosing between them for the trade-offs.
import type { HapticConfig, HapticLottieProps, HapticLottieRef, HapticMode, HapticSource, HapticLottieHandle, UseHapticLottieOptions,} from 'react-native-pulsar-lottie';HapticSource is Pattern | (() => void). HapticLottieProps is LottieViewProps plus HapticConfig, with source required unless a preset supplies it. See the Jest mock for unit tests.