animot-presenter 0.6.4 → 0.6.5

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.
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Svelte action for arrow animations. Two strategies depending on mode:
3
3
  *
4
- * • 'draw' / 'undraw' / 'draw-undraw' — clip-path inset on the host <svg>.
5
- * Doesn't touch stroke-dasharray, so dashed/dotted patterns are preserved
6
- * while the path is progressively revealed/hidden from one side.
4
+ * • 'draw' / 'undraw' / 'draw-undraw' — reveals the stroke ALONG THE PATH using
5
+ * stroke-dasharray/offset keyed to the path's total length. This follows the
6
+ * real geometry, so curved, looping and spiral arrows draw the way they're
7
+ * shaped (a linear clip-path wipe could only sweep left↔right / top↔bottom).
8
+ * The arrowhead (a separate sub-path) is revealed in step. Any dashed/dotted
9
+ * pattern is overridden during the reveal and restored when fully drawn.
7
10
  *
8
11
  * • 'flow' — marching ants. Continuously shifts stroke-dashoffset on the
9
12
  * inner .arrow-path, so the dash pattern appears to flow along the path
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Svelte action for arrow animations. Two strategies depending on mode:
3
3
  *
4
- * • 'draw' / 'undraw' / 'draw-undraw' — clip-path inset on the host <svg>.
5
- * Doesn't touch stroke-dasharray, so dashed/dotted patterns are preserved
6
- * while the path is progressively revealed/hidden from one side.
4
+ * • 'draw' / 'undraw' / 'draw-undraw' — reveals the stroke ALONG THE PATH using
5
+ * stroke-dasharray/offset keyed to the path's total length. This follows the
6
+ * real geometry, so curved, looping and spiral arrows draw the way they're
7
+ * shaped (a linear clip-path wipe could only sweep left↔right / top↔bottom).
8
+ * The arrowhead (a separate sub-path) is revealed in step. Any dashed/dotted
9
+ * pattern is overridden during the reveal and restored when fully drawn.
7
10
  *
8
11
  * • 'flow' — marching ants. Continuously shifts stroke-dashoffset on the
9
12
  * inner .arrow-path, so the dash pattern appears to flow along the path
