react-lottie 2.0.1 → 2.2.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 +17 -0
- package/README.md +59 -4
- package/dist/colorFilters.js +260 -0
- package/dist/index.js +70 -7
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
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.2.0](https://github.com/chenqingspring/react-lottie/compare/v2.1.0...v2.2.0) (2026-11-05)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* 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))
|
|
10
|
+
|
|
11
|
+
## [2.1.0](https://github.com/chenqingspring/react-lottie/compare/v2.0.1...v2.1.0) (2026-11-05)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add an `isSubframe` prop that is passed to lottie-web's `setSubframe()`, so `enterFrame` can fire only on whole After Effects frames, e.g. to trigger audio exactly on frame boundaries ([#71](https://github.com/chenqingspring/react-lottie/issues/71))
|
|
16
|
+
* expose `goToAndStop(frame, isFrame)` and `goToAndPlay(frame, isFrame)` on the component instance, which makes it possible to drive an animation from scroll position or jump to an exact frame/marker through a `ref` ([#40](https://github.com/chenqingspring/react-lottie/issues/40))
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* keep a playing animation running when an unrelated prop changes; added a regression test for the state-change pause reported in [#43](https://github.com/chenqingspring/react-lottie/issues/43)
|
|
21
|
+
|
|
5
22
|
## [2.0.1](https://github.com/chenqingspring/react-lottie/compare/v2.0.0...v2.0.1) (2026-11-05)
|
|
6
23
|
|
|
7
24
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/js/react-lottie)
|
|
4
4
|
|
|
5
|
+
> **This project is fully maintained automatically by [Relay](https://get-relay.cc).**
|
|
6
|
+
|
|
5
7
|
## Demo
|
|
6
8
|
https://chenqingspring.github.io/react-lottie
|
|
7
9
|
|
|
@@ -31,10 +33,7 @@ Install through npm:
|
|
|
31
33
|
npm install --save react-lottie
|
|
32
34
|
```
|
|
33
35
|
|
|
34
|
-
> **Note:**
|
|
35
|
-
> [get-relay.cc](https://get-relay.cc).
|
|
36
|
-
>
|
|
37
|
-
> Found a bug or have a feature request? Open an issue on
|
|
36
|
+
> **Note:** Found a bug or have a feature request? Open an issue on
|
|
38
37
|
> [GitHub](https://github.com/chenqingspring/react-lottie/issues) and Relay
|
|
39
38
|
> will review and fix it.
|
|
40
39
|
|
|
@@ -188,6 +187,50 @@ A segment also has to be reachable with `loop`/`autoplay` as usual: with
|
|
|
188
187
|
`loop: false` and `autoplay: false` the range only plays once you flip
|
|
189
188
|
`isStopped` or pass a new `segments` value.
|
|
190
189
|
|
|
190
|
+
### Recolor an animation
|
|
191
|
+
|
|
192
|
+
`colorFilters` replaces the colors of fills, strokes and solid layers named by
|
|
193
|
+
a keypath, so one animation file can follow a theme (issue #144):
|
|
194
|
+
|
|
195
|
+
```jsx
|
|
196
|
+
<Lottie
|
|
197
|
+
options={defaultOptions}
|
|
198
|
+
colorFilters={[
|
|
199
|
+
{ keypath: 'Pin 1.Shape 2.Fill 1', color: '#4f46e5' },
|
|
200
|
+
{ keypath: 'Background', color: 'rgb(15, 23, 42)' },
|
|
201
|
+
]}
|
|
202
|
+
/>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
A keypath is the dot-separated name chain from the layer down to the fill,
|
|
206
|
+
stroke or solid, e.g. `layer.group.fill`. For a layer inside a precomposition,
|
|
207
|
+
the composition name can be included (`Precomp.InnerLayer.Shape.Fill 1`) or
|
|
208
|
+
omitted. `*` matches one level and `**` matches any number of levels; dots in
|
|
209
|
+
a name can be escaped with a backslash. Colors accept hex, `rgb()`/`rgba()`,
|
|
210
|
+
`hsl()`/`hsla()` and the basic CSS color names. Invalid filters are ignored.
|
|
211
|
+
|
|
212
|
+
The filters are applied to `options.animationData`. Changing them reloads the
|
|
213
|
+
animation, because lottie-web cannot recolor a loaded animation; the current
|
|
214
|
+
frame and paused/playing state are preserved. Animations loaded from
|
|
215
|
+
`options.path` are not recolored, since the JSON is fetched by lottie-web:
|
|
216
|
+
load the JSON yourself and pass it as `animationData` instead.
|
|
217
|
+
|
|
218
|
+
### Imperative control
|
|
219
|
+
|
|
220
|
+
A `ref` to the component exposes `play()`, `pause()`, `stop()`,
|
|
221
|
+
`playSegments()`, `goToAndStop(frame, isFrame)` and
|
|
222
|
+
`goToAndPlay(frame, isFrame)`, mirroring the lottie-web animation object. Use
|
|
223
|
+
`isFrame: true` for a frame number, or omit it to pass a time in seconds. This
|
|
224
|
+
is how you can sync an animation to scroll position (issue #40):
|
|
225
|
+
|
|
226
|
+
```jsx
|
|
227
|
+
const lottie = useRef(null);
|
|
228
|
+
|
|
229
|
+
const onScroll = (frame) => lottie.current.goToAndStop(frame, true);
|
|
230
|
+
|
|
231
|
+
<Lottie ref={lottie} options={defaultOptions} />
|
|
232
|
+
```
|
|
233
|
+
|
|
191
234
|
### props
|
|
192
235
|
The `<Lottie />` Component supports the following props:
|
|
193
236
|
|
|
@@ -207,6 +250,11 @@ the object representing the animation settings that will be instantiated by body
|
|
|
207
250
|
>
|
|
208
251
|
>**rendererSettings** *required*
|
|
209
252
|
|
|
253
|
+
**colorFilters** *optional* [default: `[]`]
|
|
254
|
+
|
|
255
|
+
Array of `{ keypath, color }` objects that replace the colors of fills,
|
|
256
|
+
strokes and solid layers. See [Recolor an animation](#recolor-an-animation).
|
|
257
|
+
|
|
210
258
|
**width** *optional* [default: `100%`]
|
|
211
259
|
|
|
212
260
|
pixel value for containers width.
|
|
@@ -235,6 +283,13 @@ values play in reverse at that rate.
|
|
|
235
283
|
`1` plays forward, `-1` plays backward. Changing it keeps the current frame,
|
|
236
284
|
which is what you want for hover-in / hover-out effects.
|
|
237
285
|
|
|
286
|
+
**isSubframe** *optional* [default: `true`]
|
|
287
|
+
|
|
288
|
+
Passed to lottie-web's `setSubframe()`. When `true`, frames are interpolated
|
|
289
|
+
for smooth playback. Set it to `false` to make the `enterFrame` event fire only
|
|
290
|
+
on whole After Effects frames, for example to trigger audio exactly on frame
|
|
291
|
+
boundaries (issue #71).
|
|
292
|
+
|
|
238
293
|
**segments** *optional* [default: none]
|
|
239
294
|
|
|
240
295
|
`[fromFrame, toFrame]` to play only part of the animation, e.g. `[45, 90]`.
|
|
@@ -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,20 +66,33 @@ 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();
|
|
80
94
|
this.setDirection();
|
|
95
|
+
this.setSubframe();
|
|
81
96
|
|
|
82
97
|
/* Apply a segment passed on mount. The component otherwise only reacts to
|
|
83
98
|
`segments` changes, so the animation used to play the whole timeline
|
|
@@ -100,17 +115,35 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
100
115
|
}, {
|
|
101
116
|
key: "componentDidUpdate",
|
|
102
117
|
value: function componentDidUpdate(prevProps) {
|
|
103
|
-
/* Recreate the animation handle if the data
|
|
104
|
-
Done here rather than in componentWillUpdate, which
|
|
105
|
-
and which is unsafe in concurrent rendering
|
|
106
|
-
|
|
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;
|
|
107
123
|
var pathChanged = this.options.path !== this.props.options.path;
|
|
108
|
-
|
|
124
|
+
var colorFiltersChanged = !(0, _colorFilters.colorFiltersAreEqual)(this.colorFilters, this.props.colorFilters);
|
|
125
|
+
if (dataChanged || pathChanged || colorFiltersChanged) {
|
|
126
|
+
var currentFrame = this.anim ? this.anim.currentFrame : 0;
|
|
127
|
+
var wasPaused = this.anim ? this.anim.isPaused : true;
|
|
128
|
+
/* lottie-web cannot recolor a loaded animation, so a filter change has to
|
|
129
|
+
load the data again. Only a filter-only change restores the frame. */
|
|
130
|
+
var colorOnlyChange = colorFiltersChanged && !dataChanged && !pathChanged;
|
|
109
131
|
this.deRegisterEvents(prevProps.eventListeners);
|
|
110
132
|
this.destroy();
|
|
133
|
+
this.animationData = this.props.options.animationData;
|
|
134
|
+
this.colorFilters = this.props.colorFilters;
|
|
111
135
|
this.options = _objectSpread(_objectSpread({}, this.options), this.props.options);
|
|
136
|
+
this.options.animationData = (0, _colorFilters.applyColorFilters)(this.props.options.animationData, this.props.colorFilters);
|
|
112
137
|
this.anim = _lottieWeb["default"].loadAnimation(this.options);
|
|
113
138
|
this.registerEvents(this.props.eventListeners);
|
|
139
|
+
|
|
140
|
+
/* Keep the frame and playback state so a theme change does not restart
|
|
141
|
+
a paused or half-played animation. */
|
|
142
|
+
if (colorOnlyChange) {
|
|
143
|
+
if (this.props.segments) this.anim.playSegments(this.props.segments, true);
|
|
144
|
+
if (Number.isFinite(currentFrame)) this.anim.goToAndStop(currentFrame, true);
|
|
145
|
+
if (!wasPaused) this.anim.play();
|
|
146
|
+
}
|
|
114
147
|
}
|
|
115
148
|
|
|
116
149
|
/* Only drive playback when a playback prop asks for it. Re-rendering used
|
|
@@ -132,6 +165,7 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
132
165
|
}
|
|
133
166
|
this.setSpeed();
|
|
134
167
|
this.setDirection();
|
|
168
|
+
this.setSubframe();
|
|
135
169
|
}
|
|
136
170
|
}, {
|
|
137
171
|
key: "componentWillUnmount",
|
|
@@ -151,11 +185,33 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
151
185
|
value: function setDirection() {
|
|
152
186
|
this.anim.setDirection(this.props.direction);
|
|
153
187
|
}
|
|
188
|
+
|
|
189
|
+
/* `isSubframe` mirrors lottie-web's setSubframe(): with it enabled (the
|
|
190
|
+
default) frames are interpolated, with it disabled `enterFrame` fires only
|
|
191
|
+
on whole After Effects frames (issue #71). */
|
|
192
|
+
}, {
|
|
193
|
+
key: "setSubframe",
|
|
194
|
+
value: function setSubframe() {
|
|
195
|
+
this.anim.setSubframe(this.props.isSubframe);
|
|
196
|
+
}
|
|
154
197
|
}, {
|
|
155
198
|
key: "play",
|
|
156
199
|
value: function play() {
|
|
157
200
|
this.anim.play();
|
|
158
201
|
}
|
|
202
|
+
|
|
203
|
+
/* Frame/marker control from a ref, e.g. to sync an animation to scroll.
|
|
204
|
+
`isFrame` false treats the value as a time in seconds (issue #40). */
|
|
205
|
+
}, {
|
|
206
|
+
key: "goToAndStop",
|
|
207
|
+
value: function goToAndStop(value, isFrame) {
|
|
208
|
+
this.anim.goToAndStop(value, isFrame);
|
|
209
|
+
}
|
|
210
|
+
}, {
|
|
211
|
+
key: "goToAndPlay",
|
|
212
|
+
value: function goToAndPlay(value, isFrame) {
|
|
213
|
+
this.anim.goToAndPlay(value, isFrame);
|
|
214
|
+
}
|
|
159
215
|
}, {
|
|
160
216
|
key: "playSegments",
|
|
161
217
|
value: function playSegments() {
|
|
@@ -251,6 +307,10 @@ var Lottie = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
|
251
307
|
Lottie.propTypes = {
|
|
252
308
|
eventListeners: _propTypes["default"].arrayOf(_propTypes["default"].object),
|
|
253
309
|
options: _propTypes["default"].object.isRequired,
|
|
310
|
+
colorFilters: _propTypes["default"].arrayOf(_propTypes["default"].shape({
|
|
311
|
+
keypath: _propTypes["default"].string.isRequired,
|
|
312
|
+
color: _propTypes["default"].string.isRequired
|
|
313
|
+
})),
|
|
254
314
|
height: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number]),
|
|
255
315
|
width: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number]),
|
|
256
316
|
isStopped: _propTypes["default"].bool,
|
|
@@ -258,6 +318,7 @@ Lottie.propTypes = {
|
|
|
258
318
|
speed: _propTypes["default"].number,
|
|
259
319
|
segments: _propTypes["default"].arrayOf(_propTypes["default"].number),
|
|
260
320
|
direction: _propTypes["default"].number,
|
|
321
|
+
isSubframe: _propTypes["default"].bool,
|
|
261
322
|
ariaRole: _propTypes["default"].string,
|
|
262
323
|
ariaLabel: _propTypes["default"].string,
|
|
263
324
|
isClickToPauseDisabled: _propTypes["default"].bool,
|
|
@@ -267,9 +328,11 @@ Lottie.propTypes = {
|
|
|
267
328
|
};
|
|
268
329
|
Lottie.defaultProps = {
|
|
269
330
|
eventListeners: [],
|
|
331
|
+
colorFilters: [],
|
|
270
332
|
isStopped: false,
|
|
271
333
|
isPaused: false,
|
|
272
334
|
speed: 1,
|
|
335
|
+
isSubframe: true,
|
|
273
336
|
ariaRole: 'button',
|
|
274
337
|
ariaLabel: 'animation',
|
|
275
338
|
isClickToPauseDisabled: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-lottie",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.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"
|