@pixodesk/svg-animator-rn 1.0.35 → 1.0.40
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/README.md +47 -55
- package/dist/index.cjs +113 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -237
- package/dist/index.d.ts +31 -237
- package/dist/index.js +100 -81
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/PixodeskSvgAnimator.tsx +158 -154
- package/src/PxRnErrorBoundary.tsx +13 -4
- package/src/PxRnMatrix.test.ts +2 -2
- package/src/PxRnMatrix.ts +1 -1
- package/src/PxRnPropNames.ts +4 -3
- package/src/PxRnRender.tsx +21 -27
- package/src/PxRnSafety.ts +3 -1
- package/src/PxRnTracks.test.ts +12 -12
- package/src/PxRnTracks.ts +10 -12
- package/src/PxRnTypeMap.ts +1 -0
- package/src/index.ts +0 -9
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ document says so.
|
|
|
51
51
|
|
|
52
52
|
```tsx
|
|
53
53
|
// Play once when a screen opens, then hold the last frame
|
|
54
|
-
<PixodeskSvgAnimator doc={doc} autoplay iterations={1}
|
|
54
|
+
<PixodeskSvgAnimator doc={doc} autoplay iterations={1} timeline={{ fillMode: 'forwards' }} />
|
|
55
55
|
|
|
56
56
|
// Loop forever regardless of what the document says
|
|
57
57
|
<PixodeskSvgAnimator doc={doc} autoplay iterations="infinite" />
|
|
@@ -166,6 +166,7 @@ const [time, setTime] = useState(0);
|
|
|
166
166
|
|
|
167
167
|
## Props
|
|
168
168
|
|
|
169
|
+
<!-- px-check props PixodeskSvgAnimatorProps pkg=rn -->
|
|
169
170
|
| Prop | Type | Description |
|
|
170
171
|
|---|---|---|
|
|
171
172
|
| `doc` | `PxAnimatedSvgDocument` | The animation document to render (required) |
|
|
@@ -175,18 +176,22 @@ const [time, setTime] = useState(0);
|
|
|
175
176
|
| `apiRef` | `RefObject<RnAnimatorApi>` | Ref for imperative control |
|
|
176
177
|
| `progress` | `number` | show the frame at this position in the whole timeline (duration × iterations): `0` is the first frame, `0.5` the middle, `1` the last |
|
|
177
178
|
| `time` | `number` | show the frame at that time, in milliseconds from the start |
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `duration` | `number` | Shortcut for `
|
|
181
|
-
| `delay` | `number` | Shortcut for `
|
|
182
|
-
| `iterations` | `number \| 'infinite'` | Shortcut for `
|
|
183
|
-
| `startOn` | `
|
|
179
|
+
| `timeline` | `object \| string` | per-instance override of the document's `timeline` block, deep-merged over it — same shape as the file; `null` at any slot deletes that key. A JSON string is accepted too. See [Playback overrides](#playback-overrides) |
|
|
180
|
+
| `resetTimeline` | `boolean` | ignore the document's own timeline and start from the player's default timeline, with `timeline` on top |
|
|
181
|
+
| `duration` | `number` | Shortcut for `timeline.duration` (ms) |
|
|
182
|
+
| `delay` | `number` | Shortcut for `timeline.delay` (ms) |
|
|
183
|
+
| `iterations` | `number \| 'infinite'` | Shortcut for `timeline.iterations` |
|
|
184
|
+
| `startOn` | `PxStartOn` | Shortcut for `timeline.trigger.startOn`. `mouseOver` has no touch equivalent and is ignored |
|
|
184
185
|
| `onPlay` | `() => void` | Called on play/resume |
|
|
185
186
|
| `onPause` | `() => void` | Called on pause |
|
|
186
|
-
| `onFinish` | `() => void` | Called
|
|
187
|
+
| `onFinish` | `() => void` | Called when the animation reaches its end — every iteration played, or `finish()` was called |
|
|
187
188
|
| `onCancel` | `() => void` | Called on cancel |
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
189
|
+
| `onRemove` | `() => void` | Called when the animator is thrown away — the component unmounted, or a new `doc` replaced it |
|
|
190
|
+
| `onStop` | `() => void` | Called whenever playback halts (pause / cancel / finish / remove) |
|
|
191
|
+
| `onError` | `(diagnostic) => void` | this instance will not play — the document could not be compiled or rendered; `fallback` shows instead. `diagnostic.error` is the Error, `diagnostic.detail.componentStack` when the error boundary caught it |
|
|
192
|
+
| `onWarn` | `(diagnostic) => void` | it plays, but something was ignored, degraded or misspelled; without it → `console.warn` |
|
|
193
|
+
| `muteWarn` | `boolean` | switch the `console.warn` fallback off — for when you know the player has something to say about this document and are prepared to tolerate it. `onWarn`, if you gave it, still fires: mute is about the console, not about you |
|
|
194
|
+
| `muteError` | `boolean` | the same switch for `console.error` |
|
|
190
195
|
| `fallback` | `(error) => ReactElement \| null` | Rendered in place of a failed animation (default: nothing) |
|
|
191
196
|
|
|
192
197
|
With none of `autoplay` / `play` / `pause` / `progress` / `time` set, the component
|
|
@@ -196,14 +201,14 @@ renders the animation statically (initial state, no playback).
|
|
|
196
201
|
|
|
197
202
|
The component never throws for a bad document. Compilation and rendering both
|
|
198
203
|
run inside `try`/`catch`, and the rendered tree sits behind an error boundary,
|
|
199
|
-
so a failure is reported through `onError`
|
|
200
|
-
surrounding screen keeps working.
|
|
204
|
+
so a failure is reported through `onError` — the shared diagnostic (`diagnostic.error` is the
|
|
205
|
+
Error), exactly as on the web — and shows `fallback` while the surrounding screen keeps working.
|
|
201
206
|
|
|
202
207
|
```tsx
|
|
203
208
|
<PixodeskSvgAnimator
|
|
204
209
|
doc={doc}
|
|
205
210
|
autoplay
|
|
206
|
-
onError={
|
|
211
|
+
onError={d => console.warn('animation failed:', d.message)}
|
|
207
212
|
fallback={() => <Text>could not play this animation</Text>}
|
|
208
213
|
/>
|
|
209
214
|
```
|
|
@@ -214,11 +219,12 @@ renderer never reaches JavaScript and cannot be caught — see
|
|
|
214
219
|
|
|
215
220
|
### Differences from the React package
|
|
216
221
|
|
|
222
|
+
<!-- px-check off differences from React, prose -->
|
|
217
223
|
| Prop | Why it differs |
|
|
218
224
|
|---|---|
|
|
219
|
-
| `timeline.engine` | Accepted inside `
|
|
220
|
-
| `timeline.frameRate` | Ignored. The screen's own refresh rate is used. The player does not compute values frame by frame: when the document loads it works out the animated values in advance, as a list of snapshots (60 per second of animation), and each screen refresh shows the nearest one. The closest thing to a frame rate is how many snapshots per second are prepared
|
|
221
|
-
| `startOn: 'mouseOver'` | Has no touch equivalent, so it is not
|
|
225
|
+
| `timeline.engine` | Accepted inside `timeline` but ignored. There is no Web Animations API on React Native; playback is always native-driven. |
|
|
226
|
+
| `timeline.frameRate` | Ignored. The screen's own refresh rate is used. The player does not compute values frame by frame: when the document loads it works out the animated values in advance, as a list of snapshots (60 per second of animation), and each screen refresh shows the nearest one. The closest thing to a frame rate is how many snapshots per second are prepared, which the player fixes at 60. |
|
|
227
|
+
| `startOn: 'mouseOver'` | Has no touch equivalent, so it is not honored. The other four values (`load`, `click`, `scrollIntoView`, `programmatic`) work as they do on the web, from the file or from the `startOn` prop. |
|
|
222
228
|
| `className` / `style` | Not accepted — you cannot style the component itself. It fills whatever `View` you put it in, so to set its size, give that `View` a `width` and `height`. Styling *inside* the document (`style` on an element in the JSON) is supported. |
|
|
223
229
|
| `onRemove` | Never called. On the web it tells you the animator was thrown away; here there is nothing to tell — when the component leaves the screen, React removes it and everything it created. To run code at that moment, use a `useEffect` cleanup function in your own component. |
|
|
224
230
|
|
|
@@ -228,9 +234,9 @@ There is **no JavaScript frame loop** — the JS thread is idle while an
|
|
|
228
234
|
animation runs.
|
|
229
235
|
|
|
230
236
|
1. **Once per document:** the shared core flattens it
|
|
231
|
-
(`
|
|
237
|
+
(`materializeAllInTree` → effects, loops, motion-path sampling, animated
|
|
232
238
|
`<use>` inlining), then a track compiler densely samples every animated
|
|
233
|
-
property with `calcAnimationValues` — the same function the web
|
|
239
|
+
property with `calcAnimationValues` — the same function the web frame-loop engine
|
|
234
240
|
renders with, so values match the web player exactly.
|
|
235
241
|
2. **Per frame:** one reanimated progress value, driven by
|
|
236
242
|
`withTiming`/`withRepeat` on the UI thread, and a tiny worklet per animated
|
|
@@ -243,73 +249,78 @@ converts it into plain values ahead of time instead of fighting the platform.
|
|
|
243
249
|
## Feature support
|
|
244
250
|
|
|
245
251
|
Every row below was verified by running the document through the real
|
|
246
|
-
pipeline (`
|
|
252
|
+
pipeline (`materializeAllInTree` → track compilation) and checking that the
|
|
247
253
|
element maps to a `react-native-svg` component and that its animated
|
|
248
254
|
properties actually change over time.
|
|
249
255
|
|
|
250
256
|
### Elements
|
|
251
257
|
|
|
258
|
+
<!-- px-check off support matrix, prose -->
|
|
252
259
|
| Element | Renders | Notes |
|
|
253
260
|
|---|---|---|
|
|
254
261
|
| `svg`, `g`, `defs` | ✅ | |
|
|
255
262
|
| `rect`, `circle`, `ellipse`, `line`, `path`, `polygon`, `polyline` | ✅ | |
|
|
256
|
-
| `text`, `tspan` | ✅ | content via
|
|
263
|
+
| `text`, `tspan` | ✅ | content via `textContent` |
|
|
257
264
|
| `textPath` | ✅ | see *Text along a path* below |
|
|
258
|
-
| `image` | ✅ | `href` accepts `data:` URIs; remote URLs are blocked by the
|
|
265
|
+
| `image` | ✅ | `href` accepts `data:` URIs; remote URLs are blocked by the sanitizer |
|
|
259
266
|
| `use`, `symbol` | ✅ | animated targets are **inlined into real clones** before render — `<use>` does not propagate animation natively in React Native |
|
|
260
267
|
| `linearGradient`, `radialGradient`, `stop` | ✅ | |
|
|
261
268
|
| `pattern`, `marker` | ✅ | static geometry verified; complex cases unverified on device |
|
|
262
269
|
| `mask`, `clipPath` | ✅ | |
|
|
263
270
|
| `filter` + all 22 `fe*` primitives | ✅ | `feGaussianBlur`, `feDropShadow`, `feColorMatrix`, `feMerge`, `feComponentTransfer` + `feFunc*`, … **The visual result has not yet been checked on a real device** |
|
|
264
|
-
| `foreignObject` | ❌ | blocked by the shared
|
|
265
|
-
| `script` | ❌ | blocked by the shared
|
|
271
|
+
| `foreignObject` | ❌ | blocked by the shared sanitizer (embeds arbitrary host content) |
|
|
272
|
+
| `script` | ❌ | blocked by the shared sanitizer |
|
|
266
273
|
|
|
267
274
|
### Animatable attributes
|
|
268
275
|
|
|
276
|
+
<!-- px-check off support matrix, prose -->
|
|
269
277
|
| Attribute | Animates | Notes |
|
|
270
278
|
|---|---|---|
|
|
271
|
-
| `opacity`, `
|
|
272
|
-
| `fill`, `stroke`, `
|
|
273
|
-
| `
|
|
279
|
+
| `opacity`, `fillOpacity`, `strokeOpacity` | ✅ | |
|
|
280
|
+
| `fill`, `stroke`, `stopColor` | ✅ | interpolated as RGBA |
|
|
281
|
+
| `strokeWidth`, `strokeDasharray`, `strokeDashoffset` | ✅ | dash arrays are converted to the numeric form React Native expects |
|
|
274
282
|
| `x`, `y`, `width`, `height`, `cx`, `cy`, `r`, `rx`, `ry` | ✅ | |
|
|
275
283
|
| `d` (**path morphing**) | ✅ | keyframes must share command structure |
|
|
276
284
|
| `transform` (unified parts record) | ✅ | `translate`, `rotate`, `skew`, `scale`, `origin` |
|
|
277
285
|
| `translate` / `rotate` / `scale` (legacy per-key form) | ✅ | |
|
|
278
|
-
| `offset` and `
|
|
286
|
+
| `offset` and `stopColor` on gradient stops | ✅ | |
|
|
279
287
|
| filter primitive attrs (e.g. `stdDeviation`) | ✅ | compiles correctly; on-device rendering unverified |
|
|
280
|
-
| `
|
|
288
|
+
| `fontSize` | ✅ | |
|
|
281
289
|
| Any other numeric SVG attribute | ✅ | interpolated numerically and written straight through |
|
|
282
290
|
|
|
283
291
|
### Effects (`node.effects`)
|
|
284
292
|
|
|
285
|
-
All effects are
|
|
293
|
+
All effects are materialized by the shared core before rendering, so the React Native
|
|
286
294
|
player sees plain nodes. **All are supported:**
|
|
287
295
|
|
|
296
|
+
<!-- px-check schema PxEffectsSchema -->
|
|
288
297
|
| Effect | Status | Notes |
|
|
289
298
|
|---|---|---|
|
|
290
299
|
| `transformBy` | ✅ | all parts animatable, including `skew` |
|
|
291
|
-
| `repeater` | ✅ | copies
|
|
300
|
+
| `repeater` | ✅ | copies materialized as real elements; per-copy params animatable |
|
|
292
301
|
| `maskedBy` | ✅ | including an animated mask source |
|
|
293
302
|
| `clipPath` | ✅ | including animated clip geometry |
|
|
294
303
|
| `strokeTrim` | ✅ | incl. `offset` and `subPaths: 'combined'` |
|
|
295
304
|
| `clone` + `retime` | ✅ | each clone keeps its own time shift, incl. `retime.timeCrop` (a visibility window on the document timeline) |
|
|
296
|
-
| `fillGradient` / `strokeGradient` | ✅ | animated stops **and animated geometry** (`animate.gradientX1`/`Cx`/`R`, …); `gradientTransform` is static (core-wide) |
|
|
305
|
+
| `fillGradient` / `strokeGradient` | ✅ | animated stops **and animated geometry** (`animate.gradientX1`/`Cx`/`R`, …); `gradientTransform` is static (core-wide) | <!-- px names=fillGradient,strokeGradient -->
|
|
297
306
|
| `textPath` | ✅ | incl. animated `startOffset` |
|
|
298
307
|
| `text.useGlyphs` | ✅ | text becomes `<path>` outlines from `definitions.fonts` — no font needed |
|
|
299
308
|
|
|
300
309
|
### Motion, timing and references
|
|
301
310
|
|
|
311
|
+
<!-- px-check off support matrix, prose -->
|
|
302
312
|
| Feature | Status | Notes |
|
|
303
313
|
|---|---|---|
|
|
304
314
|
| **Motion along a path** + `autoOrient` | ✅ | **sampled** by the core into plain transform keyframes — `react-native-svg` has no native path motion |
|
|
305
315
|
| **Text along a path** | ✅ two ways | native `textPath` (incl. animated `startOffset`), or **per-letter motion paths** for smooth results — the example app uses the latter, since animating native `startOffset` is janky in `react-native-svg` |
|
|
306
316
|
| Per-property `loop` (incl. `alternate` pingpong) | ✅ | expanded before playback |
|
|
307
317
|
| Easing (cubic-bezier and named refs) | ✅ | baked into the sampled tracks |
|
|
308
|
-
| `definitions.animations` / `easings` / `
|
|
309
|
-
| `node.style` (inline
|
|
318
|
+
| `definitions.animations` / `easings` / `fonts` | ✅ | named refs resolved |
|
|
319
|
+
| `node.style` (inline record) | ✅ | applied as props — React Native has no CSS, so explicit attributes win |
|
|
310
320
|
|
|
311
321
|
### Playback and triggers
|
|
312
322
|
|
|
323
|
+
<!-- px-check off support matrix, prose -->
|
|
313
324
|
| Feature | Status | Notes |
|
|
314
325
|
|---|---|---|
|
|
315
326
|
| `duration`, `delay`, `iterations` (incl. `'infinite'`) | ✅ | |
|
|
@@ -321,9 +332,9 @@ player sees plain nodes. **All are supported:**
|
|
|
321
332
|
| `setPlaybackRate` — faster, slower and **reverse** (negative) | ✅ | composes with `direction` |
|
|
322
333
|
| Trigger `load` / `programmatic` | ✅ | |
|
|
323
334
|
| Trigger `click` | ✅ | wrapped in a `Pressable`; a second tap applies `outAction` |
|
|
324
|
-
| Trigger `scrollIntoView` | ✅ | visibility sampled by measuring against the window (React Native has no `IntersectionObserver`);
|
|
335
|
+
| Trigger `scrollIntoView` | ✅ | visibility sampled by measuring against the window (React Native has no `IntersectionObserver`); honors `scrollIntoViewThreshold` and `outAction` |
|
|
325
336
|
| Trigger `mouseOver` | ❌ | no touch equivalent — use `click`, or drive `play` yourself |
|
|
326
|
-
| `timeline.frameRate` | n/a | reanimated runs at the display refresh rate;
|
|
337
|
+
| `timeline.frameRate` | n/a | reanimated runs at the display refresh rate; the player prepares 60 snapshots per second of animation |
|
|
327
338
|
| `timeline.engine` (`auto` / `native` / `js`) | n/a | there is no Web Animations API on React Native — playback is always native-driven |
|
|
328
339
|
|
|
329
340
|
### Known limitations
|
|
@@ -342,7 +353,7 @@ player sees plain nodes. **All are supported:**
|
|
|
342
353
|
glyph placement by `startOffset … startOffset + pathLength` instead of
|
|
343
354
|
`0 … pathLength`, so glyphs past the end of the path reach a lookup that
|
|
344
355
|
returns `NSNotFound`. On native the player gives such a `<textPath>` its own
|
|
345
|
-
open copy of the path
|
|
356
|
+
open copy of the path, which restores the
|
|
346
357
|
correct bounds. Text that would have wrapped around past the end of the loop
|
|
347
358
|
is clipped instead. Web is unaffected and left untouched.
|
|
348
359
|
|
|
@@ -381,25 +392,6 @@ config.resolver.resolveRequest = (context, moduleName, platform) => {
|
|
|
381
392
|
A complete config is in
|
|
382
393
|
[`examples/react-native-preview-player/metro.config.js`](../../examples/react-native-preview-player/metro.config.js).
|
|
383
394
|
|
|
384
|
-
## Advanced exports
|
|
385
|
-
|
|
386
|
-
For custom rendering or diagnostics:
|
|
387
|
-
|
|
388
|
-
- `renderRnNode(node, opts)` — render a `PxNode` tree to `react-native-svg`
|
|
389
|
-
elements, with a `decorate` hook for wrapping animated elements.
|
|
390
|
-
- `compileTracks(doc, { sampleRate, maxSamples, native })` — build the sampled
|
|
391
|
-
tracks yourself; `sampleRate` trades memory for temporal precision
|
|
392
|
-
(default 60/s). `native` selects the value form: the default is the SVG/DOM
|
|
393
|
-
one, `true` gives what the native views want (a `transform` becomes a
|
|
394
|
-
6-number matrix).
|
|
395
|
-
- `sampleProps(tracks, tMs, stepMs, sampleCount, native)` — the worklet-safe
|
|
396
|
-
lookup. `native` renames `transform` to the native views' `matrix`; pass it
|
|
397
|
-
only for values going through reanimated's animated-props path on a device.
|
|
398
|
-
- `openClosedTextPathTargets(doc, warnings?)` — the closed-path `<textPath>`
|
|
399
|
-
workaround described under [Known limitations](#known-limitations).
|
|
400
|
-
- `PxRnErrorBoundary` — the boundary the component wraps itself in.
|
|
401
|
-
- `RN_SVG_COMPONENTS`, `toRnPropName` — the tag and attribute maps.
|
|
402
|
-
|
|
403
395
|
## Example app
|
|
404
396
|
|
|
405
397
|
A full preview player with six animations and transport controls:
|