@@ -13,55 +16,37 @@
13
16
  export function arrowClipDraw(node, params) {
14
17
  let raf = 0;
15
18
  let pathEl = null;
19
+ let headEl = null;
16
20
  let baseDash = '';
17
21
  let baseOffset = '';
18
- function clearArrowPath() {
19
- if (!pathEl)
20
- return;
21
- pathEl.style.strokeDasharray = baseDash;
22
- pathEl.style.strokeDashoffset = baseOffset;
22
+ function restorePath() {
23
+ // Back to the element's natural (fully-drawn) appearance.
24
+ if (pathEl) {
25
+ pathEl.style.strokeDasharray = baseDash;
26
+ pathEl.style.strokeDashoffset = baseOffset;
27
+ }
28
+ if (headEl)
29
+ headEl.style.opacity = '';
23
30
  }
24
31
  function reset() {
25
32
  if (raf)
26
33
  cancelAnimationFrame(raf);
27
34
  raf = 0;
28
35
  node.style.clipPath = '';
29
- clearArrowPath();
36
+ restorePath();
30
37
  }
31
38
  function run() {
32
39
  reset();
33
40
  if (!params.enabled || params.mode === 'none')
34
41
  return;
35
- // Capture the inner arrow path (used by 'flow' mode).
36
42
  pathEl = node.querySelector('.arrow-path');
37
- if (pathEl) {
38
- baseDash = pathEl.style.strokeDasharray || '';
39
- baseOffset = pathEl.style.strokeDashoffset || '';
40
- }
41
- // Pick the dominant axis so vertical arrows reveal top↔bottom and horizontal
42
- // arrows reveal left↔right. clip-path inset(top right bottom left).
43
- const dx = params.endX - params.startX;
44
- const dy = params.endY - params.startY;
45
- const horizontal = Math.abs(dx) >= Math.abs(dy);
46
- const positive = horizontal ? dx >= 0 : dy >= 0;
47
- const goesPositive = params.reverse ? !positive : positive;
48
- // Build the clip-path string for a given progress (0 = fully hidden,
49
- // 100 = fully visible) on the dominant axis.
50
- function clip(insetPct) {
51
- if (horizontal) {
52
- return goesPositive
53
- ? `inset(0 ${insetPct}% 0 0)` // hide from right edge
54
- : `inset(0 0 0 ${insetPct}%)`; // hide from left edge
55
- }
56
- return goesPositive
57
- ? `inset(0 0 ${insetPct}% 0)` // hide from bottom
58
- : `inset(${insetPct}% 0 0 0)`; // hide from top
59
- }
60
- const clipFull = clip(100); // fully hidden (100% inset on hide side)
61
- const clipNone = clip(0);
43
+ headEl = node.querySelector('.arrow-head');
44
+ if (!pathEl)
45
+ return;
46
+ baseDash = pathEl.style.strokeDasharray || '';
47
+ baseOffset = pathEl.style.strokeDashoffset || '';
62
48
  // Round duration so an integer number of cycles fits in slide_duration.
63
- // Without this, GIF loop boundary shows the arrow mid-draw → snap to
64
- // invisible → re-draw, which the user perceives as a reset.
49
+ // Without this, a GIF loop boundary can show the arrow mid-draw → snap.
65
50
  const requested = Math.max(50, params.duration);
66
51
  const dur = params.slideDuration && params.slideDuration > 0 && (params.loop || params.mode === 'flow')
67
52
  ? params.slideDuration / Math.max(1, Math.round(params.slideDuration / requested))
@@ -70,21 +55,15 @@ export function arrowClipDraw(node, params) {
70
55
  const m = params.mode;
71
56
  // FLOW: marching ants — animate dashoffset continuously, keep base pattern.
72
57
  if (m === 'flow') {
73
- if (!pathEl)
74
- return;
75
- // If the arrow has no inline dasharray (i.e. it's solid), give it one
76
- // so dashes are visible to flow. Otherwise keep the user's pattern.
77
58
  const dashAttr = pathEl.getAttribute('stroke-dasharray');
78
59
  if (!dashAttr || dashAttr === 'none') {
79
60
  pathEl.style.strokeDasharray = '8 5';
80
61
  }
81
- // One pixel of dashoffset shift per ms feels like a steady current; use
82
- // `duration` as the ms per cycle of 24px (one base dash repeat-ish).
83
62
  const cycle = 24;
84
63
  const dir = params.reverse ? 1 : -1;
85
64
  function flowStep(now) {
86
65
  const elapsed = now - start;
87
- const offset = (dir * (elapsed / dur) * cycle) % (cycle * 1000); // huge mod just to keep it bounded
66
+ const offset = (dir * (elapsed / dur) * cycle) % (cycle * 1000);
88
67
  if (pathEl)
89
68
  pathEl.style.strokeDashoffset = String(offset);
90
69
  raf = requestAnimationFrame(flowStep);
@@ -92,59 +71,95 @@ export function arrowClipDraw(node, params) {
92
71
  raf = requestAnimationFrame(flowStep);
93
72
  return;
94
73
  }
95
- // Initial clip-path state for draw modes.
96
- if (m === 'draw' || m === 'draw-undraw') {
97
- node.style.clipPath = clipFull;
74
+ // PATH-LENGTH REVEAL for draw / undraw / draw-undraw.
75
+ let len = 0;
76
+ try {
77
+ len = pathEl.getTotalLength();
78
+ }
79
+ catch {
80
+ len = 0;
81
+ }
82
+ if (!len) {
83
+ // Degenerate path — nothing sensible to animate.
84
+ restorePath();
85
+ return;
98
86
  }
99
- else if (m === 'undraw') {
100
- node.style.clipPath = clipNone;
87
+ // dashoffset reveals from the path start (positive) or the path end
88
+ // (negative) toward the other end, so `reverse` flips the draw direction.
89
+ const hideOffset = params.reverse ? -len : len;
90
+ // p: 0 = nothing drawn, 1 = fully drawn (along the path).
91
+ function setProgress(p) {
92
+ if (!pathEl)
93
+ return;
94
+ pathEl.style.strokeDasharray = `${len} ${len}`;
95
+ pathEl.style.strokeDashoffset = String(hideOffset * (1 - p));
96
+ }
97
+ // Head only shows once the end of the line is reached.
98
+ function setHead(p) {
99
+ if (headEl)
100
+ headEl.style.opacity = p >= 0.999 ? '1' : '0';
101
+ }
102
+ const cubicOut = (t) => 1 - Math.pow(1 - t, 3);
103
+ // Initial frame.
104
+ if (m === 'undraw') {
105
+ setProgress(1);
106
+ setHead(1);
107
+ }
108
+ else {
109
+ setProgress(0);
110
+ setHead(0);
101
111
  }
102
112
  function step(now) {
103
113
  const elapsed = now - start;
104
114
  if (m === 'draw') {
105
115
  const t = Math.min(elapsed / dur, 1);
106
- const eased = 1 - Math.pow(1 - t, 3);
107
- node.style.clipPath = clip(100 * (1 - eased));
116
+ const p = cubicOut(t);
117
+ setProgress(p);
118
+ setHead(p);
108
119
  if (t < 1)
109
120
  raf = requestAnimationFrame(step);
110
121
  else if (params.loop)
111
122
  run();
112
123
  else {
113
- node.style.clipPath = clipNone;
124
+ restorePath();
114
125
  raf = 0;
115
- }
126
+ } // settle fully drawn (restores dashes)
116
127
  }
117
128
  else if (m === 'undraw') {
118
129
  const t = Math.min(elapsed / dur, 1);
119
- const eased = 1 - Math.pow(1 - t, 3);
120
- node.style.clipPath = clip(100 * eased);
130
+ const p = 1 - cubicOut(t);
131
+ setProgress(p);
132
+ setHead(p);
121
133
  if (t < 1)
122
134
  raf = requestAnimationFrame(step);
123
135
  else if (params.loop)
124
136
  run();
125
137
  else {
126
- node.style.clipPath = clipFull;
138
+ setProgress(0);
139
+ setHead(0);
127
140
  raf = 0;
128
- }
141
+ } // settle hidden
129
142
  }
130
143
  else if (m === 'draw-undraw') {
131
144
  const half = dur / 2;
132
145
  if (elapsed < half) {
133
- const t = Math.min(elapsed / half, 1);
134
- const eased = 1 - Math.pow(1 - t, 3);
135
- node.style.clipPath = clip(100 * (1 - eased));
146
+ const p = cubicOut(Math.min(elapsed / half, 1));
147
+ setProgress(p);
148
+ setHead(p);
136
149
  raf = requestAnimationFrame(step);
137
150
  }
138
151
  else {
139
152
  const t = Math.min((elapsed - half) / half, 1);
140
- const eased = 1 - Math.pow(1 - t, 3);
141
- node.style.clipPath = clip(100 * eased);
153
+ const p = 1 - cubicOut(t);
154
+ setProgress(p);
155
+ setHead(p);
142
156
  if (t < 1)
143
157
  raf = requestAnimationFrame(step);
144
158
  else if (params.loop)
145
159
  run();
146
160
  else {
147
- node.style.clipPath = clipFull;
161
+ setProgress(0);
162
+ setHead(0);
148
163
  raf = 0;
149
164
  }
150
165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "animot-presenter",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "Embed animated presentations anywhere. Works with vanilla JS, React, Vue, Angular, Svelte, and any frontend framework. Morphing animations, code highlighting, charts, particles, and more.",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",