react-lottie 2.1.0 → 2.3.0
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 +12 -0
- package/README.md +123 -0
- package/dist/colorFilters.js +260 -0
- package/dist/index.js +58 -11
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
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.0](https://github.com/chenqingspring/react-lottie/compare/v2.2.0...v2.3.0) (2026-11-05)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* add an `as` prop to choose the element rendered around the animation, so `<Lottie as="span" />` can be nested in a `<p>` without invalid HTML. Changing `as` rebinds the animation to the new node and keeps the current frame ([#73](https://github.com/chenqingspring/react-lottie/issues/73))
|
|
10
|
+
|
|
11
|
+
## [2.2.0](https://github.com/chenqingspring/react-lottie/compare/v2.1.0...v2.2.0) (2026-11-05)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add a `colorFilters` prop that replaces the colors of fills, strokes and solid layers by keypath, mirroring lottie-react-native's API. The filters are applied to a copy of `animationData`; changing them reloads the animation while preserving the current frame and playback state ([#144](https://github.com/chenqingspring/react-lottie/issues/144))
|
|
16
|
+
|
|
5
17
|
## [2.1.0](https://github.com/chenqingspring/react-lottie/compare/v2.0.1...v2.1.0) (2026-11-05)
|
|
6
18
|
|
|
7
19
|
### Features
|
package/README.md
CHANGED
|
@@ -116,6 +116,74 @@ export default class LottieControl extends React.Component {
|
|
|
116
116
|
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
### Loading animation data without bloating the bundle
|
|
120
|
+
|
|
121
|
+
A static import like `import animationData from './pinjump.json'` (the example
|
|
122
|
+
above) makes the bundler inline the animation into the JavaScript chunk that
|
|
123
|
+
imports it. A 500 kB JSON file then becomes 500 kB of JavaScript the browser has
|
|
124
|
+
to download and parse before the page can run. The JSON is data, not code, so
|
|
125
|
+
keep it out of the main bundle:
|
|
126
|
+
|
|
127
|
+
**Serve it from `public/` and use `options.path`.** In Create React App, Vite
|
|
128
|
+
and Next.js, files in `public/` are copied to the build output as static
|
|
129
|
+
assets; they are not bundled into JavaScript. `lottie-web` fetches the file when
|
|
130
|
+
the component mounts, and the browser caches it separately from the app bundle:
|
|
131
|
+
|
|
132
|
+
```jsx
|
|
133
|
+
const defaultOptions = {
|
|
134
|
+
path: '/animations/pinjump.json', // relative to the deployed site root
|
|
135
|
+
loop: true,
|
|
136
|
+
autoplay: true,
|
|
137
|
+
rendererSettings: {
|
|
138
|
+
preserveAspectRatio: 'xMidYMid slice'
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
<Lottie options={defaultOptions} height={400} width={400} />
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Because lottie-web owns the fetch, `colorFilters` is not applied to animations
|
|
146
|
+
loaded this way — fetch the JSON yourself if you need to recolor it.
|
|
147
|
+
|
|
148
|
+
**Or load it on demand with a dynamic `import()`.** If you need the parsed data
|
|
149
|
+
in your own code, a dynamic import puts the JSON in a separate chunk that is
|
|
150
|
+
downloaded only when the component renders:
|
|
151
|
+
|
|
152
|
+
```jsx
|
|
153
|
+
import { useEffect, useState } from 'react';
|
|
154
|
+
import Lottie from 'react-lottie';
|
|
155
|
+
|
|
156
|
+
export default function Animation() {
|
|
157
|
+
const [animationData, setAnimationData] = useState(null);
|
|
158
|
+
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
let cancelled = false;
|
|
161
|
+
import('./pinjump.json').then(({ default: data }) => {
|
|
162
|
+
if (!cancelled) setAnimationData(data);
|
|
163
|
+
});
|
|
164
|
+
return () => { cancelled = true; };
|
|
165
|
+
}, []);
|
|
166
|
+
|
|
167
|
+
if (!animationData) return null;
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<Lottie
|
|
171
|
+
options={{ animationData, loop: true, autoplay: true }}
|
|
172
|
+
height={400}
|
|
173
|
+
width={400}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Rendering `<Animation />` lazily (for example when it scrolls into view) defers
|
|
180
|
+
even that chunk until it is needed. The same idea works with `React.lazy` around
|
|
181
|
+
a component that imports the JSON.
|
|
182
|
+
|
|
183
|
+
Finally, the file itself can usually get smaller: export at a lower precision,
|
|
184
|
+
remove unused layers and precompositions, avoid embedded raster images, and let
|
|
185
|
+
gzip/Brotli compress the JSON. LottieFiles' optimizer automates the first steps.
|
|
186
|
+
|
|
119
187
|
### Play on hover
|
|
120
188
|
|
|
121
189
|
`eventListeners` takes lottie-web animation events (`complete`, `loopComplete`,
|
|
@@ -187,6 +255,49 @@ A segment also has to be reachable with `loop`/`autoplay` as usual: with
|
|
|
187
255
|
`loop: false` and `autoplay: false` the range only plays once you flip
|
|
188
256
|
`isStopped` or pass a new `segments` value.
|
|
189
257
|
|
|
258
|
+
### Recolor an animation
|
|
259
|
+
|
|
260
|
+
`colorFilters` replaces the colors of fills, strokes and solid layers named by
|
|
261
|
+
a keypath, so one animation file can follow a theme (issue #144):
|
|
262
|
+
|
|
263
|
+
```jsx
|
|
264
|
+
<Lottie
|
|
265
|
+
options={defaultOptions}
|
|
266
|
+
colorFilters={[
|
|
267
|
+
{ keypath: 'Pin 1.Shape 2.Fill 1', color: '#4f46e5' },
|
|
268
|
+
{ keypath: 'Background', color: 'rgb(15, 23, 42)' },
|
|
269
|
+
]}
|
|
270
|
+
/>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
A keypath is the dot-separated name chain from the layer down to the fill,
|
|
274
|
+
stroke or solid, e.g. `layer.group.fill`. For a layer inside a precomposition,
|
|
275
|
+
the composition name can be included (`Precomp.InnerLayer.Shape.Fill 1`) or
|
|
276
|
+
omitted. `*` matches one level and `**` matches any number of levels; dots in
|
|
277
|
+
a name can be escaped with a backslash. Colors accept hex, `rgb()`/`rgba()`,
|
|
278
|
+
`hsl()`/`hsla()` and the basic CSS color names. Invalid filters are ignored.
|
|
279
|
+
|
|
280
|
+
The filters are applied to `options.animationData`. Changing them reloads the
|
|
281
|
+
animation, because lottie-web cannot recolor a loaded animation; the current
|
|
282
|
+
frame and paused/playing state are preserved. Animations loaded from
|
|
283
|
+
`options.path` are not recolored, since the JSON is fetched by lottie-web:
|
|
284
|
+
load the JSON yourself and pass it as `animationData` instead.
|
|
285
|
+
|
|
286
|
+
### Render inline inside a paragraph
|
|
287
|
+
|
|
288
|
+
The container is a `div` by default, which is not a valid child of `<p>`. Pass
|
|
289
|
+
`as` to choose the element (issue #73):
|
|
290
|
+
|
|
291
|
+
```jsx
|
|
292
|
+
<p>
|
|
293
|
+
Text <Lottie as="span" options={defaultOptions} ariaRole="img" /> more text
|
|
294
|
+
</p>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`as` accepts a tag name or a component that forwards its `ref` to the DOM node
|
|
298
|
+
lottie-web should render into. Changing `as` reloads the animation into the new
|
|
299
|
+
element and keeps the current frame.
|
|
300
|
+
|
|
190
301
|
### Imperative control
|
|
191
302
|
|
|
192
303
|
A `ref` to the component exposes `play()`, `pause()`, `stop()`,
|
|
@@ -222,6 +333,11 @@ the object representing the animation settings that will be instantiated by body
|
|
|
222
333
|
>
|
|
223
334
|
>**rendererSettings** *required*
|
|
224
335
|
|
|
336
|
+
**colorFilters** *optional* [default: `[]`]
|
|
337
|
+
|
|
338
|
+
Array of `{ keypath, color }` objects that replace the colors of fills,
|
|
339
|
+
strokes and solid layers. See [Recolor an animation](#recolor-an-animation).
|
|
340
|
+
|
|
225
341
|
**width** *optional* [default: `100%`]
|
|
226
342
|
|
|
227
343
|
pixel value for containers width.
|
|
@@ -303,6 +419,13 @@ layout defaults. Use it to override `overflow` or `margin`.
|
|
|
303
419
|
Tab order of the rendered container. Pass `-1` for a purely presentational
|
|
304
420
|
animation that should not receive keyboard focus.
|
|
305
421
|
|
|
422
|
+
**as** *optional* [default: `div`]
|
|
423
|
+
|
|
424
|
+
Element or component rendered as the animation container. Use `as="span"` when
|
|
425
|
+
`<Lottie />` is a child of `<p>`, where a `div` is invalid HTML (issue #73). A
|
|
426
|
+
custom component must forward its `ref` to a DOM element. See
|
|
427
|
+
[Render inline inside a paragraph](#render-inline-inside-a-paragraph).
|
|
428
|
+
|
|
306
429
|
## Related Projects
|
|
307
430
|
|
|
308
431
|
* [lottie-web](https://github.com/airbnb/lottie-web) react-lottie is a wrapper of lottie-web
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.parseColor = exports.colorFiltersAreEqual = exports.applyColorFilters = void 0;
|
|
7
|
+
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
8
|
+
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
9
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
10
|
+
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
11
|
+
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
|
12
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
13
|
+
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); }
|
|
14
|
+
/* `colorFilters` mirrors lottie-react-native: every filter names a keypath in
|
|
15
|
+
the animation (layer → group → shape item) and the color to replace there.
|
|
16
|
+
lottie-web has no public API for recoloring a loaded animation, so the
|
|
17
|
+
animation JSON is cloned with the matching colors replaced and the animation
|
|
18
|
+
is loaded again. The caller's animationData is never mutated, because it may
|
|
19
|
+
be a frozen ESM import (#144). */
|
|
20
|
+
|
|
21
|
+
var NAMED_COLORS = {
|
|
22
|
+
aqua: [0, 255, 255],
|
|
23
|
+
black: [0, 0, 0],
|
|
24
|
+
blue: [0, 0, 255],
|
|
25
|
+
cyan: [0, 255, 255],
|
|
26
|
+
fuchsia: [255, 0, 255],
|
|
27
|
+
gray: [128, 128, 128],
|
|
28
|
+
green: [0, 128, 0],
|
|
29
|
+
grey: [128, 128, 128],
|
|
30
|
+
lime: [0, 255, 0],
|
|
31
|
+
magenta: [255, 0, 255],
|
|
32
|
+
maroon: [128, 0, 0],
|
|
33
|
+
navy: [0, 0, 128],
|
|
34
|
+
olive: [128, 128, 0],
|
|
35
|
+
purple: [128, 0, 128],
|
|
36
|
+
red: [255, 0, 0],
|
|
37
|
+
silver: [192, 192, 192],
|
|
38
|
+
teal: [0, 128, 128],
|
|
39
|
+
white: [255, 255, 255],
|
|
40
|
+
yellow: [255, 255, 0]
|
|
41
|
+
};
|
|
42
|
+
var clampChannel = function clampChannel(value) {
|
|
43
|
+
return Math.min(255, Math.max(0, value));
|
|
44
|
+
};
|
|
45
|
+
var toFractions = function toFractions(channels) {
|
|
46
|
+
return channels.map(function (channel) {
|
|
47
|
+
return clampChannel(channel) / 255;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
var parseHex = function parseHex(value) {
|
|
51
|
+
var match = /^#?([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(value);
|
|
52
|
+
if (!match) return null;
|
|
53
|
+
var digits = match[1].length <= 4 ? match[1].split('').map(function (digit) {
|
|
54
|
+
return digit + digit;
|
|
55
|
+
}) : match[1].match(/.{2}/g);
|
|
56
|
+
|
|
57
|
+
/* The fourth value is alpha; lottie keeps the animation's own alpha, so it
|
|
58
|
+
is ignored here. */
|
|
59
|
+
return toFractions(digits.slice(0, 3).map(function (digit) {
|
|
60
|
+
return parseInt(digit, 16);
|
|
61
|
+
}));
|
|
62
|
+
};
|
|
63
|
+
var parseChannel = function parseChannel(token) {
|
|
64
|
+
var value = parseFloat(token);
|
|
65
|
+
if (Number.isNaN(value)) return null;
|
|
66
|
+
return token.endsWith('%') ? value * 255 / 100 : value;
|
|
67
|
+
};
|
|
68
|
+
var parseRgb = function parseRgb(value) {
|
|
69
|
+
var match = /^rgba?\(\s*([^)]+)\)$/i.exec(value);
|
|
70
|
+
if (!match) return null;
|
|
71
|
+
var channels = match[1].split(/[\s,/]+/).filter(Boolean).slice(0, 3).map(parseChannel);
|
|
72
|
+
if (channels.length !== 3 || channels.some(function (channel) {
|
|
73
|
+
return channel === null;
|
|
74
|
+
})) return null;
|
|
75
|
+
return toFractions(channels);
|
|
76
|
+
};
|
|
77
|
+
var hueToRgb = function hueToRgb(p, q, t) {
|
|
78
|
+
var hue = t;
|
|
79
|
+
if (hue < 0) hue += 1;
|
|
80
|
+
if (hue > 1) hue -= 1;
|
|
81
|
+
if (hue < 1 / 6) return p + (q - p) * 6 * hue;
|
|
82
|
+
if (hue < 1 / 2) return q;
|
|
83
|
+
if (hue < 2 / 3) return p + (q - p) * (2 / 3 - hue) * 6;
|
|
84
|
+
return p;
|
|
85
|
+
};
|
|
86
|
+
var parseHsl = function parseHsl(value) {
|
|
87
|
+
var match = /^hsla?\(\s*([^)]+)\)$/i.exec(value);
|
|
88
|
+
if (!match) return null;
|
|
89
|
+
var parts = match[1].split(/[\s,/]+/).filter(Boolean);
|
|
90
|
+
if (parts.length < 3) return null;
|
|
91
|
+
var hue = (parseFloat(parts[0]) % 360 + 360) % 360 / 360;
|
|
92
|
+
var saturation = parseFloat(parts[1]) / 100;
|
|
93
|
+
var lightness = parseFloat(parts[2]) / 100;
|
|
94
|
+
if (Number.isNaN(hue) || Number.isNaN(saturation) || Number.isNaN(lightness)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (saturation === 0) return [lightness, lightness, lightness];
|
|
98
|
+
var q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation;
|
|
99
|
+
var p = 2 * lightness - q;
|
|
100
|
+
return [hueToRgb(p, q, hue + 1 / 3), hueToRgb(p, q, hue), hueToRgb(p, q, hue - 1 / 3)];
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/* Accepts the CSS color forms an app is likely to use for theming. Anything
|
|
104
|
+
else (including CSS variables) is ignored rather than crashing the render. */
|
|
105
|
+
var parseColor = exports.parseColor = function parseColor(value) {
|
|
106
|
+
if (typeof value !== 'string') return null;
|
|
107
|
+
var color = value.trim().toLowerCase();
|
|
108
|
+
if (NAMED_COLORS[color]) return toFractions(NAMED_COLORS[color]);
|
|
109
|
+
return parseHex(color) || parseRgb(color) || parseHsl(color);
|
|
110
|
+
};
|
|
111
|
+
var setColorArray = function setColorArray(target, color) {
|
|
112
|
+
if (!Array.isArray(target) || target.length < 3) return false;
|
|
113
|
+
target[0] = color[0];
|
|
114
|
+
target[1] = color[1];
|
|
115
|
+
target[2] = color[2];
|
|
116
|
+
return true;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/* A color property is either static (`k: [r, g, b, a]`) or animated
|
|
120
|
+
(`k: [{ s: [...], e: [...] }, ...]`). Replace both so an animated fill stays
|
|
121
|
+
recolored throughout its keyframes. */
|
|
122
|
+
var setColorProperty = function setColorProperty(property, color) {
|
|
123
|
+
if (!property || !Array.isArray(property.k) || property.k.length === 0) return false;
|
|
124
|
+
if (typeof property.k[0] === 'number') {
|
|
125
|
+
return setColorArray(property.k, color);
|
|
126
|
+
}
|
|
127
|
+
var applied = false;
|
|
128
|
+
property.k.forEach(function (keyframe) {
|
|
129
|
+
if (!keyframe || _typeof(keyframe) !== 'object') return;
|
|
130
|
+
if (setColorArray(keyframe.s, color)) applied = true;
|
|
131
|
+
if (setColorArray(keyframe.e, color)) applied = true;
|
|
132
|
+
});
|
|
133
|
+
return applied;
|
|
134
|
+
};
|
|
135
|
+
var toHex = function toHex(color) {
|
|
136
|
+
return "#".concat(color.map(function (channel) {
|
|
137
|
+
return Math.round(channel * 255).toString(16).padStart(2, '0');
|
|
138
|
+
}).join(''));
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/* Solid layers store their color as a hex string instead of a color array. */
|
|
142
|
+
var setSolidColor = function setSolidColor(layer, color) {
|
|
143
|
+
if (typeof layer.sc === 'string') {
|
|
144
|
+
layer.sc = toHex(color);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return setColorArray(layer.sc, color);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/* Split on unescaped dots so layer names that contain a dot (`icon.dark`) can
|
|
151
|
+
be targeted with `icon\.dark`. */
|
|
152
|
+
var splitKeypath = function splitKeypath(keypath) {
|
|
153
|
+
var parts = [];
|
|
154
|
+
var current = '';
|
|
155
|
+
for (var index = 0; index < keypath.length; index += 1) {
|
|
156
|
+
var character = keypath[index];
|
|
157
|
+
if (character === '\\' && index + 1 < keypath.length) {
|
|
158
|
+
current += keypath[index + 1];
|
|
159
|
+
index += 1;
|
|
160
|
+
} else if (character === '.') {
|
|
161
|
+
parts.push(current);
|
|
162
|
+
current = '';
|
|
163
|
+
} else {
|
|
164
|
+
current += character;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
parts.push(current);
|
|
168
|
+
return parts;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/* `*` matches one level and `**` matches any number of levels, matching the
|
|
172
|
+
keypath conventions used by lottie-ios. */
|
|
173
|
+
var _matchesKeypath = function matchesKeypath(pattern, path) {
|
|
174
|
+
if (pattern.length === 0) return path.length === 0;
|
|
175
|
+
if (pattern[0] === '**') {
|
|
176
|
+
for (var index = 0; index <= path.length; index += 1) {
|
|
177
|
+
if (_matchesKeypath(pattern.slice(1), path.slice(index))) return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (path.length === 0) return false;
|
|
182
|
+
if (pattern[0] !== '*' && pattern[0] !== path[0]) return false;
|
|
183
|
+
return _matchesKeypath(pattern.slice(1), path.slice(1));
|
|
184
|
+
};
|
|
185
|
+
var colorFiltersAreEqual = exports.colorFiltersAreEqual = function colorFiltersAreEqual(first, second) {
|
|
186
|
+
if (first === second) return true;
|
|
187
|
+
if (!Array.isArray(first) || !Array.isArray(second) || first.length !== second.length) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return first.every(function (filter, index) {
|
|
191
|
+
var other = second[index];
|
|
192
|
+
return Boolean(filter && other) && filter.keypath === other.keypath && filter.color === other.color;
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
var applyColorFilters = exports.applyColorFilters = function applyColorFilters(animationData, colorFilters) {
|
|
196
|
+
if (!animationData || !Array.isArray(colorFilters) || colorFilters.length === 0) {
|
|
197
|
+
return animationData;
|
|
198
|
+
}
|
|
199
|
+
var filters = colorFilters.filter(function (filter) {
|
|
200
|
+
return filter && typeof filter.keypath === 'string';
|
|
201
|
+
}).map(function (filter) {
|
|
202
|
+
return {
|
|
203
|
+
parts: splitKeypath(filter.keypath.trim()),
|
|
204
|
+
color: parseColor(filter.color)
|
|
205
|
+
};
|
|
206
|
+
}).filter(function (filter) {
|
|
207
|
+
return filter.color;
|
|
208
|
+
});
|
|
209
|
+
if (filters.length === 0) return animationData;
|
|
210
|
+
var data = JSON.parse(JSON.stringify(animationData));
|
|
211
|
+
var assets = new Map();
|
|
212
|
+
if (Array.isArray(data.assets)) {
|
|
213
|
+
data.assets.forEach(function (asset) {
|
|
214
|
+
if (asset && asset.id) assets.set(asset.id, asset);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
var applied = false;
|
|
218
|
+
var recolor = function recolor(node, path) {
|
|
219
|
+
filters.forEach(function (filter) {
|
|
220
|
+
if (!_matchesKeypath(filter.parts, path)) return;
|
|
221
|
+
if (node.c && setColorProperty(node.c, filter.color)) applied = true;
|
|
222
|
+
if (node.ty === 1 && setSolidColor(node, filter.color)) applied = true;
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
var _walkShapes = function walkShapes(shapes, path) {
|
|
226
|
+
if (!Array.isArray(shapes)) return;
|
|
227
|
+
shapes.forEach(function (shape) {
|
|
228
|
+
if (!shape || _typeof(shape) !== 'object') return;
|
|
229
|
+
var shapePath = shape.nm ? [].concat(_toConsumableArray(path), [shape.nm]) : path;
|
|
230
|
+
recolor(shape, shapePath);
|
|
231
|
+
_walkShapes(shape.it, shapePath);
|
|
232
|
+
_walkShapes(shape.shapes, shapePath);
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/* Precompositions are walked twice: once from the asset (so a keypath can
|
|
237
|
+
start at the inner layer name) and once from each layer that references
|
|
238
|
+
them (so `Precomp.InnerLayer...` also works). `refStack` prevents a
|
|
239
|
+
self-referencing composition from recursing forever. */
|
|
240
|
+
var _walkLayers = function walkLayers(layers, prefix, refStack) {
|
|
241
|
+
if (!Array.isArray(layers)) return;
|
|
242
|
+
layers.forEach(function (layer) {
|
|
243
|
+
if (!layer || _typeof(layer) !== 'object') return;
|
|
244
|
+
var path = layer.nm ? [].concat(_toConsumableArray(prefix), [layer.nm]) : prefix;
|
|
245
|
+
recolor(layer, path);
|
|
246
|
+
_walkShapes(layer.shapes, path);
|
|
247
|
+
var asset = layer.refId ? assets.get(layer.refId) : null;
|
|
248
|
+
if (layer.ty === 0 && asset && !refStack.includes(layer.refId)) {
|
|
249
|
+
_walkLayers(asset.layers, path, [].concat(_toConsumableArray(refStack), [layer.refId]));
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
_walkLayers(data.layers, [], []);
|
|
254
|
+
if (Array.isArray(data.assets)) {
|
|
255
|
+
data.assets.forEach(function (asset) {
|
|
256
|
+
return _walkLayers(asset.layers, [], []);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return applied ? data : animationData;
|
|
260
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ exports["default"] = void 0;
|
|
|
7
7
|
var _react = _interopRequireDefault(require("react"));
|
|
8
8
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
9
|
var _lottieWeb = _interopRequireDefault(require("lottie-web"));
|
|
10
|
+
var _colorFilters = require("./colorFilters");
|
|
10
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
11
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); }
|
|
12
13
|
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; }
|
|
@@ -56,7 +57,8 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
56
57
|
value: function componentDidMount() {
|
|
57
58
|
var _this$props = this.props,
|
|
58
59
|
options = _this$props.options,
|
|
59
|
-
eventListeners = _this$props.eventListeners
|
|
60
|
+
eventListeners = _this$props.eventListeners,
|
|
61
|
+
colorFilters = _this$props.colorFilters;
|
|
60
62
|
var loop = options.loop,
|
|
61
63
|
autoplay = options.autoplay,
|
|
62
64
|
animationData = options.animationData,
|
|
@@ -64,16 +66,28 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
64
66
|
renderer = _options$renderer === void 0 ? 'svg' : _options$renderer,
|
|
65
67
|
rendererSettings = options.rendererSettings,
|
|
66
68
|
segments = options.segments;
|
|
69
|
+
|
|
70
|
+
/* Keep the caller's data and filter list around so a later update can tell
|
|
71
|
+
whether either actually changed. */
|
|
72
|
+
this.animationData = animationData;
|
|
73
|
+
this.colorFilters = colorFilters;
|
|
74
|
+
var filteredAnimationData = (0, _colorFilters.applyColorFilters)(animationData, colorFilters);
|
|
67
75
|
this.options = {
|
|
68
76
|
container: this.el,
|
|
69
77
|
renderer: renderer,
|
|
70
78
|
loop: loop !== false,
|
|
71
79
|
autoplay: autoplay !== false,
|
|
72
80
|
segments: segments !== false,
|
|
73
|
-
animationData:
|
|
81
|
+
animationData: filteredAnimationData,
|
|
74
82
|
rendererSettings: rendererSettings
|
|
75
83
|
};
|
|
76
84
|
this.options = _objectSpread(_objectSpread({}, this.options), options);
|
|
85
|
+
/* Spreading `options` restored the unfiltered data; use the filtered copy
|
|
86
|
+
after the merge. */
|
|
87
|
+
this.options.animationData = filteredAnimationData;
|
|
88
|
+
if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'production' && Array.isArray(colorFilters) && colorFilters.length > 0 && !animationData) {
|
|
89
|
+
console.warn('[react-lottie] colorFilters requires options.animationData; ' + 'animations loaded from options.path are not recolored.');
|
|
90
|
+
}
|
|
77
91
|
this.anim = _lottieWeb["default"].loadAnimation(this.options);
|
|
78
92
|
this.registerEvents(eventListeners);
|
|
79
93
|
this.setSpeed();
|
|
@@ -101,17 +115,42 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
101
115
|
}, {
|
|
102
116
|
key: "componentDidUpdate",
|
|
103
117
|
value: function componentDidUpdate(prevProps) {
|
|
104
|
-
/* Recreate the animation handle if the data
|
|
105
|
-
Done here rather than in componentWillUpdate, which
|
|
106
|
-
and which is unsafe in concurrent rendering
|
|
107
|
-
|
|
118
|
+
/* Recreate the animation handle if the data, its remote path or the color
|
|
119
|
+
filters changed. Done here rather than in componentWillUpdate, which
|
|
120
|
+
React has deprecated and which is unsafe in concurrent rendering
|
|
121
|
+
(#77, #100, #119). */
|
|
122
|
+
var dataChanged = this.animationData !== this.props.options.animationData;
|
|
108
123
|
var pathChanged = this.options.path !== this.props.options.path;
|
|
109
|
-
|
|
124
|
+
var colorFiltersChanged = !(0, _colorFilters.colorFiltersAreEqual)(this.colorFilters, this.props.colorFilters);
|
|
125
|
+
/* The wrapper element is part of lottie-web's configuration, so a new
|
|
126
|
+
element means the animation has to be bound to the new node. */
|
|
127
|
+
var containerChanged = prevProps.as !== this.props.as;
|
|
128
|
+
if (dataChanged || pathChanged || colorFiltersChanged || containerChanged) {
|
|
129
|
+
var currentFrame = this.anim ? this.anim.currentFrame : 0;
|
|
130
|
+
var wasPaused = this.anim ? this.anim.isPaused : true;
|
|
131
|
+
/* lottie-web cannot recolor a loaded animation and cannot move one to
|
|
132
|
+
another node, so those changes load the same data again. Only then is
|
|
133
|
+
it worth restoring the frame and playback state. */
|
|
134
|
+
var reloadInPlace = !dataChanged && !pathChanged;
|
|
110
135
|
this.deRegisterEvents(prevProps.eventListeners);
|
|
111
136
|
this.destroy();
|
|
137
|
+
this.animationData = this.props.options.animationData;
|
|
138
|
+
this.colorFilters = this.props.colorFilters;
|
|
112
139
|
this.options = _objectSpread(_objectSpread({}, this.options), this.props.options);
|
|
140
|
+
/* `options` is caller-owned and knows nothing about the wrapper, so the
|
|
141
|
+
ref (already pointing at the newly rendered node) wins. */
|
|
142
|
+
this.options.container = this.el;
|
|
143
|
+
this.options.animationData = (0, _colorFilters.applyColorFilters)(this.props.options.animationData, this.props.colorFilters);
|
|
113
144
|
this.anim = _lottieWeb["default"].loadAnimation(this.options);
|
|
114
145
|
this.registerEvents(this.props.eventListeners);
|
|
146
|
+
|
|
147
|
+
/* Keep the frame and playback state so a theme or element change does
|
|
148
|
+
not restart a paused or half-played animation. */
|
|
149
|
+
if (reloadInPlace) {
|
|
150
|
+
if (this.props.segments) this.anim.playSegments(this.props.segments, true);
|
|
151
|
+
if (Number.isFinite(currentFrame)) this.anim.goToAndStop(currentFrame, true);
|
|
152
|
+
if (!wasPaused) this.anim.play();
|
|
153
|
+
}
|
|
115
154
|
}
|
|
116
155
|
|
|
117
156
|
/* Only drive playback when a playback prop asks for it. Re-rendering used
|
|
@@ -232,7 +271,8 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
232
271
|
ariaLabel = _this$props2.ariaLabel,
|
|
233
272
|
isClickToPauseDisabled = _this$props2.isClickToPauseDisabled,
|
|
234
273
|
title = _this$props2.title,
|
|
235
|
-
tabIndex = _this$props2.tabIndex
|
|
274
|
+
tabIndex = _this$props2.tabIndex,
|
|
275
|
+
Container = _this$props2.as;
|
|
236
276
|
var getSize = function getSize(initial) {
|
|
237
277
|
var size;
|
|
238
278
|
if (typeof initial === 'number') {
|
|
@@ -257,7 +297,7 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
257
297
|
// Click-to-pause is intentional and role="button" is set; keyboard control
|
|
258
298
|
// is not part of the API yet.
|
|
259
299
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
|
|
260
|
-
_react["default"].createElement(
|
|
300
|
+
_react["default"].createElement(Container, {
|
|
261
301
|
ref: function ref(c) {
|
|
262
302
|
_this4.el = c;
|
|
263
303
|
},
|
|
@@ -275,6 +315,10 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
275
315
|
Lottie.propTypes = {
|
|
276
316
|
eventListeners: _propTypes["default"].arrayOf(_propTypes["default"].object),
|
|
277
317
|
options: _propTypes["default"].object.isRequired,
|
|
318
|
+
colorFilters: _propTypes["default"].arrayOf(_propTypes["default"].shape({
|
|
319
|
+
keypath: _propTypes["default"].string.isRequired,
|
|
320
|
+
color: _propTypes["default"].string.isRequired
|
|
321
|
+
})),
|
|
278
322
|
height: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number]),
|
|
279
323
|
width: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number]),
|
|
280
324
|
isStopped: _propTypes["default"].bool,
|
|
@@ -288,10 +332,12 @@ Lottie.propTypes = {
|
|
|
288
332
|
isClickToPauseDisabled: _propTypes["default"].bool,
|
|
289
333
|
title: _propTypes["default"].string,
|
|
290
334
|
style: _propTypes["default"].object,
|
|
291
|
-
tabIndex: _propTypes["default"].number
|
|
335
|
+
tabIndex: _propTypes["default"].number,
|
|
336
|
+
as: _propTypes["default"].elementType
|
|
292
337
|
};
|
|
293
338
|
Lottie.defaultProps = {
|
|
294
339
|
eventListeners: [],
|
|
340
|
+
colorFilters: [],
|
|
295
341
|
isStopped: false,
|
|
296
342
|
isPaused: false,
|
|
297
343
|
speed: 1,
|
|
@@ -300,5 +346,6 @@ Lottie.defaultProps = {
|
|
|
300
346
|
ariaLabel: 'animation',
|
|
301
347
|
isClickToPauseDisabled: false,
|
|
302
348
|
title: '',
|
|
303
|
-
tabIndex: 0
|
|
349
|
+
tabIndex: 0,
|
|
350
|
+
as: 'div'
|
|
304
351
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-lottie",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"funding": "https://opencollective.com/react-lottie",
|
|
5
5
|
"description": "lottie animation view for React",
|
|
6
6
|
"repository": {
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"react": ">=16.8.0"
|
|
52
52
|
},
|
|
53
53
|
"overrides": {
|
|
54
|
-
"cheerio": "1.0.0-rc.12"
|
|
54
|
+
"cheerio": "1.0.0-rc.12",
|
|
55
|
+
"minimatch@^10.2.2": "11.0.4"
|
|
55
56
|
},
|
|
56
57
|
"resolutions": {
|
|
57
58
|
"cheerio": "1.0.0-rc.12"
|