react-lottie 2.4.2 → 2.5.1

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
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.5.1](https://github.com/chenqingspring/react-lottie/compare/v2.5.0...v2.5.1) (2026-11-13)
6
+
7
+ ### Bug Fixes
8
+
9
+ * regenerate the animation when `options.renderer` changes, so a mounted component can switch between the `svg`, `canvas` and `html` engines and keeps its current frame and play/pause state; dropping the key falls back to `svg` (a change used to be silently ignored)
10
+
11
+ ### Documentation
12
+
13
+ * document switching `options.renderer` on a mounted component, and that a light `lottie-web` build registers one renderer only
14
+ * Storybook demo `lottie-renderer` with a live `svg` / `canvas` toggle next to both engines on their own component
15
+
16
+ ## [2.5.0](https://github.com/chenqingspring/react-lottie/compare/v2.4.2...v2.5.0) (2026-11-10)
17
+
18
+ ### Features
19
+
20
+ * regenerate the animation when `options.rendererSettings` changes, so `preserveAspectRatio` and the other renderer settings can be switched on a mounted component; the current frame and play/pause state are kept, and recreating an equal object does not reload anything ([#98](https://github.com/chenqingspring/react-lottie/issues/98))
21
+ * Storybook demo of the live `preserveAspectRatio` toggle next to the three static modes
22
+
23
+ ### Documentation
24
+
25
+ * document switching `rendererSettings` on a mounted component instead of remounting the animation ([#98](https://github.com/chenqingspring/react-lottie/issues/98))
26
+
5
27
  ## [2.4.2](https://github.com/chenqingspring/react-lottie/compare/v2.4.1...v2.4.2) (2026-11-09)
6
28
 
7
29
  ### Documentation
package/README.md CHANGED
@@ -472,7 +472,8 @@ In rough order of impact:
472
472
  3. **Compare the renderers.** `svg` (the default) is usually fastest for a few
473
473
  large shapes and stays crisp at any size. `canvas` avoids updating hundreds
474
474
  of DOM nodes and often wins for animations with many small shapes. Measure
475
- both on the target device.
475
+ both on the target device; a mounted component can switch between them
476
+ (see [Switch renderers](#switch-renderers-svg-canvas-html)).
476
477
  4. **Stop off-screen animations.** lottie-web drives every playing animation
477
478
  from the same `requestAnimationFrame` loop, so an animation scrolled out of
478
479
  view still costs a frame. Pause it with `isPaused`, or with `pause()` on a
@@ -538,9 +539,11 @@ Two things make a working setting look broken:
538
539
  - `meet` and `slice` render identically when the container has the same aspect
539
540
  ratio as the animation. Sizing a square animation with `height={400}
540
541
  width={400}` leaves nothing to crop or letterbox.
541
- - The value is applied when the animation loads. Changing
542
- `options.rendererSettings` on a mounted component does not re-apply it;
543
- remount the animation (or change its `key`) to pick up a new value.
542
+ - The value is applied when the renderer is generated. Changing
543
+ `options.rendererSettings` on a mounted component regenerates the animation
544
+ and keeps the current frame and playback state, so a live toggle works; a
545
+ render that recreates an object with the same values does not reload
546
+ anything. See [Fit or crop, live](#fit-or-crop-live).
544
547
 
545
548
  If the `<svg>` in the inspector still lacks `preserveAspectRatio`, check what
546
549
  you pass as `options.animationData`. An ES module namespace
@@ -550,6 +553,50 @@ scale. See [Usage](#usage).
550
553
 
551
554
  The canvas renderer reads `preserveAspectRatio` too.
552
555
 
556
+ #### Fit or crop, live
557
+
558
+ `rendererSettings` can be switched after mount. The animation is regenerated
559
+ with the new settings and the current frame plus play/pause state are restored,
560
+ so a `meet`/`slice` toggle does not restart a half-played animation:
561
+
562
+ ```jsx
563
+ const [mode, setMode] = useState('xMidYMid meet');
564
+
565
+ <Lottie
566
+ options={{ animationData, loop: true, autoplay: true, rendererSettings: { preserveAspectRatio: mode } }}
567
+ height={200}
568
+ width={400}
569
+ />
570
+ <button onClick={() => setMode(mode === 'xMidYMid meet' ? 'xMidYMid slice' : 'xMidYMid meet')}>
571
+ toggle fit / crop
572
+ </button>
573
+ ```
574
+
575
+ The Storybook story `lottie-renderer-settings` demonstrates the same toggle.
576
+
577
+ ### Switch renderers (svg, canvas, html)
578
+
579
+ `renderer` selects the engine lottie-web uses to draw the animation and belongs
580
+ inside `options`. It defaults to `svg`; `canvas` and `html` are the other
581
+ choices.
582
+
583
+ ```jsx
584
+ const [renderer, setRenderer] = useState('svg');
585
+
586
+ <Lottie options={{ animationData, loop: true, autoplay: true, renderer }} height={200} width={200} />
587
+ <button onClick={() => setRenderer(renderer === 'svg' ? 'canvas' : 'svg')}>
588
+ switch renderer
589
+ </button>
590
+ ```
591
+
592
+ Changing `options.renderer` on a mounted component regenerates the animation
593
+ with the new engine and keeps the current frame and play/pause state, so the
594
+ toggle does not restart it. Dropping the key falls back to `svg` again. This
595
+ needs the full `lottie-web` player: a [light build](#shrinking-the-lottie-web-bundle)
596
+ registers a single renderer, so switching to another one fails to start.
597
+
598
+ The Storybook story `lottie-renderer` demonstrates the toggle.
599
+
553
600
  ### Masks are missing in Safari
554
601
 
555
602
  Safari resolves the `url(#…)` references that lottie-web generates for masks,
@@ -875,7 +922,7 @@ the object representing the animation settings that will be instantiated by body
875
922
  >
876
923
  >**renderer** *optional* [default: `svg`] — `svg`, `canvas` or `html`. See [Performance on iOS and low-end devices](#performance-on-ios-and-low-end-devices).
877
924
  >
878
- >**rendererSettings** *optional* — settings forwarded to lottie-web's renderer, such as `preserveAspectRatio`, `progressiveLoad`, `className` and `id`. Must be inside `options`, not a prop on `<Lottie />`. Read once when the animation loads. See [Fit or crop an animation](#fit-or-crop-an-animation-preserveaspectratio).
925
+ >**rendererSettings** *optional* — settings forwarded to lottie-web's renderer, such as `preserveAspectRatio`, `progressiveLoad`, `className` and `id`. Must be inside `options`, not a prop on `<Lottie />`. Changing it regenerates the animation and keeps the current frame and playback state. See [Fit or crop an animation](#fit-or-crop-an-animation-preserveaspectratio).
879
926
  >
880
927
  >**locationHref** *optional* — the base URL lottie-web writes into its SVG mask, matte, gradient and filter references, applied with `setLocationHref()` before the animation is generated. Use it when Safari drops masks because of a `<base>` tag. See [Masks are missing in Safari](#masks-are-missing-in-safari).
881
928
 
package/dist/index.js CHANGED
@@ -9,7 +9,6 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
9
9
  var _lottieWeb = _interopRequireDefault(require("lottie-web"));
10
10
  var _colorFilters = require("./colorFilters");
11
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
12
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
13
12
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
14
13
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
15
14
  function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
@@ -25,6 +24,7 @@ function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf
25
24
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
26
25
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
27
26
  function _toPrimitive(t, e) { if ("object" != _typeof(t) || !t) return t; var r; if ("undefined" != typeof Symbol && void 0 !== (r = t[Symbol.toPrimitive])) { var i = r.call(t, e || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === e ? String : Number)(t); }
27
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
28
28
  var segmentsAreEqual = function segmentsAreEqual(a, b) {
29
29
  if (a === b) return true;
30
30
  if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
@@ -33,6 +33,27 @@ var segmentsAreEqual = function segmentsAreEqual(a, b) {
33
33
  });
34
34
  };
35
35
 
36
+ /* `rendererSettings` is usually written as an object literal inside `options`,
37
+ so a re-render creates a new object even when nothing changed. Compare the
38
+ values, not the identity, to avoid reloading the animation on every render
39
+ while still reacting to a real change (issue #98). */
40
+ var rendererSettingsAreEqual = function rendererSettingsAreEqual(a, b) {
41
+ if (a === b) return true;
42
+ if (a == null || b == null) return a == null && b == null;
43
+ if (_typeof(a) !== 'object' || _typeof(b) !== 'object') return false;
44
+ var keys = Object.keys(a);
45
+ if (keys.length !== Object.keys(b).length) return false;
46
+ return keys.every(function (key) {
47
+ return Object.prototype.hasOwnProperty.call(b, key) && a[key] === b[key];
48
+ });
49
+ };
50
+
51
+ /* lottie-web falls back to the SVG renderer when `options.renderer` is absent,
52
+ so compare the value it actually ends up with. */
53
+ var rendererOf = function rendererOf(options) {
54
+ return options && options.renderer ? options.renderer : 'svg';
55
+ };
56
+
36
57
  /* lottie-web crashes deep inside its renderer with "Cannot read property
37
58
  'length' of undefined" when the data is not a plain animation object. Accept
38
59
  the shapes users actually pass (a namespace import, a frozen ESM object) and
@@ -101,6 +122,8 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
101
122
  this.animationData = animationData;
102
123
  this.colorFilters = colorFilters;
103
124
  this.locationHref = options.locationHref;
125
+ this.rendererSettings = rendererSettings;
126
+ this.renderer = rendererOf(options);
104
127
  var filteredAnimationData = (0, _colorFilters.applyColorFilters)(_normalizeAnimationData(animationData, options.path), colorFilters);
105
128
  this.options = {
106
129
  container: this.el,
@@ -159,10 +182,18 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
159
182
  /* lottie-web bakes the base URL into each generated `url(#id)` reference,
160
183
  so a new href only takes effect when the animation is generated again. */
161
184
  var locationHrefChanged = this.locationHref !== this.props.options.locationHref;
185
+ /* `preserveAspectRatio`, `progressiveLoad` and the other renderer settings
186
+ are read by lottie-web when it generates the renderer, so a change has to
187
+ regenerate the animation too (issue #98). */
188
+ var rendererSettingsChanged = !rendererSettingsAreEqual(this.rendererSettings, this.props.options.rendererSettings);
189
+ /* `renderer` selects the engine that generates the animation (`svg`,
190
+ `canvas` or `html`), so it can only change by loading again. A change
191
+ used to be ignored, which left the animation on the old engine. */
192
+ var rendererChanged = this.renderer !== rendererOf(this.props.options);
162
193
  /* The wrapper element is part of lottie-web's configuration, so a new
163
194
  element means the animation has to be bound to the new node. */
164
195
  var containerChanged = prevProps.as !== this.props.as;
165
- if (dataChanged || pathChanged || colorFiltersChanged || locationHrefChanged || containerChanged) {
196
+ if (dataChanged || pathChanged || colorFiltersChanged || locationHrefChanged || rendererSettingsChanged || rendererChanged || containerChanged) {
166
197
  var currentFrame = this.anim ? this.anim.currentFrame : 0;
167
198
  var wasPaused = this.anim ? this.anim.isPaused : true;
168
199
  /* lottie-web cannot recolor a loaded animation and cannot move one to
@@ -174,10 +205,16 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
174
205
  this.animationData = this.props.options.animationData;
175
206
  this.colorFilters = this.props.colorFilters;
176
207
  this.locationHref = this.props.options.locationHref;
208
+ this.rendererSettings = this.props.options.rendererSettings;
209
+ this.renderer = rendererOf(this.props.options);
177
210
  this.options = _objectSpread(_objectSpread({}, this.options), this.props.options);
178
211
  /* `options` is caller-owned and knows nothing about the wrapper, so the
179
212
  ref (already pointing at the newly rendered node) wins. */
180
213
  this.options.container = this.el;
214
+ /* Spreading does not delete a key the caller dropped, so a removed
215
+ rendererSettings or renderer would otherwise keep its old value. */
216
+ this.options.rendererSettings = this.props.options.rendererSettings;
217
+ this.options.renderer = this.renderer;
181
218
  this.options.animationData = (0, _colorFilters.applyColorFilters)(_normalizeAnimationData(this.props.options.animationData, this.props.options.path), this.props.colorFilters);
182
219
  this.setLocationHref(this.options);
183
220
  this.anim = _lottieWeb["default"].loadAnimation(this.options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-lottie",
3
- "version": "2.4.2",
3
+ "version": "2.5.1",
4
4
  "funding": "https://opencollective.com/react-lottie",
5
5
  "description": "lottie animation view for React",
6
6
  "repository": {