@strategicprojects/rpic 0.7.0 → 0.8.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/index.js CHANGED
@@ -63,7 +63,9 @@ function ensure() {
63
63
  }
64
64
 
65
65
  /**
66
- * Compile pic source into `{ svg, animations, diagnostics, warnings, objects }` (throws on a pic error).
66
+ * Compile pic source into `{ svg, animations, diagnostics, warnings, objects }`
67
+ * (throws on a pic error). A top-level `scroll: true` is present when the source
68
+ * used `animate scroll` — a hint to scrub the timeline on scroll (see `animate`).
67
69
  * @param {string} src
68
70
  * @param {{circuits?: boolean, texlabels?: boolean}} [opts]
69
71
  */
@@ -93,8 +95,8 @@ export function renderSvg(src, opts) {
93
95
  * Build a GSAP timeline from a drawing's animation manifest and play it on the
94
96
  * SVG inside `root`. Browser-only (needs the DOM and a GSAP instance).
95
97
  * @param {Element} root container holding the injected SVG
96
- * @param {Array<{id:string,effect:string,start:number,duration:number}>} animations
97
- * @param {*} gsap the GSAP instance
98
+ * @param {Array<{id:string,effect:string,start:number,duration:number,repeat?:number,yoyo?:boolean,ease?:string,path?:string,color?:string,out?:boolean,from?:string,morph?:string}>} animations
99
+ * @param {*} gsap the GSAP instance (register MotionPathPlugin for `move`, MorphSVGPlugin for `morph`)
98
100
  * @returns the GSAP timeline
99
101
  */
100
102
  export function animate(root, animations, gsap) {
@@ -106,25 +108,131 @@ export function animate(root, animations, gsap) {
106
108
  if (!el) continue;
107
109
  switch (a.effect) {
108
110
  case 'fade':
109
- tl.from(el, { opacity: 0, duration: a.duration, ease: 'power1.out' }, a.start);
111
+ enterExit(tl, el, withOverrides({ opacity: 0, duration: a.duration, ease: 'power1.out' }, a), a);
110
112
  break;
111
113
  case 'pop':
112
- tl.from(
114
+ enterExit(
115
+ tl,
113
116
  el,
114
- { scale: 0, transformOrigin: '50% 50%', duration: a.duration, ease: 'back.out(1.7)' },
115
- a.start
117
+ withOverrides({ scale: 0, transformOrigin: '50% 50%', duration: a.duration, ease: 'back.out(1.7)' }, a),
118
+ a
116
119
  );
117
120
  break;
121
+ case 'slide':
122
+ slideIn(tl, el, a);
123
+ break;
118
124
  case 'draw':
119
125
  drawOn(el, a, tl);
120
126
  break;
127
+ case 'move':
128
+ moveAlong(root, el, a, tl);
129
+ break;
130
+ case 'morph':
131
+ morphInto(root, el, a, tl);
132
+ break;
133
+ case 'highlight':
134
+ highlightWith(el, a, tl);
135
+ break;
121
136
  default:
122
- tl.from(el, { opacity: 0, duration: a.duration }, a.start);
137
+ enterExit(tl, el, withOverrides({ opacity: 0, duration: a.duration }, a), a);
123
138
  }
124
139
  }
125
140
  return tl;
126
141
  }
127
142
 
143
+ // Entrances tween FROM the hidden `vars` to the natural state; `out` reverses
144
+ // it into an exit (TO the hidden state). Shared by fade/pop/slide.
145
+ function enterExit(tl, el, vars, a) {
146
+ return a.out ? tl.to(el, vars, a.start) : tl.from(el, vars, a.start);
147
+ }
148
+
149
+ // The `slide` effect: enter (or, with `out`, leave) by translating from a
150
+ // compass direction, offset by the element's own extent so it clears its slot.
151
+ function slideIn(tl, el, a) {
152
+ let bb = { width: 0, height: 0 };
153
+ try {
154
+ bb = el.getBBox();
155
+ } catch {
156
+ /* not yet laid out */
157
+ }
158
+ const off = {
159
+ left: { x: -(bb.width * 1.5 || 40) },
160
+ right: { x: bb.width * 1.5 || 40 },
161
+ up: { y: -(bb.height * 1.5 || 40) },
162
+ down: { y: bb.height * 1.5 || 40 },
163
+ }[a.from || 'left'];
164
+ enterExit(tl, el, withOverrides({ ...off, opacity: 0, duration: a.duration, ease: 'power2.out' }, a), a);
165
+ }
166
+
167
+ // Fold the optional GSAP overrides (repeat/yoyo/ease) into a tween's vars.
168
+ // `ease` replaces the effect's default easing; repeat/yoyo loop the tween.
169
+ function withOverrides(vars, a) {
170
+ if (a.repeat) vars.repeat = a.repeat;
171
+ if (a.yoyo) vars.yoyo = true;
172
+ if (a.ease) vars.ease = a.ease;
173
+ return vars;
174
+ }
175
+
176
+ // The `highlight` effect: emphasise `el`. With a target colour, tween the
177
+ // stroke of its geometry to that colour; without one, a colour-free scale
178
+ // pulse. It's a one-directional `.to()` — pair with `repeat 1 yoyo` for a
179
+ // flash-and-return, or `repeat -1 yoyo` for a continuous pulse.
180
+ function highlightWith(el, a, tl) {
181
+ if (a.color) {
182
+ const shapes = el.querySelectorAll('path, polyline, line, rect, circle, ellipse, polygon');
183
+ tl.to(shapes, withOverrides({ stroke: a.color, duration: a.duration, ease: 'power1.inOut' }, a), a.start);
184
+ } else {
185
+ tl.to(
186
+ el,
187
+ withOverrides({ scale: 1.12, transformOrigin: '50% 50%', duration: a.duration, ease: 'power1.inOut' }, a),
188
+ a.start
189
+ );
190
+ }
191
+ }
192
+
193
+ // The `morph` effect: tween `el`'s outline into the shape of the object
194
+ // `a.morph` references, via GSAP's MorphSVGPlugin. The consumer must have
195
+ // registered it (`gsap.registerPlugin(MorphSVGPlugin)`); without it GSAP
196
+ // no-ops the morphSVG and the shape stays put.
197
+ function morphInto(root, el, a, tl) {
198
+ if (!a.morph) return;
199
+ const tsel =
200
+ typeof CSS !== 'undefined' && CSS.escape ? '#' + CSS.escape(a.morph) : `[id="${a.morph}"]`;
201
+ const target = root.querySelector(tsel);
202
+ const sel = 'path, polyline, line, rect, circle, ellipse, polygon';
203
+ const src = el.querySelector(sel);
204
+ const dst = target && target.querySelector(sel);
205
+ if (!src || !dst) return;
206
+ tl.to(src, withOverrides({ morphSVG: dst, duration: a.duration, ease: 'power1.inOut' }, a), a.start);
207
+ }
208
+
209
+ // The `move` effect: travel `el` along the geometry of the object `a.path`
210
+ // references, via GSAP's MotionPathPlugin. The consumer must have registered
211
+ // it (`gsap.registerPlugin(MotionPathPlugin)`); without it GSAP no-ops the
212
+ // motionPath and the object simply stays put.
213
+ function moveAlong(root, el, a, tl) {
214
+ if (!a.path) return;
215
+ const psel =
216
+ typeof CSS !== 'undefined' && CSS.escape ? '#' + CSS.escape(a.path) : `[id="${a.path}"]`;
217
+ const group = root.querySelector(psel);
218
+ const pathEl = group && group.querySelector('path, polyline, line, polygon');
219
+ if (!pathEl) return;
220
+ // `align` maps the path into the traveller's coordinate space; a `move`
221
+ // tween is a `.to()` (current position → along the path), not a `.from()`.
222
+ tl.to(
223
+ el,
224
+ withOverrides(
225
+ {
226
+ motionPath: { path: pathEl, align: pathEl, alignOrigin: [0.5, 0.5], autoRotate: false },
227
+ duration: a.duration,
228
+ ease: 'none',
229
+ },
230
+ a
231
+ ),
232
+ a.start
233
+ );
234
+ }
235
+
128
236
  function drawOn(group, a, tl) {
129
237
  const els = group.querySelectorAll('path, polyline, line, rect, circle, ellipse, polygon');
130
238
  els.forEach((el) => {
@@ -146,14 +254,16 @@ function drawOn(group, a, tl) {
146
254
  len = 0;
147
255
  }
148
256
  if (len > 0) {
257
+ // `out` reverses the trace: the stroke retracts (dashoffset 0 → len).
258
+ const [begin, end] = a.out ? [0, len] : [len, 0];
149
259
  tl.fromTo(
150
260
  el,
151
- { strokeDasharray: len, strokeDashoffset: len },
152
- { strokeDashoffset: 0, duration: a.duration, ease: 'none' },
261
+ { strokeDasharray: len, strokeDashoffset: begin },
262
+ withOverrides({ strokeDashoffset: end, duration: a.duration, ease: 'none' }, a),
153
263
  a.start
154
264
  );
155
265
  } else {
156
- tl.from(el, { opacity: 0, duration: a.duration }, a.start);
266
+ enterExit(tl, el, withOverrides({ opacity: 0, duration: a.duration }, a), a);
157
267
  }
158
268
  });
159
269
  const texts = group.querySelectorAll('text');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strategicprojects/rpic",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "The pic graphics language compiled to SVG with animation manifests, via WebAssembly. Browser + Node.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
Binary file
Binary file