animot-presenter 0.5.21 → 0.5.23
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 +83 -84
- package/dist/cdn/animot-presenter.esm.js +3186 -3196
- package/dist/cdn/animot-presenter.min.js +8 -8
- package/package.json +2 -1
|
@@ -55,8 +55,14 @@
|
|
|
55
55
|
if (motionPathLoopAbort) { motionPathLoopAbort.abort(); motionPathLoopAbort = null; }
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// Keyframe schedule loop
|
|
58
|
+
// Keyframe schedule loop. Plain (non-reactive) module-scope state — adding
|
|
59
|
+
// $state for keyframe overrides in 0.5.20 broke reactivity for the existing
|
|
60
|
+
// tween-driven render. This implementation only retargets existing tweens
|
|
61
|
+
// via setTimeout; nothing here is reactive, nothing is read from render.
|
|
59
62
|
let keyframeLoopAbort: AbortController | null = null;
|
|
63
|
+
// Per-element overrides for keyframe-driven props that aren't tweened
|
|
64
|
+
// (backgroundColor, text color). The schedule writes these at each
|
|
65
|
+
// keyframe boundary; liveProps reads them at render time.
|
|
60
66
|
let keyframeOverrides = $state<Map<string, Record<string, any>>>(new Map());
|
|
61
67
|
function cancelKeyframeLoops() {
|
|
62
68
|
if (keyframeLoopAbort) { keyframeLoopAbort.abort(); keyframeLoopAbort = null; }
|
|
@@ -66,27 +72,28 @@
|
|
|
66
72
|
keyframeOverrides.set(elementId, { ...cur, [prop]: value });
|
|
67
73
|
keyframeOverrides = new Map(keyframeOverrides);
|
|
68
74
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
// Tweens take an easing FUNCTION (t→number), not a CSS keyword. Returning
|
|
76
|
+
// a string here was the cause of "r is not a function" thrown deep in the
|
|
77
|
+
// tween animation loop — `r(t)` crashed because `r` was the literal string
|
|
78
|
+
// passed in. Reuse the engine's `getEasingFn` so keyframe easing matches
|
|
79
|
+
// the slide-morph engine's vocabulary.
|
|
80
|
+
function easingForTween(name: string | undefined): (t: number) => number {
|
|
81
|
+
return getEasingFn(name ?? 'ease-out');
|
|
76
82
|
}
|
|
77
83
|
function animateKeyframes(slide: Slide) {
|
|
78
84
|
cancelKeyframeLoops();
|
|
85
|
+
const hasAnyKeyframes = slide.canvas.elements.some((el) => el.keyframes && el.keyframes.length > 0);
|
|
86
|
+
if (keyframeOverrides.size > 0) keyframeOverrides = new Map();
|
|
87
|
+
if (!hasAnyKeyframes) return;
|
|
79
88
|
keyframeLoopAbort = new AbortController();
|
|
80
89
|
const signal = keyframeLoopAbort.signal;
|
|
81
|
-
if (keyframeOverrides.size > 0) keyframeOverrides = new Map();
|
|
82
90
|
for (const element of slide.canvas.elements) {
|
|
83
91
|
if (!element.keyframes || element.keyframes.length === 0) continue;
|
|
84
92
|
const animated = animatedElements.get(element.id) as any;
|
|
85
93
|
if (!animated) continue;
|
|
86
94
|
const sorted = [...element.keyframes].sort((a, b) => a.time - b.time);
|
|
87
95
|
const first = sorted[0];
|
|
88
|
-
|
|
89
|
-
// Snap to KF1 instantly so the slide-displayed pose IS the start.
|
|
96
|
+
// Snap tweens to KF1 instantly so the slide-displayed pose IS the start.
|
|
90
97
|
if (first.position) { animated.x?.to(first.position.x, { duration: 0 }); animated.y?.to(first.position.y, { duration: 0 }); }
|
|
91
98
|
if (first.size) { animated.width?.to(first.size.width, { duration: 0 }); animated.height?.to(first.size.height, { duration: 0 }); }
|
|
92
99
|
if (first.rotation !== undefined) animated.rotation?.to(first.rotation, { duration: 0 });
|
|
@@ -107,9 +114,7 @@
|
|
|
107
114
|
if (first.grayscale !== undefined) animated.grayscale?.to(first.grayscale, { duration: 0 });
|
|
108
115
|
if (first.backgroundColor !== undefined) setKeyframeOverride(element.id, 'backgroundColor', first.backgroundColor);
|
|
109
116
|
if (first.color !== undefined) setKeyframeOverride(element.id, 'color', first.color);
|
|
110
|
-
|
|
111
117
|
for (const kf of sorted.slice(1)) {
|
|
112
|
-
const delay = Math.max(0, kf.time);
|
|
113
118
|
setTimeout(() => {
|
|
114
119
|
if (signal.aborted) return;
|
|
115
120
|
const easing = easingForTween(kf.easing);
|
|
@@ -136,7 +141,7 @@
|
|
|
136
141
|
if (kf.grayscale !== undefined) animated.grayscale?.to(kf.grayscale, { duration: span, easing });
|
|
137
142
|
if (kf.backgroundColor !== undefined) setKeyframeOverride(element.id, 'backgroundColor', kf.backgroundColor);
|
|
138
143
|
if (kf.color !== undefined) setKeyframeOverride(element.id, 'color', kf.color);
|
|
139
|
-
},
|
|
144
|
+
}, Math.max(0, kf.time));
|
|
140
145
|
}
|
|
141
146
|
}
|
|
142
147
|
}
|
|
@@ -350,54 +355,15 @@
|
|
|
350
355
|
return elements.map(e => e.id);
|
|
351
356
|
});
|
|
352
357
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
* into account. Keyframes turn the element's "effective state" into
|
|
356
|
-
* something time-dependent — `pose='enter'` returns the FIRST keyframe's
|
|
357
|
-
* state (used as morph TARGET / initial-tween-seed), `pose='exit'` the
|
|
358
|
-
* LAST keyframe's (used as morph SOURCE on slide leave).
|
|
359
|
-
*/
|
|
360
|
-
function getElementInSlide(slide: Slide | null, elementId: string, pose: 'enter' | 'exit' = 'enter'): CanvasElement | undefined {
|
|
361
|
-
const el = slide?.canvas.elements.find(el => el.id === elementId);
|
|
362
|
-
if (!el) return el;
|
|
363
|
-
if (!el.keyframes || el.keyframes.length === 0) return el;
|
|
364
|
-
const sorted = [...el.keyframes].sort((a, b) => a.time - b.time);
|
|
365
|
-
const k = pose === 'exit' ? sorted[sorted.length - 1] : sorted[0];
|
|
366
|
-
const out: any = { ...el };
|
|
367
|
-
if (k.position) out.position = k.position;
|
|
368
|
-
if (k.size) out.size = k.size;
|
|
369
|
-
if (k.rotation !== undefined) out.rotation = k.rotation;
|
|
370
|
-
if (k.opacity !== undefined) out.opacity = k.opacity;
|
|
371
|
-
if (k.skewX !== undefined) out.skewX = k.skewX;
|
|
372
|
-
if (k.skewY !== undefined) out.skewY = k.skewY;
|
|
373
|
-
if (k.tiltX !== undefined) out.tiltX = k.tiltX;
|
|
374
|
-
if (k.tiltY !== undefined) out.tiltY = k.tiltY;
|
|
375
|
-
if (k.borderRadius !== undefined) out.borderRadius = k.borderRadius;
|
|
376
|
-
if (k.fontSize !== undefined) out.fontSize = k.fontSize;
|
|
377
|
-
if (k.fillColor !== undefined) out.fillColor = k.fillColor;
|
|
378
|
-
if (k.strokeColor !== undefined) out.strokeColor = k.strokeColor;
|
|
379
|
-
if (k.strokeWidth !== undefined) out.strokeWidth = k.strokeWidth;
|
|
380
|
-
if (k.blur !== undefined) out.blur = k.blur;
|
|
381
|
-
if (k.brightness !== undefined) out.brightness = k.brightness;
|
|
382
|
-
if (k.contrast !== undefined) out.contrast = k.contrast;
|
|
383
|
-
if (k.saturate !== undefined) out.saturate = k.saturate;
|
|
384
|
-
if (k.grayscale !== undefined) out.grayscale = k.grayscale;
|
|
385
|
-
return out as CanvasElement;
|
|
358
|
+
function getElementInSlide(slide: Slide | null, elementId: string): CanvasElement | undefined {
|
|
359
|
+
return slide?.canvas.elements.find(el => el.id === elementId);
|
|
386
360
|
}
|
|
387
361
|
|
|
388
362
|
/**
|
|
389
|
-
* Overlay
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*/
|
|
394
|
-
/**
|
|
395
|
-
* Overlay tween-driven values for properties that the renderer reads
|
|
396
|
-
* directly from element data (instead of via tween.current). Only
|
|
397
|
-
* applies when this element has keyframes OR has an active override —
|
|
398
|
-
* for everything else this is a passthrough so the existing slide-morph
|
|
399
|
-
* render pipeline is left exactly as it was. Without this gate, every
|
|
400
|
-
* render frame allocated a new copy and crowded out the tween reads.
|
|
363
|
+
* Overlay tween-driven values + non-tweened keyframe overrides onto the
|
|
364
|
+
* element used in render. Gated: returns the element unchanged when it
|
|
365
|
+
* has no keyframes AND no active override — that's the 99% case and
|
|
366
|
+
* keeps the existing slide-morph render pipeline allocation-free.
|
|
401
367
|
*/
|
|
402
368
|
function liveProps<T extends CanvasElement>(element: T): T {
|
|
403
369
|
const hasKeyframes = !!element.keyframes && element.keyframes.length > 0;
|
|
@@ -562,36 +528,71 @@
|
|
|
562
528
|
for (const element of slide.canvas.elements) {
|
|
563
529
|
if (!animatedElements.has(element.id)) {
|
|
564
530
|
const inCurrent = getElementInSlide(currentSlide, element.id);
|
|
565
|
-
|
|
531
|
+
let startOpacity = inCurrent ? ((inCurrent as any).opacity ?? 1) : 0;
|
|
566
532
|
const br = (element as any).borderRadius ?? 0;
|
|
567
533
|
const isShape = element.type === 'shape';
|
|
568
534
|
const shapeEl = isShape ? element as ShapeElement : null;
|
|
569
535
|
const isText = element.type === 'text';
|
|
570
536
|
const textEl = isText ? element as TextElement : null;
|
|
537
|
+
|
|
538
|
+
// If the element has keyframes, seed each tween with the FIRST
|
|
539
|
+
// keyframe's value instead of the persisted (last-captured)
|
|
540
|
+
// state. The persisted state is whatever was on the canvas
|
|
541
|
+
// at the moment the user captured their final keyframe; using
|
|
542
|
+
// it as the tween's starting value would cause the slide to
|
|
543
|
+
// flash at the "wrong" pose before the keyframe schedule
|
|
544
|
+
// snaps it to KF1. Doing it here also avoids depending on a
|
|
545
|
+
// duration:0 snap that the tween library may not apply
|
|
546
|
+
// synchronously.
|
|
547
|
+
const sortedKfs = element.keyframes && element.keyframes.length > 0
|
|
548
|
+
? [...element.keyframes].sort((a, b) => a.time - b.time)
|
|
549
|
+
: null;
|
|
550
|
+
const k0 = sortedKfs ? sortedKfs[0] : null;
|
|
551
|
+
const seedX = k0?.position ? k0.position.x : element.position.x;
|
|
552
|
+
const seedY = k0?.position ? k0.position.y : element.position.y;
|
|
553
|
+
const seedW = k0?.size ? k0.size.width : element.size.width;
|
|
554
|
+
const seedH = k0?.size ? k0.size.height : element.size.height;
|
|
555
|
+
const seedRot = k0?.rotation !== undefined ? k0.rotation : element.rotation;
|
|
556
|
+
const seedSkewX = k0?.skewX !== undefined ? k0.skewX : (element.skewX ?? 0);
|
|
557
|
+
const seedSkewY = k0?.skewY !== undefined ? k0.skewY : (element.skewY ?? 0);
|
|
558
|
+
const seedTiltX = k0?.tiltX !== undefined ? k0.tiltX : (element.tiltX ?? 0);
|
|
559
|
+
const seedTiltY = k0?.tiltY !== undefined ? k0.tiltY : (element.tiltY ?? 0);
|
|
560
|
+
if (k0?.opacity !== undefined) startOpacity = k0.opacity;
|
|
561
|
+
const seedBR = k0?.borderRadius !== undefined ? k0.borderRadius : br;
|
|
562
|
+
const seedFontSize = k0?.fontSize !== undefined ? k0.fontSize : (textEl ? textEl.fontSize : 0);
|
|
563
|
+
const seedFill = k0?.fillColor !== undefined ? k0.fillColor : (shapeEl ? shapeEl.fillColor : '');
|
|
564
|
+
const seedStroke = k0?.strokeColor !== undefined ? k0.strokeColor : (shapeEl ? shapeEl.strokeColor : '');
|
|
565
|
+
const seedStrokeW = k0?.strokeWidth !== undefined ? k0.strokeWidth : (shapeEl ? shapeEl.strokeWidth : 0);
|
|
566
|
+
const seedBlur = k0?.blur !== undefined ? k0.blur : (element.blur ?? 0);
|
|
567
|
+
const seedBright = k0?.brightness !== undefined ? k0.brightness : (element.brightness ?? 100);
|
|
568
|
+
const seedContrast = k0?.contrast !== undefined ? k0.contrast : (element.contrast ?? 100);
|
|
569
|
+
const seedSat = k0?.saturate !== undefined ? k0.saturate : (element.saturate ?? 100);
|
|
570
|
+
const seedGray = k0?.grayscale !== undefined ? k0.grayscale : (element.grayscale ?? 0);
|
|
571
|
+
|
|
571
572
|
animatedElements.set(element.id, {
|
|
572
|
-
x: tween(
|
|
573
|
-
y: tween(
|
|
574
|
-
width: tween(
|
|
575
|
-
height: tween(
|
|
576
|
-
rotation: tween(
|
|
577
|
-
skewX: tween(
|
|
578
|
-
skewY: tween(
|
|
579
|
-
tiltX: tween(
|
|
580
|
-
tiltY: tween(
|
|
573
|
+
x: tween(seedX, { duration: 500 }),
|
|
574
|
+
y: tween(seedY, { duration: 500 }),
|
|
575
|
+
width: tween(seedW, { duration: 500 }),
|
|
576
|
+
height: tween(seedH, { duration: 500 }),
|
|
577
|
+
rotation: tween(seedRot, { duration: 500 }),
|
|
578
|
+
skewX: tween(seedSkewX, { duration: 500 }),
|
|
579
|
+
skewY: tween(seedSkewY, { duration: 500 }),
|
|
580
|
+
tiltX: tween(seedTiltX, { duration: 500 }),
|
|
581
|
+
tiltY: tween(seedTiltY, { duration: 500 }),
|
|
581
582
|
perspective: tween(element.perspective ?? 1000, { duration: 500 }),
|
|
582
583
|
opacity: tween(startOpacity, { duration: 300 }),
|
|
583
|
-
borderRadius: tween(
|
|
584
|
-
fontSize: textEl ? tween(
|
|
585
|
-
fillColor: shapeEl ? tween(
|
|
586
|
-
strokeColor: shapeEl ? tween(
|
|
587
|
-
strokeWidth: shapeEl ? tween(
|
|
584
|
+
borderRadius: tween(seedBR, { duration: 500 }),
|
|
585
|
+
fontSize: textEl ? tween(seedFontSize, { duration: 500 }) : null,
|
|
586
|
+
fillColor: shapeEl ? tween(seedFill, { duration: 500 }) : null,
|
|
587
|
+
strokeColor: shapeEl ? tween(seedStroke, { duration: 500 }) : null,
|
|
588
|
+
strokeWidth: shapeEl ? tween(seedStrokeW, { duration: 500 }) : null,
|
|
588
589
|
shapeMorph: shapeEl ? tween(1, { duration: 500 }) : null,
|
|
589
590
|
motionPathProgress: element.motionPathConfig ? tween(0, { duration: 500 }) : null,
|
|
590
|
-
blur: tween(
|
|
591
|
-
brightness: tween(
|
|
592
|
-
contrast: tween(
|
|
593
|
-
saturate: tween(
|
|
594
|
-
grayscale: tween(
|
|
591
|
+
blur: tween(seedBlur, { duration: 500 }),
|
|
592
|
+
brightness: tween(seedBright, { duration: 500 }),
|
|
593
|
+
contrast: tween(seedContrast, { duration: 500 }),
|
|
594
|
+
saturate: tween(seedSat, { duration: 500 }),
|
|
595
|
+
grayscale: tween(seedGray, { duration: 500 })
|
|
595
596
|
});
|
|
596
597
|
const currentSlideEl = getElementInSlide(currentSlide, element.id);
|
|
597
598
|
elementContent.set(element.id, JSON.parse(JSON.stringify(currentSlideEl || element)));
|
|
@@ -853,9 +854,7 @@
|
|
|
853
854
|
// Per-element morphing (transition type = 'none')
|
|
854
855
|
const animations: Promise<void>[] = [];
|
|
855
856
|
for (const elementId of allElementIds) {
|
|
856
|
-
|
|
857
|
-
// from where the keyframe loop left the element on screen.
|
|
858
|
-
const currentEl = getElementInSlide(currentSlide, elementId, 'exit');
|
|
857
|
+
const currentEl = getElementInSlide(currentSlide, elementId);
|
|
859
858
|
const animated = animatedElements.get(elementId);
|
|
860
859
|
if (!animated) continue;
|
|
861
860
|
if (currentEl) {
|
|
@@ -916,8 +915,8 @@
|
|
|
916
915
|
const animationTasks: AnimationTask[] = [];
|
|
917
916
|
|
|
918
917
|
for (const elementId of allElementIds) {
|
|
919
|
-
const currentEl = getElementInSlide(currentSlide, elementId
|
|
920
|
-
const targetEl = getElementInSlide(targetSlide, elementId
|
|
918
|
+
const currentEl = getElementInSlide(currentSlide, elementId);
|
|
919
|
+
const targetEl = getElementInSlide(targetSlide, elementId);
|
|
921
920
|
const animated = animatedElements.get(elementId);
|
|
922
921
|
if (!animated) continue;
|
|
923
922
|
const animConfig = targetEl?.animationConfig || currentEl?.animationConfig;
|