animot-presenter 0.6.4 → 0.6.6
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/dist/AnimotPresenter.svelte +11 -2
- package/dist/cdn/animot-presenter.esm.js +6394 -6273
- package/dist/cdn/animot-presenter.min.js +9 -9
- package/dist/types.d.ts +1 -0
- package/dist/utils/arrow-clip-draw.d.ts +6 -3
- package/dist/utils/arrow-clip-draw.js +78 -63
- package/dist/utils/freehand-draw-reveal.d.ts +19 -0
- package/dist/utils/freehand-draw-reveal.js +116 -0
- package/dist/utils/freehand.d.ts +10 -0
- package/dist/utils/freehand.js +66 -0
- package/package.json +1 -1
package/dist/types.d.ts
CHANGED
|
@@ -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' —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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' —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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);
|
|
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
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
|
107
|
-
|
|
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
|
-
|
|
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
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
141
|
-
|
|
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
|
-
|
|
161
|
+
setProgress(0);
|
|
162
|
+
setHead(0);
|
|
148
163
|
raf = 0;
|
|
149
164
|
}
|
|
150
165
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DrawElement } from '../types';
|
|
2
|
+
type DrawStrokeEl = Pick<DrawElement, 'points' | 'brush' | 'strokeWidth' | 'thinning' | 'smoothing' | 'streamline' | 'taperStart' | 'taperEnd'>;
|
|
3
|
+
export interface FreehandDrawRevealParams {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
mode: 'draw' | 'undraw' | 'draw-undraw' | 'none' | string;
|
|
6
|
+
duration: number;
|
|
7
|
+
el: DrawStrokeEl;
|
|
8
|
+
loop?: boolean;
|
|
9
|
+
reverse?: boolean;
|
|
10
|
+
/** Slide loop duration in ms — when looping, the effective duration is
|
|
11
|
+
* rounded so an integer number of cycles fits (seamless GIF loop). */
|
|
12
|
+
slideDuration?: number;
|
|
13
|
+
key?: unknown;
|
|
14
|
+
}
|
|
15
|
+
export declare function freehandDrawReveal(node: SVGPathElement, params: FreehandDrawRevealParams): {
|
|
16
|
+
update(p: FreehandDrawRevealParams): void;
|
|
17
|
+
destroy(): void;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action for freehand stroke-reveal (draw / undraw / draw-undraw).
|
|
3
|
+
*
|
|
4
|
+
* Unlike arrows (a thin stroked line revealed via stroke-dashoffset), a freehand
|
|
5
|
+
* element is a FILLED brush outline. Revealing it by masking leaks the future
|
|
6
|
+
* segment at self-crossings (the thick already-drawn mask overlaps wherever a
|
|
7
|
+
* later part of the loop passes through). So instead we rebuild the filled
|
|
8
|
+
* outline from the points up to the current arc-length each frame — the
|
|
9
|
+
* not-yet-drawn part simply doesn't exist yet, so loops draw cleanly.
|
|
10
|
+
*
|
|
11
|
+
* The action owns the host <path>'s `d` attribute (don't bind `d=` on it).
|
|
12
|
+
*/
|
|
13
|
+
import { drawOutlineAtProgress } from './freehand';
|
|
14
|
+
export function freehandDrawReveal(node, params) {
|
|
15
|
+
let raf = 0;
|
|
16
|
+
const setShown = (shown) => node.setAttribute('d', drawOutlineAtProgress(params.el, shown, !!params.reverse));
|
|
17
|
+
const full = () => node.setAttribute('d', drawOutlineAtProgress(params.el, 1, !!params.reverse));
|
|
18
|
+
function reset() {
|
|
19
|
+
if (raf)
|
|
20
|
+
cancelAnimationFrame(raf);
|
|
21
|
+
raf = 0;
|
|
22
|
+
}
|
|
23
|
+
function run() {
|
|
24
|
+
reset();
|
|
25
|
+
if (!params.enabled || params.mode === 'none') {
|
|
26
|
+
full();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const requested = Math.max(50, params.duration);
|
|
30
|
+
const dur = params.slideDuration && params.slideDuration > 0 && params.loop
|
|
31
|
+
? params.slideDuration / Math.max(1, Math.round(params.slideDuration / requested))
|
|
32
|
+
: requested;
|
|
33
|
+
const start = performance.now();
|
|
34
|
+
const m = params.mode;
|
|
35
|
+
const cubicOut = (t) => 1 - Math.pow(1 - t, 3);
|
|
36
|
+
// Initial frame.
|
|
37
|
+
setShown(m === 'undraw' ? 1 : 0);
|
|
38
|
+
function step(now) {
|
|
39
|
+
const elapsed = now - start;
|
|
40
|
+
if (m === 'draw') {
|
|
41
|
+
const t = Math.min(elapsed / dur, 1);
|
|
42
|
+
setShown(cubicOut(t));
|
|
43
|
+
if (t < 1)
|
|
44
|
+
raf = requestAnimationFrame(step);
|
|
45
|
+
else if (params.loop)
|
|
46
|
+
run();
|
|
47
|
+
else {
|
|
48
|
+
full();
|
|
49
|
+
raf = 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (m === 'undraw') {
|
|
53
|
+
const t = Math.min(elapsed / dur, 1);
|
|
54
|
+
setShown(1 - cubicOut(t));
|
|
55
|
+
if (t < 1)
|
|
56
|
+
raf = requestAnimationFrame(step);
|
|
57
|
+
else if (params.loop)
|
|
58
|
+
run();
|
|
59
|
+
else {
|
|
60
|
+
setShown(0);
|
|
61
|
+
raf = 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (m === 'draw-undraw') {
|
|
65
|
+
const half = dur / 2;
|
|
66
|
+
if (elapsed < half) {
|
|
67
|
+
setShown(cubicOut(Math.min(elapsed / half, 1)));
|
|
68
|
+
raf = requestAnimationFrame(step);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const t = Math.min((elapsed - half) / half, 1);
|
|
72
|
+
setShown(1 - cubicOut(t));
|
|
73
|
+
if (t < 1)
|
|
74
|
+
raf = requestAnimationFrame(step);
|
|
75
|
+
else if (params.loop)
|
|
76
|
+
run();
|
|
77
|
+
else {
|
|
78
|
+
setShown(0);
|
|
79
|
+
raf = 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
raf = requestAnimationFrame(step);
|
|
85
|
+
}
|
|
86
|
+
queueMicrotask(run);
|
|
87
|
+
// Let the video-export pipeline restart this under the virtual clock.
|
|
88
|
+
if (typeof window !== 'undefined') {
|
|
89
|
+
const reg = (window.__svgAnimRestart ||= []);
|
|
90
|
+
reg.push(run);
|
|
91
|
+
node.__svgAnimRestart = run;
|
|
92
|
+
}
|
|
93
|
+
let lastKey = params.key;
|
|
94
|
+
return {
|
|
95
|
+
update(p) {
|
|
96
|
+
const keyChanged = p.key !== lastKey;
|
|
97
|
+
params = p;
|
|
98
|
+
if (keyChanged) {
|
|
99
|
+
lastKey = p.key;
|
|
100
|
+
queueMicrotask(run);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
destroy() {
|
|
104
|
+
reset();
|
|
105
|
+
if (typeof window !== 'undefined') {
|
|
106
|
+
const reg = window.__svgAnimRestart;
|
|
107
|
+
const fn = node.__svgAnimRestart;
|
|
108
|
+
if (reg && fn) {
|
|
109
|
+
const idx = reg.indexOf(fn);
|
|
110
|
+
if (idx >= 0)
|
|
111
|
+
reg.splice(idx, 1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
package/dist/utils/freehand.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ export interface StrokeOptions {
|
|
|
8
8
|
taperEnd: boolean;
|
|
9
9
|
}
|
|
10
10
|
export declare function resolveStrokeOptions(el: Pick<DrawElement, 'brush' | 'strokeWidth' | 'thinning' | 'smoothing' | 'streamline' | 'taperStart' | 'taperEnd'>): StrokeOptions;
|
|
11
|
+
type DrawStrokeEl = Pick<DrawElement, 'points' | 'brush' | 'strokeWidth' | 'thinning' | 'smoothing' | 'streamline' | 'taperStart' | 'taperEnd'>;
|
|
12
|
+
/**
|
|
13
|
+
* Filled-outline `d` for the stroke revealed up to `fraction` (0..1) of its arc
|
|
14
|
+
* length. Rebuilds the geometry from the partial point list each call — so at a
|
|
15
|
+
* self-crossing the not-yet-drawn segment simply isn't there (a mask reveal
|
|
16
|
+
* leaks the future segment wherever the thick already-drawn mask overlaps it).
|
|
17
|
+
* The growing tip is left capped (a pen nib); the real taper applies at full.
|
|
18
|
+
*/
|
|
19
|
+
export declare function drawOutlineAtProgress(el: DrawStrokeEl, fraction: number, reverse?: boolean): string;
|
|
11
20
|
/** Compute the axis-aligned bounding box of raw input points. */
|
|
12
21
|
export declare function pointsBounds(points: number[][]): {
|
|
13
22
|
minX: number;
|
|
@@ -24,3 +33,4 @@ export declare function drawElementToPath(el: Pick<DrawElement, 'points' | 'brus
|
|
|
24
33
|
d: string;
|
|
25
34
|
viewBox: string;
|
|
26
35
|
};
|
|
36
|
+
export {};
|
package/dist/utils/freehand.js
CHANGED
|
@@ -30,6 +30,72 @@ function outlineToPath(points) {
|
|
|
30
30
|
d.push('Z');
|
|
31
31
|
return d.join(' ');
|
|
32
32
|
}
|
|
33
|
+
/** Slice the input points to the first `fraction` (0..1) of total arc length,
|
|
34
|
+
* interpolating the final partial segment. `reverse` slices from the far end
|
|
35
|
+
* so the reveal can run end→start. */
|
|
36
|
+
function pointsUpToFraction(points, fraction, reverse) {
|
|
37
|
+
let pts = reverse ? [...points].reverse() : points;
|
|
38
|
+
const n = pts.length;
|
|
39
|
+
if (n === 0)
|
|
40
|
+
return [];
|
|
41
|
+
if (fraction >= 1)
|
|
42
|
+
return pts;
|
|
43
|
+
let total = 0;
|
|
44
|
+
const seg = [];
|
|
45
|
+
for (let i = 1; i < n; i++) {
|
|
46
|
+
const l = Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]);
|
|
47
|
+
seg.push(l);
|
|
48
|
+
total += l;
|
|
49
|
+
}
|
|
50
|
+
if (total === 0)
|
|
51
|
+
return [pts[0]];
|
|
52
|
+
const target = fraction * total;
|
|
53
|
+
const out = [pts[0]];
|
|
54
|
+
let acc = 0;
|
|
55
|
+
for (let i = 1; i < n; i++) {
|
|
56
|
+
const l = seg[i - 1];
|
|
57
|
+
if (acc + l >= target) {
|
|
58
|
+
const t = l > 0 ? (target - acc) / l : 0;
|
|
59
|
+
const x = pts[i - 1][0] + (pts[i][0] - pts[i - 1][0]) * t;
|
|
60
|
+
const y = pts[i - 1][1] + (pts[i][1] - pts[i - 1][1]) * t;
|
|
61
|
+
const p0 = pts[i - 1][2], p1 = pts[i][2];
|
|
62
|
+
out.push(p0 !== undefined && p1 !== undefined ? [x, y, p0 + (p1 - p0) * t] : [x, y]);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
acc += l;
|
|
66
|
+
out.push(pts[i]);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Filled-outline `d` for the stroke revealed up to `fraction` (0..1) of its arc
|
|
72
|
+
* length. Rebuilds the geometry from the partial point list each call — so at a
|
|
73
|
+
* self-crossing the not-yet-drawn segment simply isn't there (a mask reveal
|
|
74
|
+
* leaks the future segment wherever the thick already-drawn mask overlaps it).
|
|
75
|
+
* The growing tip is left capped (a pen nib); the real taper applies at full.
|
|
76
|
+
*/
|
|
77
|
+
export function drawOutlineAtProgress(el, fraction, reverse = false) {
|
|
78
|
+
const f = Math.max(0, Math.min(1, fraction));
|
|
79
|
+
if (f <= 0)
|
|
80
|
+
return '';
|
|
81
|
+
if (f >= 1)
|
|
82
|
+
return drawElementToPath(el).d;
|
|
83
|
+
const opts = resolveStrokeOptions(el);
|
|
84
|
+
const sliced = pointsUpToFraction(el.points, f, reverse);
|
|
85
|
+
if (sliced.length < 2) {
|
|
86
|
+
const [x, y] = sliced[0] ?? [0, 0];
|
|
87
|
+
sliced.push([x + 0.01, y]);
|
|
88
|
+
}
|
|
89
|
+
const stroke = getStroke(sliced, {
|
|
90
|
+
size: opts.size,
|
|
91
|
+
thinning: opts.thinning,
|
|
92
|
+
smoothing: opts.smoothing,
|
|
93
|
+
streamline: opts.streamline,
|
|
94
|
+
start: { taper: opts.taperStart ? opts.size * 4 : 0, cap: !opts.taperStart },
|
|
95
|
+
end: { taper: 0, cap: true }
|
|
96
|
+
});
|
|
97
|
+
return outlineToPath(stroke);
|
|
98
|
+
}
|
|
33
99
|
/** Compute the axis-aligned bounding box of raw input points. */
|
|
34
100
|
export function pointsBounds(points) {
|
|
35
101
|
if (points.length === 0)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "animot-presenter",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
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",
|