react-lottie 2.3.2 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +265 -3
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.3.3](https://github.com/chenqingspring/react-lottie/compare/v2.3.2...v2.3.3) (2026-11-08)
6
+
7
+ ### Documentation
8
+
9
+ * document animations that reference raster images: the `assets` array, the `assetsPath` option and rewriting assets to bundler-hashed URLs ([#49](https://github.com/chenqingspring/react-lottie/issues/49), [#59](https://github.com/chenqingspring/react-lottie/issues/59))
10
+ * document aliasing `lottie-web` to `lottie_light`, `lottie_light_canvas` or `lottie_light_html` to cut the bundled player by about 45% ([#79](https://github.com/chenqingspring/react-lottie/issues/79))
11
+ * document server-side rendering and add a regression test that renders the component in a Node process without a DOM ([#17](https://github.com/chenqingspring/react-lottie/issues/17))
12
+
5
13
  ## [2.3.2](https://github.com/chenqingspring/react-lottie/compare/v2.3.1...v2.3.2) (2026-11-07)
6
14
 
7
15
  ### Bug Fixes
package/README.md CHANGED
@@ -103,8 +103,10 @@ shares one React copy and tree-shakes lottie-web is smaller.
103
103
 
104
104
  ## Requirements
105
105
 
106
- React 16.8 or newer. The test suite runs against React 18; the component uses
107
- plain class components and works with React 19 as well.
106
+ React 16.8 or newer, including React 17, React 18 and React 19. The
107
+ `react: ">=16.8.0"` peer dependency resolves without `--legacy-peer-deps` or
108
+ `--force`. The test suite runs against React 18; the component uses plain class
109
+ components and works with React 17 and React 19 as well.
108
110
 
109
111
  ## Local development
110
112
 
@@ -253,6 +255,170 @@ Finally, the file itself can usually get smaller: export at a lower precision,
253
255
  remove unused layers and precompositions, avoid embedded raster images, and let
254
256
  gzip/Brotli compress the JSON. LottieFiles' optimizer automates the first steps.
255
257
 
258
+ ### Animations with raster images
259
+
260
+ An After Effects export that contains bitmap layers references those files from
261
+ the animation's `assets` array. Each entry carries a directory (`u`), a file name
262
+ (`p`) and an embedded flag (`e`):
263
+
264
+ ```json
265
+ { "id": "image_0", "w": 720, "h": 720, "u": "images/", "p": "img_0.png", "e": 0 }
266
+ ```
267
+
268
+ With `"e": 0` lottie-web loads the file separately and resolves `u + p` relative
269
+ to the page, which breaks as soon as the app is served from a sub-path or
270
+ navigates client-side. Set `options.assetsPath` to the directory where the
271
+ images are deployed instead; react-lottie forwards unknown options to
272
+ lottie-web:
273
+
274
+ ```jsx
275
+ const defaultOptions = {
276
+ path: '/animations/cypher.json',
277
+ assetsPath: '/animations/images/', // trailing slash required
278
+ loop: true,
279
+ autoplay: true,
280
+ };
281
+
282
+ <Lottie options={defaultOptions} height={400} width={400} />
283
+ ```
284
+
285
+ Copy the images next to the JSON in `public/animations/images/` so they are
286
+ served as static files instead of being bundled. `assetsPath` works with both
287
+ `path` and `animationData`, and it applies to every image in the animation.
288
+
289
+ If you want the bundler to hash and inline the images, import them yourself and
290
+ rewrite the asset entries before passing the data. Don't mutate the imported
291
+ JSON in place when it is a frozen ESM object:
292
+
293
+ ```jsx
294
+ import img0 from './images/img_0.png';
295
+ import animationData from './cypher.json';
296
+
297
+ const data = {
298
+ ...animationData,
299
+ assets: animationData.assets.map((asset) => (
300
+ asset.id === 'image_0' ? { ...asset, u: '', p: img0 } : asset
301
+ )),
302
+ };
303
+
304
+ <Lottie options={{ animationData: data, loop: true, autoplay: true }} />
305
+ ```
306
+
307
+ `"e": 1` means the image is embedded in the JSON as a base64 data URI. That
308
+ keeps the animation self-contained, but it inflates the file by roughly a third
309
+ and prevents the browser from caching the image separately, so prefer external
310
+ images for anything large.
311
+
312
+ ### Shrinking the lottie-web bundle
313
+
314
+ react-lottie imports the full `lottie-web` player (299 kB minified, 611 kB
315
+ unminified in lottie-web 5.13), which includes the expression engine and every
316
+ renderer. If your animations don't use expressions and you only need one
317
+ renderer, alias `lottie-web` to a smaller build; react-lottie keeps working
318
+ because those builds expose the same `loadAnimation` API.
319
+
320
+ Vite:
321
+
322
+ ```js
323
+ // vite.config.js
324
+ export default defineConfig({
325
+ resolve: {
326
+ alias: {
327
+ 'lottie-web': 'lottie-web/build/player/lottie_light.js',
328
+ },
329
+ },
330
+ });
331
+ ```
332
+
333
+ webpack (Create React App, Next.js):
334
+
335
+ ```js
336
+ // next.config.js
337
+ module.exports = {
338
+ webpack(config) {
339
+ config.resolve.alias['lottie-web'] = require.resolve(
340
+ 'lottie-web/build/player/lottie_light.js'
341
+ );
342
+ return config;
343
+ },
344
+ };
345
+ ```
346
+
347
+ The light builds are about 45% smaller (164 kB vs 299 kB minified):
348
+
349
+ * `lottie_light.js` — SVG renderer only, no expressions; shapes and raster
350
+ images still work.
351
+ * `lottie_light_canvas.js` — canvas renderer only, no expressions.
352
+ * `lottie_light_html.js` — HTML renderer only, no expressions.
353
+
354
+ Pick the build that matches `options.renderer`: `lottie_light` only registers
355
+ the SVG renderer, so pass `renderer: 'svg'` (the default); use
356
+ `lottie_light_canvas` with `renderer: 'canvas'`. Don't alias when an animation
357
+ uses expressions or effects, because the light builds silently skip them.
358
+
359
+ ### Performance on iOS and low-end devices
360
+
361
+ File size mostly affects how long an animation takes to download and parse, not
362
+ its frame rate. Once playback starts, the cost of each frame comes from what the
363
+ renderer has to draw: the number of layers and shapes, masks and mattes,
364
+ expressions, effects, and the pixel size of the canvas. A multi-megabyte export
365
+ is a good signal that there is a lot of that (issue #133).
366
+
367
+ On iOS 14.5, Safari, Chrome and Firefox all run on WebKit, so they render at the
368
+ same speed; measuring in Safari is representative. Phones are also much slower
369
+ than a desktop, so an animation that is smooth on a laptop can drop frames on
370
+ mobile.
371
+
372
+ In rough order of impact:
373
+
374
+ 1. **Reduce the animation.** Remove hidden and unused layers, flatten
375
+ precompositions, simplify masks and mattes, drop expressions and effects, and
376
+ avoid embedded raster images. If one scene is still heavy, split it into
377
+ smaller files and play only the needed range with `segments`.
378
+ 2. **Draw it at the size it is displayed.** `width` and `height` set the
379
+ container size; rendering a 1200 px animation into a 300 px slot does more
380
+ work than necessary. With the canvas renderer, also cap the backing-store
381
+ resolution: at a device pixel ratio of 3, a 400 px canvas is rasterized at
382
+ 1200 px unless you set `dpr`.
383
+ 3. **Compare the renderers.** `svg` (the default) is usually fastest for a few
384
+ large shapes and stays crisp at any size. `canvas` avoids updating hundreds
385
+ of DOM nodes and often wins for animations with many small shapes. Measure
386
+ both on the target device.
387
+ 4. **Stop off-screen animations.** lottie-web drives every playing animation
388
+ from the same `requestAnimationFrame` loop, so an animation scrolled out of
389
+ view still costs a frame. Pause it with `isPaused`, or with `pause()` on a
390
+ `ref`, driven by an `IntersectionObserver`.
391
+
392
+ ```jsx
393
+ const defaultOptions = {
394
+ animationData,
395
+ loop: true,
396
+ autoplay: true,
397
+ renderer: 'canvas',
398
+ rendererSettings: {
399
+ dpr: 1, // render at CSS resolution; use 2 for a sharper middle ground
400
+ },
401
+ };
402
+
403
+ <Lottie options={defaultOptions} height={300} width={300} />
404
+ ```
405
+
406
+ For the default `svg` renderer, `rendererSettings.progressiveLoad` builds the
407
+ layers over several frames instead of blocking on the first one. It does not
408
+ wait for the element to scroll into view; pause off-screen animations yourself:
409
+
410
+ ```jsx
411
+ // `visible` is kept up to date by an IntersectionObserver on a wrapper element.
412
+ <Lottie
413
+ options={{ animationData, loop: true, autoplay: false, rendererSettings: { progressiveLoad: true } }}
414
+ isPaused={!visible}
415
+ />
416
+ ```
417
+
418
+ If a reduced file still drops frames, profile it: record a timeline in Safari's
419
+ Web Inspector while the animation plays and check whether the time goes to
420
+ lottie-web's frame update or to the browser painting and compositing the layers.
421
+
256
422
  ### Play on hover
257
423
 
258
424
  `eventListeners` takes lottie-web animation events (`complete`, `loopComplete`,
@@ -367,6 +533,102 @@ The container is a `div` by default, which is not a valid child of `<p>`. Pass
367
533
  lottie-web should render into. Changing `as` reloads the animation into the new
368
534
  element and keeps the current frame.
369
535
 
536
+ ### Switch between several animations
537
+
538
+ Each `<Lottie>` owns one lottie-web instance, so an app can render two
539
+ animations side by side or keep one animation per page. To choose which one is
540
+ shown, keep the animation data in a map and pass the entry for the current page
541
+ as `options.animationData` (issue #128):
542
+
543
+ ```jsx
544
+ import homeData from './animations/home.json';
545
+ import aboutData from './animations/about.json';
546
+
547
+ const animations = { home: homeData, about: aboutData };
548
+
549
+ const PageAnimation = ({ page }) => (
550
+ <Lottie
551
+ options={{
552
+ loop: true,
553
+ autoplay: true,
554
+ animationData: animations[page],
555
+ }}
556
+ height={300}
557
+ width={300}
558
+ />
559
+ );
560
+ ```
561
+
562
+ Changing `options.animationData` reloads the animation into the same container,
563
+ so a `key` is not required. Keep the JSON modules stable: copying the data in
564
+ `render` (`animationData: { ...animations[page] }`) hands the component a new
565
+ object on every render and reloads the animation each time. If you do want to
566
+ start over — to replay an intro, for example — give the component a `key` so
567
+ React unmounts and remounts it:
568
+
569
+ ```jsx
570
+ <Lottie key={page} options={{ ...defaultOptions, animationData: animations[page] }} />
571
+ ```
572
+
573
+ Unmounting destroys the lottie-web instance, so only the visible page animates.
574
+ When the files are large, load them with `options.path` and fetch the JSON for
575
+ the next page ahead of time instead of shipping every animation in one bundle;
576
+ see [Loading animation data without bloating the bundle](#loading-animation-data-without-bloating-the-bundle).
577
+
578
+ ### Server-side rendering
579
+
580
+ `<Lottie />` renders only a sized container element on the server; lottie-web is
581
+ imported but instantiated in `componentDidMount` only, so `renderToString` (and
582
+ Next.js/Gatsby prerendering) works and the animation starts after hydration:
583
+
584
+ ```jsx
585
+ // server output: <div style="width:400px;height:400px;..."></div>
586
+ <Lottie options={defaultOptions} height={400} width={400} />
587
+ ```
588
+
589
+ Importing the package outside a browser no longer throws
590
+ `ReferenceError: window is not defined`; lottie-web guards its DOM access at
591
+ module load. If your bundler still evaluates the player on the server, load the
592
+ component without SSR:
593
+
594
+ ```jsx
595
+ // Next.js
596
+ import dynamic from 'next/dynamic';
597
+
598
+ const Lottie = dynamic(() => import('react-lottie'), { ssr: false });
599
+ ```
600
+
601
+ Animations loaded from `options.path` are fetched by lottie-web after mount, so
602
+ nothing is requested during the server render. Avoid the hydration trap
603
+ described in [Animations and page navigation](#animations-and-page-navigation)
604
+ when the container is nested in a `<p>`.
605
+
606
+ ### Animations and page navigation
607
+
608
+ react-lottie creates a lottie-web instance when the component mounts and calls
609
+ `destroy()` on unmount, so client-side navigation is safe as long as the page
610
+ really unmounts. A layout that renders every page at once (or keeps pages
611
+ mounted when switching tabs) keeps every animation running; mount only the
612
+ visible one.
613
+
614
+ One non-obvious trap on SSR frameworks such as Gatsby or Next.js: the wrapper is
615
+ a `div` by default, and a `div` inside a `p` is invalid HTML. The browser moves
616
+ the `div` out of the paragraph while parsing the server-rendered page, React
617
+ reports a hydration mismatch (`validateDOMNesting`), and the animation node ends
618
+ up outside the page subtree. Navigating back and forth can then leave animation
619
+ nodes behind. Render the animation with `as="span"` (issue #73) or move it
620
+ outside the paragraph:
621
+
622
+ ```jsx
623
+ <p>
624
+ Text <Lottie as="span" options={defaultOptions} /> more text
625
+ </p>
626
+ ```
627
+
628
+ If animations still accumulate, confirm that each page unmounts (React DevTools
629
+ shows the component tree changing), and pause off-screen instances as described
630
+ in [Performance on iOS and low-end devices](#performance-on-ios-and-low-end-devices).
631
+
370
632
  ### Imperative control
371
633
 
372
634
  A `ref` to the component exposes `play()`, `pause()`, `stop()`,
@@ -398,7 +660,7 @@ the object representing the animation settings that will be instantiated by body
398
660
  >
399
661
  >**path** *optional* — a URL (or path) to a JSON animation file. Mutually exclusive with `animationData`.
400
662
  >
401
- >**renderer** *optional* [default: `svg`]
663
+ >**renderer** *optional* [default: `svg`] — `svg`, `canvas` or `html`. See [Performance on iOS and low-end devices](#performance-on-ios-and-low-end-devices).
402
664
  >
403
665
  >**rendererSettings** *required*
404
666
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-lottie",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
4
4
  "funding": "https://opencollective.com/react-lottie",
5
5
  "description": "lottie animation view for React",
6
6
  "repository": {