@pixodesk/svg-animator-rn 1.0.34 → 1.0.35
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 +16 -18
- package/dist/index.cjs +29 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -11
- package/dist/index.d.ts +14 -11
- package/dist/index.js +31 -42
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/PixodeskSvgAnimator.tsx +57 -60
- package/src/PxRnTracks.test.ts +8 -8
- package/src/PxRnTracks.ts +2 -2
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} config={{ timeline: { fillMode: 'forwards' } }} />
|
|
55
55
|
|
|
56
56
|
// Loop forever regardless of what the document says
|
|
57
57
|
<PixodeskSvgAnimator doc={doc} autoplay iterations="infinite" />
|
|
@@ -175,13 +175,12 @@ const [time, setTime] = useState(0);
|
|
|
175
175
|
| `apiRef` | `RefObject<RnAnimatorApi>` | Ref for imperative control |
|
|
176
176
|
| `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
177
|
| `time` | `number` | show the frame at that time, in milliseconds from the start |
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `outAction` | `OutAction` | What a second tap does with the `click` trigger (default: the document's, else `pause`) |
|
|
178
|
+
| `config` | `object \| string` | Per-instance override of the document's `animator` block, deep-merged over it — same shape as the file (`{ timeline: { fillMode, direction, trigger: { outAction, finishAction, … } } }`); `null` at a slot deletes that key. A JSON string is accepted too. `timeline.engine` is accepted but ignored here |
|
|
179
|
+
| `resetDocDefaults` | `boolean` | Ignore the document's playback settings and start from the player's defaults, with `config` on top |
|
|
180
|
+
| `duration` | `number` | Shortcut for `config.timeline.duration` (ms) |
|
|
181
|
+
| `delay` | `number` | Shortcut for `config.timeline.delay` (ms) |
|
|
182
|
+
| `iterations` | `number \| 'infinite'` | Shortcut for `config.timeline.iterations` |
|
|
183
|
+
| `startOn` | `StartOn` | Shortcut for `config.timeline.trigger.startOn`. `mouseOver` has no touch equivalent and is ignored |
|
|
185
184
|
| `onPlay` | `() => void` | Called on play/resume |
|
|
186
185
|
| `onPause` | `() => void` | Called on pause |
|
|
187
186
|
| `onFinish` | `() => void` | Called on natural finish |
|
|
@@ -217,9 +216,9 @@ renderer never reaches JavaScript and cannot be caught — see
|
|
|
217
216
|
|
|
218
217
|
| Prop | Why it differs |
|
|
219
218
|
|---|---|
|
|
220
|
-
| `
|
|
221
|
-
| `frameRate` |
|
|
222
|
-
| `startOn` |
|
|
219
|
+
| `timeline.engine` | Accepted inside `config` but ignored. There is no Web Animations API on React Native; playback is always native-driven. |
|
|
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 — `compileTracks({ sampleRate })`, only available when you use the lower-level API instead of the component. |
|
|
221
|
+
| `startOn: 'mouseOver'` | Has no touch equivalent, so it is not honoured. The other four values (`load`, `click`, `scrollIntoView`, `programmatic`) work as they do on the web, from the file or from the `startOn` prop. |
|
|
223
222
|
| `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. |
|
|
224
223
|
| `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. |
|
|
225
224
|
|
|
@@ -296,8 +295,7 @@ player sees plain nodes. **All are supported:**
|
|
|
296
295
|
| `clone` + `retime` | ✅ | each clone keeps its own time shift, incl. `retime.timeCrop` (a visibility window on the document timeline) |
|
|
297
296
|
| `fillGradient` / `strokeGradient` | ✅ | animated stops **and animated geometry** (`animate.gradientX1`/`Cx`/`R`, …); `gradientTransform` is static (core-wide) |
|
|
298
297
|
| `textPath` | ✅ | incl. animated `startOffset` |
|
|
299
|
-
| `text.useGlyphs` | ✅ | text becomes `<path>` outlines from `definitions.
|
|
300
|
-
| `isCombinedShape` | ✅ | |
|
|
298
|
+
| `text.useGlyphs` | ✅ | text becomes `<path>` outlines from `definitions.fonts` — no font needed |
|
|
301
299
|
|
|
302
300
|
### Motion, timing and references
|
|
303
301
|
|
|
@@ -307,7 +305,7 @@ player sees plain nodes. **All are supported:**
|
|
|
307
305
|
| **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` |
|
|
308
306
|
| Per-property `loop` (incl. `alternate` pingpong) | ✅ | expanded before playback |
|
|
309
307
|
| Easing (cubic-bezier and named refs) | ✅ | baked into the sampled tracks |
|
|
310
|
-
| `definitions.animations` / `easings` / `styles` / `
|
|
308
|
+
| `definitions.animations` / `easings` / `styles` / `fonts` | ✅ | named refs resolved; `style` presets applied as props |
|
|
311
309
|
| `node.style` (inline or named) | ✅ | resolved to props — React Native has no CSS, so explicit attributes win |
|
|
312
310
|
|
|
313
311
|
### Playback and triggers
|
|
@@ -316,8 +314,8 @@ player sees plain nodes. **All are supported:**
|
|
|
316
314
|
|---|---|---|
|
|
317
315
|
| `duration`, `delay`, `iterations` (incl. `'infinite'`) | ✅ | |
|
|
318
316
|
| `direction` — all four values | ✅ | |
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
317
|
+
| `fillMode` — `forwards` / `backwards` / `both` / `none` | ✅ | |
|
|
318
|
+
| `trigger.finishAction: 'reset'` | ✅ | |
|
|
321
319
|
| `play` / `pause` / `cancel` / `finish` | ✅ | |
|
|
322
320
|
| `setCurrentTime` — jump to a time, including **while playing** | ✅ | playback continues from the new point |
|
|
323
321
|
| `setPlaybackRate` — faster, slower and **reverse** (negative) | ✅ | composes with `direction` |
|
|
@@ -325,8 +323,8 @@ player sees plain nodes. **All are supported:**
|
|
|
325
323
|
| Trigger `click` | ✅ | wrapped in a `Pressable`; a second tap applies `outAction` |
|
|
326
324
|
| Trigger `scrollIntoView` | ✅ | visibility sampled by measuring against the window (React Native has no `IntersectionObserver`); honours `scrollIntoViewThreshold` and `outAction` |
|
|
327
325
|
| Trigger `mouseOver` | ❌ | no touch equivalent — use `click`, or drive `play` yourself |
|
|
328
|
-
| `frameRate` | n/a | reanimated runs at the display refresh rate; use `compileTracks({sampleRate})` to trade memory for temporal precision |
|
|
329
|
-
| `
|
|
326
|
+
| `timeline.frameRate` | n/a | reanimated runs at the display refresh rate; use `compileTracks({sampleRate})` to trade memory for temporal precision |
|
|
327
|
+
| `timeline.engine` (`auto` / `native` / `js`) | n/a | there is no Web Animations API on React Native — playback is always native-driven |
|
|
330
328
|
|
|
331
329
|
### Known limitations
|
|
332
330
|
|
package/dist/index.cjs
CHANGED
|
@@ -199,7 +199,7 @@ function compileTracks(doc, opts) {
|
|
|
199
199
|
((_a2 = n == null ? void 0 : n.children) != null ? _a2 : []).forEach(indexTags);
|
|
200
200
|
};
|
|
201
201
|
indexTags(doc);
|
|
202
|
-
const bindings = (0, import_svg_animator_core2.getNormalisedBindings)(doc, import_svg_animator_core2.
|
|
202
|
+
const bindings = (0, import_svg_animator_core2.getNormalisedBindings)(doc, import_svg_animator_core2.PxTimelineEngine.js) || [];
|
|
203
203
|
const elements = [];
|
|
204
204
|
for (const binding of bindings) {
|
|
205
205
|
const animDef = binding.animate;
|
|
@@ -562,37 +562,32 @@ var EMPTY_TRACKS = {
|
|
|
562
562
|
elements: []
|
|
563
563
|
};
|
|
564
564
|
function compileDocument(doc, overrides) {
|
|
565
|
-
const {
|
|
565
|
+
const { config, resetDocDefaults, duration, delay, iterations, startOn } = overrides;
|
|
566
566
|
const warnings = (0, import_svg_animator_core5.validateNodeEffects)(doc);
|
|
567
567
|
for (const w of warnings) console.warn("[PixodeskSvgAnimator] effects shape warning:", w);
|
|
568
|
-
|
|
568
|
+
(0, import_svg_animator_core5.reportDocumentDiagnostics)(doc, "[PixodeskSvgAnimator]");
|
|
569
|
+
const patch = (0, import_svg_animator_core5.foldAnimatorConfigShortcuts)(config, { duration, delay, iterations, startOn });
|
|
570
|
+
if (patch !== void 0 || resetDocDefaults) {
|
|
571
|
+
const applied = (0, import_svg_animator_core5.applyAnimatorConfig)(doc, patch != null ? patch : {}, { resetDefaults: !!resetDocDefaults });
|
|
572
|
+
for (const w of applied.warnings) console.warn("[PixodeskSvgAnimator] config override:", w);
|
|
573
|
+
doc = applied.doc;
|
|
574
|
+
}
|
|
575
|
+
let prepared = (0, import_svg_animator_core5.materialiseAllInTree)(doc, import_svg_animator_core5.PxTimelineEngine.native);
|
|
569
576
|
if (NATIVE_SVG_VIEWS) {
|
|
570
577
|
prepared = openClosedTextPathTargets(prepared);
|
|
571
578
|
}
|
|
572
|
-
const animator = (0, import_svg_animator_core5.getAnimatorConfig)(prepared) || {};
|
|
573
|
-
prepared = __spreadProps(__spreadValues({}, prepared), {
|
|
574
|
-
animator: __spreadProps(__spreadValues({}, animator), {
|
|
575
|
-
duration: duration !== void 0 ? duration : animator.duration,
|
|
576
|
-
delay: delay !== void 0 ? delay : animator.delay,
|
|
577
|
-
iterations: iterations !== void 0 ? iterations : animator.iterations,
|
|
578
|
-
fill: fill !== void 0 ? fill : animator.fill,
|
|
579
|
-
direction: direction !== void 0 ? direction : animator.direction,
|
|
580
|
-
resetOnFinish: resetOnFinish !== void 0 ? resetOnFinish : animator.resetOnFinish
|
|
581
|
-
})
|
|
582
|
-
});
|
|
583
579
|
prepared = (0, import_svg_animator_core5.generateNewIds)(prepared);
|
|
584
580
|
const tracks = compileTracks(prepared, { native: NATIVE_SVG_VIEWS });
|
|
585
581
|
return { doc: prepared, tracks, error: null };
|
|
586
582
|
}
|
|
587
583
|
function PixodeskSvgAnimator({
|
|
588
584
|
doc,
|
|
585
|
+
config,
|
|
586
|
+
resetDocDefaults,
|
|
589
587
|
duration,
|
|
590
588
|
delay,
|
|
591
589
|
iterations,
|
|
592
|
-
|
|
593
|
-
direction,
|
|
594
|
-
resetOnFinish,
|
|
595
|
-
outAction: outActionProp,
|
|
590
|
+
startOn,
|
|
596
591
|
// (`progress` prop aliased — the name is taken by the internal reanimated SharedValue)
|
|
597
592
|
autoplay,
|
|
598
593
|
play,
|
|
@@ -609,18 +604,19 @@ function PixodeskSvgAnimator({
|
|
|
609
604
|
fallback
|
|
610
605
|
}) {
|
|
611
606
|
var _a, _b, _c, _d;
|
|
607
|
+
const configKey = typeof config === "string" ? config : JSON.stringify(config != null ? config : null);
|
|
612
608
|
const compiled = (0, import_react3.useMemo)(() => {
|
|
613
609
|
try {
|
|
614
610
|
return compileDocument(
|
|
615
611
|
doc,
|
|
616
|
-
{
|
|
612
|
+
{ config, resetDocDefaults, duration, delay, iterations, startOn }
|
|
617
613
|
);
|
|
618
614
|
} catch (e) {
|
|
619
615
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
620
616
|
console.warn("[PixodeskSvgAnimator] could not compile the document:", error.message);
|
|
621
617
|
return { doc: null, tracks: EMPTY_TRACKS, error };
|
|
622
618
|
}
|
|
623
|
-
}, [doc,
|
|
619
|
+
}, [doc, configKey, resetDocDefaults, duration, delay, iterations, startOn]);
|
|
624
620
|
const tracks = compiled.tracks;
|
|
625
621
|
const totalDuration = tracks.duration * (tracks.iterations === Infinity ? 1 : tracks.iterations);
|
|
626
622
|
const progress = (0, import_react_native_reanimated.useSharedValue)(0);
|
|
@@ -714,8 +710,8 @@ function PixodeskSvgAnimator({
|
|
|
714
710
|
};
|
|
715
711
|
(0, import_react3.useImperativeHandle)(apiRef, () => api, [compiled]);
|
|
716
712
|
const trigger = compiled.doc ? (_a = (0, import_svg_animator_core5.getAnimatorConfig)(compiled.doc)) == null ? void 0 : _a.trigger : void 0;
|
|
717
|
-
const
|
|
718
|
-
const
|
|
713
|
+
const effectiveStartOn = (_b = trigger == null ? void 0 : trigger.startOn) != null ? _b : "load";
|
|
714
|
+
const effectiveOutAction = (_c = trigger == null ? void 0 : trigger.outAction) != null ? _c : "pause";
|
|
719
715
|
(0, import_react3.useEffect)(() => {
|
|
720
716
|
if (progressProp !== void 0 || time !== void 0) {
|
|
721
717
|
const seekMs = time !== void 0 ? time : (progressProp != null ? progressProp : 0) * totalDuration;
|
|
@@ -729,7 +725,7 @@ function PixodeskSvgAnimator({
|
|
|
729
725
|
else api.play();
|
|
730
726
|
return;
|
|
731
727
|
}
|
|
732
|
-
if (autoplay &&
|
|
728
|
+
if (autoplay && effectiveStartOn === "load") {
|
|
733
729
|
api.play();
|
|
734
730
|
}
|
|
735
731
|
}, [compiled, autoplay, play, pause, progressProp, time]);
|
|
@@ -737,7 +733,7 @@ function PixodeskSvgAnimator({
|
|
|
737
733
|
const inViewRef = (0, import_react3.useRef)(false);
|
|
738
734
|
(0, import_react3.useEffect)(() => {
|
|
739
735
|
var _a2;
|
|
740
|
-
if (!autoplay ||
|
|
736
|
+
if (!autoplay || effectiveStartOn !== "scrollIntoView") return;
|
|
741
737
|
const threshold = (_a2 = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _a2 : 0;
|
|
742
738
|
inViewRef.current = false;
|
|
743
739
|
const check = () => {
|
|
@@ -754,17 +750,17 @@ function PixodeskSvgAnimator({
|
|
|
754
750
|
if (isIn) {
|
|
755
751
|
if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
|
|
756
752
|
api.play();
|
|
757
|
-
} else if (
|
|
758
|
-
else if (
|
|
753
|
+
} else if (effectiveOutAction === "reset") api.cancel();
|
|
754
|
+
else if (effectiveOutAction === "reverse") {
|
|
759
755
|
api.setPlaybackRate(-Math.abs(rateRef.current || 1));
|
|
760
756
|
api.play();
|
|
761
|
-
} else if (
|
|
757
|
+
} else if (effectiveOutAction !== "continue") api.pause();
|
|
762
758
|
});
|
|
763
759
|
};
|
|
764
760
|
check();
|
|
765
761
|
const id = setInterval(check, 200);
|
|
766
762
|
return () => clearInterval(id);
|
|
767
|
-
}, [compiled, autoplay,
|
|
763
|
+
}, [compiled, autoplay, effectiveStartOn, effectiveOutAction]);
|
|
768
764
|
(0, import_react3.useEffect)(() => {
|
|
769
765
|
return () => {
|
|
770
766
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
@@ -837,19 +833,19 @@ function PixodeskSvgAnimator({
|
|
|
837
833
|
}, [failure]);
|
|
838
834
|
if (failure) return fallback ? fallback(failure) : null;
|
|
839
835
|
let content = root;
|
|
840
|
-
if (autoplay &&
|
|
836
|
+
if (autoplay && effectiveStartOn === "scrollIntoView" && root) {
|
|
841
837
|
content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { ref: scrollRef, collapsable: false, children: root });
|
|
842
|
-
} else if (autoplay &&
|
|
838
|
+
} else if (autoplay && effectiveStartOn === "click" && root) {
|
|
843
839
|
content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
844
840
|
import_react_native.Pressable,
|
|
845
841
|
{
|
|
846
842
|
onPress: () => {
|
|
847
843
|
if (playingRef.current) {
|
|
848
|
-
if (
|
|
849
|
-
else if (
|
|
844
|
+
if (effectiveOutAction === "reset") api.cancel();
|
|
845
|
+
else if (effectiveOutAction === "reverse") {
|
|
850
846
|
api.setPlaybackRate(-Math.abs(rateRef.current || 1));
|
|
851
847
|
api.play();
|
|
852
|
-
} else if (
|
|
848
|
+
} else if (effectiveOutAction !== "continue") api.pause();
|
|
853
849
|
} else {
|
|
854
850
|
if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
|
|
855
851
|
api.play();
|