svg-scroll-draw 2.23.3 → 2.23.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.
Files changed (103) hide show
  1. package/README.md +12 -12
  2. package/dist/angular/index.cjs +3 -3
  3. package/dist/angular/index.d.mts +3 -3
  4. package/dist/angular/index.d.ts +3 -3
  5. package/dist/angular/index.mjs +1 -1
  6. package/dist/astro/index.cjs +3 -3
  7. package/dist/astro/index.d.mts +3 -3
  8. package/dist/astro/index.d.ts +3 -3
  9. package/dist/astro/index.mjs +1 -1
  10. package/dist/cdn/svg-scroll-draw.dev.global.js +222 -292
  11. package/dist/cdn/svg-scroll-draw.global.js +2 -2
  12. package/dist/{chunk-BLY6F6EC.mjs → chunk-22B37AWY.mjs} +1 -1
  13. package/dist/{chunk-YNNA33A5.mjs → chunk-BSW7W2Y5.mjs} +1 -1
  14. package/dist/{chunk-ETACBG7W.mjs → chunk-CZLUBJAI.mjs} +1 -1
  15. package/dist/chunk-K6VATIJH.mjs +3 -0
  16. package/dist/chunk-NV6YTPHI.mjs +1 -0
  17. package/dist/chunk-NYQJPPMP.mjs +1 -0
  18. package/dist/{chunk-4I7MLTP5.mjs → chunk-OSGWGHQJ.mjs} +1 -1
  19. package/dist/chunk-PAGN26EH.mjs +1 -0
  20. package/dist/chunk-THPTANES.mjs +1 -0
  21. package/dist/{chunk-OJFWQ6CU.mjs → chunk-V3Z6G5JE.mjs} +1 -1
  22. package/dist/chunk-VJ5ZU55P.mjs +1 -0
  23. package/dist/chunk-XC5NR2MU.mjs +1 -0
  24. package/dist/chunk-XNM3VKEK.mjs +1 -0
  25. package/dist/cinematic/index.cjs +2 -2
  26. package/dist/cinematic/index.mjs +1 -1
  27. package/dist/{engine-B7UpB4fT.d.ts → engine-6VsjEwzb.d.ts} +1 -1
  28. package/dist/{engine-o1VSQ1lX.d.mts → engine-BLWKCfxC.d.mts} +1 -1
  29. package/dist/group/index.cjs +2 -2
  30. package/dist/group/index.d.mts +3 -3
  31. package/dist/group/index.d.ts +3 -3
  32. package/dist/group/index.mjs +1 -1
  33. package/dist/horizontal/index.cjs +1 -1
  34. package/dist/horizontal/index.d.mts +11 -8
  35. package/dist/horizontal/index.d.ts +11 -8
  36. package/dist/horizontal/index.mjs +1 -1
  37. package/dist/{index-D3o4vT3e.d.ts → index-BN8yKeQ3.d.ts} +1 -1
  38. package/dist/{index-BY-CNE8c.d.mts → index-DAiRJgtF.d.mts} +2 -2
  39. package/dist/{index-CdaBYn3A.d.mts → index-DVjblK_4.d.mts} +1 -1
  40. package/dist/{index-DqXXiDIk.d.ts → index-hSQqLVbg.d.ts} +2 -2
  41. package/dist/index.cjs +3 -3
  42. package/dist/index.d.mts +6 -6
  43. package/dist/index.d.ts +6 -6
  44. package/dist/index.mjs +1 -1
  45. package/dist/nuxt/index.cjs +3 -3
  46. package/dist/nuxt/index.d.mts +3 -3
  47. package/dist/nuxt/index.d.ts +3 -3
  48. package/dist/nuxt/index.mjs +1 -1
  49. package/dist/progress/index.cjs +1 -1
  50. package/dist/progress/index.d.mts +1 -1
  51. package/dist/progress/index.d.ts +1 -1
  52. package/dist/progress/index.mjs +1 -1
  53. package/dist/react/index.cjs +2 -2
  54. package/dist/react/index.d.mts +6 -4
  55. package/dist/react/index.d.ts +6 -4
  56. package/dist/react/index.mjs +1 -1
  57. package/dist/reveal/index.cjs +1 -1
  58. package/dist/reveal/index.d.mts +10 -4
  59. package/dist/reveal/index.d.ts +10 -4
  60. package/dist/reveal/index.mjs +1 -1
  61. package/dist/{scroll-draw-DsBMkwrX.d.ts → scroll-draw-BsU_Ohmu.d.ts} +1 -1
  62. package/dist/{scroll-draw-Dej-GIRP.d.mts → scroll-draw-Fzir8hlU.d.mts} +1 -1
  63. package/dist/snap/index.cjs +1 -1
  64. package/dist/snap/index.d.mts +1 -1
  65. package/dist/snap/index.d.ts +1 -1
  66. package/dist/snap/index.mjs +1 -1
  67. package/dist/solid/index.cjs +2 -2
  68. package/dist/solid/index.d.mts +3 -3
  69. package/dist/solid/index.d.ts +3 -3
  70. package/dist/solid/index.mjs +1 -1
  71. package/dist/svelte/index.cjs +2 -2
  72. package/dist/svelte/index.d.mts +3 -3
  73. package/dist/svelte/index.d.ts +3 -3
  74. package/dist/svelte/index.mjs +1 -1
  75. package/dist/text/index.cjs +1 -1
  76. package/dist/text/index.d.mts +1 -1
  77. package/dist/text/index.d.ts +1 -1
  78. package/dist/text/index.mjs +1 -1
  79. package/dist/timeline/index.cjs +9 -9
  80. package/dist/timeline/index.d.mts +1 -1
  81. package/dist/timeline/index.d.ts +1 -1
  82. package/dist/timeline/index.mjs +8 -8
  83. package/dist/{types-MswK0erX.d.mts → types-9bcJdVcg.d.mts} +6 -2
  84. package/dist/{types-MswK0erX.d.ts → types-9bcJdVcg.d.ts} +6 -2
  85. package/dist/video/index.cjs +1 -1
  86. package/dist/video/index.d.mts +1 -1
  87. package/dist/video/index.d.ts +1 -1
  88. package/dist/video/index.mjs +1 -1
  89. package/dist/vue/index.cjs +3 -3
  90. package/dist/vue/index.d.mts +3 -3
  91. package/dist/vue/index.d.ts +3 -3
  92. package/dist/vue/index.mjs +1 -1
  93. package/dist/web-component/index.cjs +3 -3
  94. package/dist/web-component/index.mjs +1 -1
  95. package/package.json +1 -1
  96. package/dist/chunk-23EALNDF.mjs +0 -3
  97. package/dist/chunk-4H2SGMAQ.mjs +0 -1
  98. package/dist/chunk-A4JCC3QQ.mjs +0 -1
  99. package/dist/chunk-DM6EDKWH.mjs +0 -1
  100. package/dist/chunk-GMBIGV3M.mjs +0 -1
  101. package/dist/chunk-LGJQWZHH.mjs +0 -1
  102. package/dist/chunk-V4L3ELVC.mjs +0 -1
  103. package/dist/chunk-YZV4Z3EQ.mjs +0 -1
@@ -93,7 +93,8 @@ var SvgScrollDraw = (() => {
93
93
  "ease-in": (t) => t * t,
94
94
  "ease-out": (t) => t * (2 - t),
95
95
  "ease-in-out": (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
96
- spring: (t) => 1 - Math.cos(t * Math.PI * 2.5) * Math.pow(1 - t, 2.2),
96
+ // createSpring's defaults, the same arithmetic in the same order.
97
+ spring: createSpring(),
97
98
  bounce: createBounce(),
98
99
  elastic: createElastic()
99
100
  };
@@ -110,30 +111,12 @@ var SvgScrollDraw = (() => {
110
111
  const [element = "top", viewport = "bottom"] = trimmed.split(/\s+/).filter(Boolean);
111
112
  return { element, viewport };
112
113
  }
114
+ var anchorAt = (anchor, other) => anchor === "top" ? 0 : anchor === "center" ? 0.5 : anchor === "bottom" ? 1 : other;
113
115
  function elementAnchorY(top, height, scrollY, anchor) {
114
- switch (anchor) {
115
- case "top":
116
- return top + scrollY;
117
- case "center":
118
- return top + scrollY + height / 2;
119
- case "bottom":
120
- return top + scrollY + height;
121
- default:
122
- return top + scrollY;
123
- }
116
+ return top + scrollY + height * anchorAt(anchor, 0);
124
117
  }
125
118
  function viewportAnchorY(anchor, vpHeight) {
126
- if (/^\d+(\.\d+)?%$/.test(anchor)) return vpHeight * (parseFloat(anchor) / 100);
127
- switch (anchor) {
128
- case "top":
129
- return 0;
130
- case "center":
131
- return vpHeight / 2;
132
- case "bottom":
133
- return vpHeight;
134
- default:
135
- return vpHeight;
136
- }
119
+ return vpHeight * (/^\d+(\.\d+)?%$/.test(anchor) ? parseFloat(anchor) / 100 : anchorAt(anchor, 1));
137
120
  }
138
121
  function computeProgress(scrollY, tStart, tEnd, speed) {
139
122
  if (tEnd === tStart) return 0;
@@ -162,6 +145,8 @@ var SvgScrollDraw = (() => {
162
145
 
163
146
  // src/core/geometry.ts
164
147
  function getElementLength(el) {
148
+ const declared = parseFloat(el.getAttribute("pathLength") ?? "");
149
+ if (declared > 0) return declared;
165
150
  const tag = el.tagName.toLowerCase();
166
151
  if (tag === "rect") {
167
152
  const w = parseFloat(el.getAttribute("width") ?? "0");
@@ -311,6 +296,45 @@ var SvgScrollDraw = (() => {
311
296
  registry().delete(el);
312
297
  }
313
298
 
299
+ // src/core/motion-watch.ts
300
+ function watchReducedMotion(cb) {
301
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
302
+ return () => {
303
+ };
304
+ }
305
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
306
+ const handler = (e) => cb(!!e.matches);
307
+ if (typeof mq.addEventListener === "function") {
308
+ mq.addEventListener("change", handler);
309
+ return () => mq.removeEventListener("change", handler);
310
+ }
311
+ const legacy = mq;
312
+ legacy.addListener?.(handler);
313
+ return () => legacy.removeListener?.(handler);
314
+ }
315
+
316
+ // src/core/motion-live.ts
317
+ function finishWhenReduced(options, build) {
318
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return build(options, true);
319
+ let inst = build(options, false);
320
+ const unwatch = watchReducedMotion((reduced) => {
321
+ if (!reduced) return;
322
+ unwatch();
323
+ const finished = (inst.getProgress?.() ?? 0) >= 1;
324
+ inst.destroy();
325
+ inst = build(finished ? { ...options, onComplete: void 0 } : options, true);
326
+ });
327
+ const live = {};
328
+ for (const key of Object.keys(inst)) {
329
+ live[key] = (...args) => inst[key]?.(...args);
330
+ }
331
+ live.destroy = () => {
332
+ unwatch();
333
+ inst.destroy();
334
+ };
335
+ return live;
336
+ }
337
+
314
338
  // src/core/layout-watch.ts
315
339
  var observer;
316
340
  var watchers = /* @__PURE__ */ new Set();
@@ -364,43 +388,53 @@ var SvgScrollDraw = (() => {
364
388
  };
365
389
  }
366
390
 
367
- // src/core/motion-watch.ts
368
- function watchReducedMotion(cb) {
369
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
370
- return () => {
371
- };
372
- }
373
- const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
374
- const handler = (e) => cb(!!e.matches);
375
- if (typeof mq.addEventListener === "function") {
376
- mq.addEventListener("change", handler);
377
- return () => mq.removeEventListener("change", handler);
378
- }
379
- const legacy = mq;
380
- legacy.addListener?.(handler);
381
- return () => legacy.removeListener?.(handler);
382
- }
383
-
384
- // src/core/motion-live.ts
385
- function finishWhenReduced(options, build) {
386
- if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return build(options, true);
387
- let inst = build(options, false);
388
- const unwatch = watchReducedMotion((reduced) => {
389
- if (!reduced) return;
390
- unwatch();
391
- const finished = (inst.getProgress?.() ?? 0) >= 1;
392
- inst.destroy();
393
- inst = build(finished ? { ...options, onComplete: void 0 } : options, true);
394
- });
395
- const live = {};
396
- for (const key of Object.keys(inst)) {
397
- live[key] = (...args) => inst[key]?.(...args);
398
- }
399
- live.destroy = () => {
400
- unwatch();
401
- inst.destroy();
391
+ // src/core/frame-gate.ts
392
+ function frameGate(scroller, watched, at, frame, measure) {
393
+ let raf;
394
+ let painted = NaN;
395
+ let held;
396
+ let timer;
397
+ const tick = () => {
398
+ raf = 0;
399
+ frame();
400
+ if (!held && !raf && (painted = at()) > 0 && painted < 1) raf = requestAnimationFrame(tick);
401
+ };
402
+ const wake = () => {
403
+ if (!raf && !held && at() !== painted) raf = requestAnimationFrame(tick);
404
+ };
405
+ const hold = (on) => {
406
+ held = on;
407
+ cancelAnimationFrame(raf);
408
+ raf = painted = NaN;
409
+ wake();
410
+ };
411
+ const remeasure = () => {
412
+ clearTimeout(timer);
413
+ measure();
414
+ wake();
415
+ };
416
+ const later = () => {
417
+ clearTimeout(timer);
418
+ timer = setTimeout(remeasure, 150);
419
+ };
420
+ const unwatch = watchLayout(watched, later);
421
+ const listen = (method) => {
422
+ scroller[method]("scroll", wake, { passive: true });
423
+ window[method]("resize", later);
424
+ window[method]("orientationchange", later);
425
+ };
426
+ listen("addEventListener");
427
+ wake();
428
+ return {
429
+ hold,
430
+ remeasure,
431
+ off() {
432
+ hold(true);
433
+ listen("removeEventListener");
434
+ unwatch();
435
+ clearTimeout(timer);
436
+ }
402
437
  };
403
- return live;
404
438
  }
405
439
 
406
440
  // src/core/engine.ts
@@ -566,11 +600,11 @@ var SvgScrollDraw = (() => {
566
600
  let tStart = 0;
567
601
  let tEnd = 0;
568
602
  let staggerFit = stagger;
569
- let tLast = 0;
603
+ let gStart = 0;
604
+ let gEnd = 0;
570
605
  let completed = false;
571
606
  let started = false;
572
607
  let rafId = 0;
573
- let isVisible = false;
574
608
  let frozenAlpha = -1;
575
609
  let frozenPath = [];
576
610
  let prevScroll = -1;
@@ -578,6 +612,7 @@ var SvgScrollDraw = (() => {
578
612
  let currentAlpha = 0;
579
613
  let repeatCount = 0;
580
614
  let repeatTimer;
615
+ let parked = false;
581
616
  let debugOverlay = null;
582
617
  function destroyDebugOverlay() {
583
618
  debugOverlay?.destroy();
@@ -611,7 +646,10 @@ var SvgScrollDraw = (() => {
611
646
  const room = s[`scroll${d}`] - s[`client${d}`] - 1 - tStart - range / speed;
612
647
  if (room >= 0) staggerFit = Math.min(stagger, room / (gaps * range));
613
648
  }
614
- tLast = tEnd + gaps * staggerFit * range;
649
+ const span = Math.max(0, gaps) * staggerFit * range;
650
+ const len = range / Math.min(speed, 1);
651
+ gStart = tStart + Math.min(0, span, len, span + len);
652
+ gEnd = tStart + Math.max(0, span, len, span + len);
615
653
  }
616
654
  function cacheTriggers() {
617
655
  ({ tStart, tEnd } = measureTriggers());
@@ -676,13 +714,26 @@ var SvgScrollDraw = (() => {
676
714
  el.setAttribute("d", originalDs[i]);
677
715
  });
678
716
  }
679
- paths.forEach((el) => {
717
+ function measure(el, i) {
718
+ const d = el.getAttribute("d");
719
+ let len = getElementLength(el);
720
+ if (morphTo && originalDs[i]) {
721
+ el.setAttribute("d", morphTo);
722
+ const target = getElementLength(el);
723
+ el.setAttribute("d", originalDs[i]);
724
+ len = Math.max(getElementLength(el), target);
725
+ el.setAttribute("d", d);
726
+ }
727
+ lengths[i] = len;
728
+ el.style.strokeDasharray = `${len}`;
729
+ }
730
+ paths.forEach((el, i) => {
680
731
  checkElement(el);
681
- const len = getElementLength(el);
682
- lengths.push(len);
683
732
  const isPath = el.tagName.toLowerCase() === "path";
684
733
  if (isPath) originalDs.push(el.getAttribute("d") ?? "");
685
734
  else originalDs.push("");
735
+ measure(el, i);
736
+ const len = lengths[i];
686
737
  if (morphTo) {
687
738
  if (!isPath) {
688
739
  warn(`morphTo only applies to <path> elements \u2014 ignored on <${el.tagName}>.`, el);
@@ -691,7 +742,6 @@ var SvgScrollDraw = (() => {
691
742
  }
692
743
  }
693
744
  if (prefersReduced) {
694
- el.style.strokeDasharray = `${len}`;
695
745
  el.style.strokeDashoffset = direction === "reverse" ? `${len}` : "0";
696
746
  if (fade) el.style.opacity = "1";
697
747
  if (colorTo) el.style.stroke = colorTo;
@@ -699,7 +749,6 @@ var SvgScrollDraw = (() => {
699
749
  if (fillOpacityTo !== null) el.style.fillOpacity = `${fillOpacityTo}`;
700
750
  if (morphTo && el.tagName.toLowerCase() === "path") el.setAttribute("d", morphTo);
701
751
  } else {
702
- el.style.strokeDasharray = `${len}`;
703
752
  el.style.strokeDashoffset = direction === "reverse" ? "0" : `${len}`;
704
753
  if (fade) el.style.opacity = direction === "reverse" ? "1" : "0";
705
754
  else el.style.opacity = "";
@@ -723,7 +772,7 @@ var SvgScrollDraw = (() => {
723
772
  function nativeEligible() {
724
773
  if (native === false) return false;
725
774
  if (autoplay) return false;
726
- if (debug) return false;
775
+ if (debug && IS_DEV) return false;
727
776
  if (!supportsNativeTimeline()) return false;
728
777
  if (!paths.length) return false;
729
778
  if (!cssTiming) return false;
@@ -837,9 +886,8 @@ var SvgScrollDraw = (() => {
837
886
  */
838
887
  refresh() {
839
888
  paths.forEach((el, i) => {
840
- lengths[i] = getElementLength(el);
889
+ measure(el, i);
841
890
  el.style.setProperty("--ssd-len", String(lengths[i]));
842
- el.style.strokeDasharray = `${lengths[i]}`;
843
891
  });
844
892
  }
845
893
  };
@@ -929,7 +977,7 @@ var SvgScrollDraw = (() => {
929
977
  resetPaths();
930
978
  rafId = requestAnimationFrame(tick);
931
979
  }
932
- const observer3 = new IntersectionObserver(
980
+ const observer2 = new IntersectionObserver(
933
981
  (entries) => {
934
982
  entries.forEach((e) => {
935
983
  if (e.isIntersecting && !(once && completed)) {
@@ -943,28 +991,23 @@ var SvgScrollDraw = (() => {
943
991
  },
944
992
  { root: scrollEl ?? null, threshold, rootMargin }
945
993
  );
946
- let resizeTimer2;
947
- function onResize2() {
948
- clearTimeout(resizeTimer2);
949
- resizeTimer2 = setTimeout(() => {
950
- paths.forEach((el, i) => {
951
- lengths[i] = getElementLength(el);
952
- el.style.strokeDasharray = `${lengths[i]}`;
953
- });
954
- }, 150);
994
+ let resizeTimer;
995
+ function onResize() {
996
+ clearTimeout(resizeTimer);
997
+ resizeTimer = setTimeout(() => paths.forEach(measure), 150);
955
998
  }
956
- window.addEventListener("resize", onResize2);
957
- window.addEventListener("orientationchange", onResize2);
958
- if (delay > 0) setTimeout(() => observer3.observe(container), delay);
959
- else observer3.observe(container);
999
+ window.addEventListener("resize", onResize);
1000
+ window.addEventListener("orientationchange", onResize);
1001
+ if (delay > 0) setTimeout(() => observer2.observe(container), delay);
1002
+ else observer2.observe(container);
960
1003
  return {
961
1004
  destroy() {
962
1005
  cancelAnimationFrame(rafId);
963
1006
  clearTimeout(repeatTimer);
964
- observer3.disconnect();
965
- window.removeEventListener("resize", onResize2);
966
- window.removeEventListener("orientationchange", onResize2);
967
- clearTimeout(resizeTimer2);
1007
+ observer2.disconnect();
1008
+ window.removeEventListener("resize", onResize);
1009
+ window.removeEventListener("orientationchange", onResize);
1010
+ clearTimeout(resizeTimer);
968
1011
  },
969
1012
  replay() {
970
1013
  repeatCount = 0;
@@ -997,24 +1040,39 @@ var SvgScrollDraw = (() => {
997
1040
  },
998
1041
  /** No trigger window on this path — it runs on time — so lengths only. */
999
1042
  refresh() {
1000
- clearTimeout(resizeTimer2);
1001
- paths.forEach((el, i) => {
1002
- lengths[i] = getElementLength(el);
1003
- el.style.strokeDasharray = `${lengths[i]}`;
1004
- });
1043
+ clearTimeout(resizeTimer);
1044
+ paths.forEach(measure);
1005
1045
  }
1006
1046
  };
1007
1047
  }
1008
1048
  if (autoplay) return tracked(buildAutoplay());
1009
1049
  cacheTriggers();
1050
+ const at = () => computeProgress(scrollPos(), gStart, gEnd, 1);
1051
+ function complete() {
1052
+ completed = true;
1053
+ onComplete?.();
1054
+ if (repeatCount < (repeat === "infinite" ? Infinity : repeat)) {
1055
+ repeatCount++;
1056
+ repeatTimer = setTimeout(() => {
1057
+ parked = true;
1058
+ }, repeatDelay);
1059
+ }
1060
+ }
1061
+ function restart() {
1062
+ frozenAlpha = -1;
1063
+ frozenPath = [];
1064
+ started = false;
1065
+ completed = false;
1066
+ parked = false;
1067
+ firedWaypoints.clear();
1068
+ resetPaths();
1069
+ dirty = true;
1070
+ }
1010
1071
  function update() {
1011
- if (paused || !looping) return;
1012
1072
  const now = performance.now();
1013
1073
  const currentScroll = scrollPos();
1014
- if (!dirty && velocityScale === false && currentScroll === lastAppliedScroll) {
1015
- next(currentScroll);
1016
- return;
1017
- }
1074
+ if (parked && at() % 1) restart();
1075
+ if (!dirty && velocityScale === false && currentScroll === lastAppliedScroll) return;
1018
1076
  dirty = false;
1019
1077
  lastAppliedScroll = currentScroll;
1020
1078
  let effectiveSpeed = speed;
@@ -1033,9 +1091,9 @@ var SvgScrollDraw = (() => {
1033
1091
  const rawProgress = range === 0 ? 0 : (currentScroll - tStart) / range;
1034
1092
  if (!isNaN(prevRawProgress)) {
1035
1093
  if (prevRawProgress <= 0 && rawProgress > 0) onEnter?.();
1036
- else if (prevRawProgress > 0 && rawProgress <= 0) onLeaveBack?.();
1037
1094
  if (prevRawProgress < 1 && rawProgress >= 1) onLeave?.();
1038
- else if (prevRawProgress >= 1 && rawProgress < 1) onEnterBack?.();
1095
+ if (prevRawProgress >= 1 && rawProgress < 1) onEnterBack?.();
1096
+ if (prevRawProgress > 0 && rawProgress <= 0) onLeaveBack?.();
1039
1097
  }
1040
1098
  prevRawProgress = rawProgress;
1041
1099
  if (clipDirection) {
@@ -1050,23 +1108,10 @@ var SvgScrollDraw = (() => {
1050
1108
  onStart?.();
1051
1109
  }
1052
1110
  if (alpha >= 1 && !completed) {
1053
- completed = true;
1054
- onComplete?.();
1055
- const maxRepeats = repeat === "infinite" ? Infinity : repeat ?? 0;
1056
- if (repeatCount < maxRepeats) {
1057
- repeatCount++;
1058
- repeatTimer = setTimeout(() => {
1059
- frozenAlpha = -1;
1060
- frozenPath = [];
1061
- started = false;
1062
- completed = false;
1063
- resetPaths();
1064
- }, repeatDelay);
1065
- }
1111
+ complete();
1066
1112
  } else if (alpha < 1 && !once) {
1067
1113
  completed = false;
1068
1114
  }
1069
- next(currentScroll);
1070
1115
  return;
1071
1116
  }
1072
1117
  paths.forEach((el, i) => {
@@ -1095,117 +1140,60 @@ var SvgScrollDraw = (() => {
1095
1140
  onStart?.();
1096
1141
  }
1097
1142
  if (allComplete && !completed) {
1098
- completed = true;
1099
- onComplete?.();
1100
- const maxRepeats = repeat === "infinite" ? Infinity : repeat ?? 0;
1101
- if (repeatCount < maxRepeats) {
1102
- repeatCount++;
1103
- repeatTimer = setTimeout(() => {
1104
- frozenAlpha = -1;
1105
- frozenPath = [];
1106
- started = false;
1107
- completed = false;
1108
- firedWaypoints.clear();
1109
- resetPaths();
1110
- dirty = true;
1111
- }, repeatDelay);
1112
- }
1143
+ complete();
1113
1144
  } else if (!allComplete && !once) {
1114
1145
  completed = false;
1115
1146
  }
1116
- next(currentScroll);
1117
1147
  }
1118
- function inStaggerTail(scroll) {
1119
- return scroll >= tEnd && scroll < tLast;
1120
- }
1121
- function next(scroll) {
1122
- if (isVisible || inStaggerTail(scroll)) rafId = requestAnimationFrame(update);
1123
- else looping = false;
1124
- }
1125
- function live() {
1126
- return isVisible || inStaggerTail(scrollPos());
1127
- }
1128
- let looping = false;
1129
- function startLoop() {
1130
- if (looping) return;
1131
- looping = true;
1132
- rafId = requestAnimationFrame(function frame() {
1133
- update();
1134
- });
1135
- }
1136
- function stopLoop() {
1137
- looping = false;
1138
- cancelAnimationFrame(rafId);
1139
- }
1140
- const observer2 = new IntersectionObserver(
1141
- (entries) => {
1142
- entries.forEach((e) => {
1143
- isVisible = e.isIntersecting;
1144
- if (isVisible && !paused) {
1145
- dirty = true;
1146
- startLoop();
1147
- } else if (!(looping && !paused && inStaggerTail(scrollPos()))) {
1148
- stopLoop();
1149
- }
1150
- });
1151
- },
1152
- { root: scrollEl ?? null, threshold, rootMargin }
1148
+ const gate = frameGate(
1149
+ scrollEl ?? window,
1150
+ [container],
1151
+ at,
1152
+ update,
1153
+ () => {
1154
+ const before = lengths.join();
1155
+ paths.forEach(measure);
1156
+ cacheTriggers();
1157
+ dirty = true;
1158
+ if (lengths.join() !== before) go();
1159
+ }
1153
1160
  );
1154
- function remeasure() {
1155
- paths.forEach((el, i) => {
1156
- lengths[i] = getElementLength(el);
1157
- el.style.strokeDasharray = `${lengths[i]}`;
1158
- });
1159
- cacheTriggers();
1160
- dirty = true;
1161
- }
1162
- let resizeTimer;
1163
- function onResize() {
1164
- clearTimeout(resizeTimer);
1165
- resizeTimer = setTimeout(remeasure, 150);
1166
- }
1167
- window.addEventListener("resize", onResize);
1168
- window.addEventListener("orientationchange", onResize);
1169
- const unwatch = watchLayout([container], onResize);
1170
- if (delay > 0) setTimeout(() => observer2.observe(container), delay);
1171
- else observer2.observe(container);
1161
+ let waiting = delay > 0;
1162
+ function go() {
1163
+ gate.hold(paused || waiting);
1164
+ }
1165
+ let delayTimer;
1166
+ if (waiting) delayTimer = setTimeout(() => {
1167
+ waiting = false;
1168
+ go();
1169
+ }, delay);
1170
+ go();
1172
1171
  return tracked({
1173
1172
  destroy() {
1174
- cancelAnimationFrame(rafId);
1175
1173
  clearTimeout(repeatTimer);
1176
- stopLoop();
1177
- observer2.disconnect();
1178
- unwatch();
1179
- window.removeEventListener("resize", onResize);
1180
- window.removeEventListener("orientationchange", onResize);
1181
- clearTimeout(resizeTimer);
1174
+ clearTimeout(delayTimer);
1175
+ gate.off();
1182
1176
  destroyDebugOverlay();
1183
1177
  },
1184
1178
  replay() {
1185
- frozenAlpha = -1;
1186
- frozenPath = [];
1179
+ restart();
1187
1180
  prevScroll = -1;
1188
1181
  prevVelScroll = -1;
1189
- started = false;
1190
- completed = false;
1191
1182
  repeatCount = 0;
1192
1183
  paused = false;
1193
1184
  currentAlpha = 0;
1194
- firedWaypoints.clear();
1195
1185
  clearTimeout(repeatTimer);
1196
- resetPaths();
1197
- dirty = true;
1198
- if (live()) startLoop();
1186
+ go();
1199
1187
  },
1200
1188
  pause() {
1201
1189
  paused = true;
1202
- stopLoop();
1190
+ gate.hold(true);
1203
1191
  },
1204
1192
  resume() {
1205
1193
  if (!paused) return;
1206
1194
  paused = false;
1207
1195
  dirty = true;
1208
- if (live()) startLoop();
1196
+ go();
1209
1197
  },
1210
1198
  seek(progress) {
1211
1199
  const p = Math.min(1, Math.max(0, progress));
@@ -1213,7 +1201,7 @@ var SvgScrollDraw = (() => {
1213
1201
  frozenAlpha = p;
1214
1202
  frozenPath = [];
1215
1203
  paused = true;
1216
- stopLoop();
1204
+ gate.hold(true);
1217
1205
  applyAlpha(p, direction);
1218
1206
  dirty = true;
1219
1207
  },
@@ -1227,11 +1215,7 @@ var SvgScrollDraw = (() => {
1227
1215
  * window — run immediately and marked dirty so the next frame repaints from
1228
1216
  * the new numbers.
1229
1217
  */
1230
- refresh() {
1231
- clearTimeout(resizeTimer);
1232
- remeasure();
1233
- if (live() && !paused) startLoop();
1234
- }
1218
+ refresh: gate.remeasure
1235
1219
  }, () => ({ tStart, tEnd }));
1236
1220
  }
1237
1221
 
@@ -1482,8 +1466,6 @@ var SvgScrollDraw = (() => {
1482
1466
  if (nativeEligible()) return buildNative();
1483
1467
  let tStart = 0;
1484
1468
  let tEnd = 0;
1485
- let rafId = 0;
1486
- let isVisible = false;
1487
1469
  let paused = false;
1488
1470
  let frozenAlpha = -1;
1489
1471
  let frozenRaw = 0;
@@ -1525,13 +1507,13 @@ var SvgScrollDraw = (() => {
1525
1507
  return;
1526
1508
  }
1527
1509
  if (prevRawProgress <= 0 && raw > 0) onEnter?.();
1528
- else if (prevRawProgress > 0 && raw <= 0) onLeaveBack?.();
1529
1510
  if (prevRawProgress < 1 && raw >= 1) onLeave?.();
1530
- else if (prevRawProgress >= 1 && raw < 1) onEnterBack?.();
1511
+ if (prevRawProgress >= 1 && raw < 1) onEnterBack?.();
1512
+ if (prevRawProgress > 0 && raw <= 0) onLeaveBack?.();
1531
1513
  prevRawProgress = raw;
1532
1514
  }
1533
1515
  function update() {
1534
- if (!isVisible || paused) return;
1516
+ if (paused) return;
1535
1517
  const now = performance.now();
1536
1518
  const currentScroll = scrollPos();
1537
1519
  let effectiveSpeed = speed;
@@ -1555,52 +1537,36 @@ var SvgScrollDraw = (() => {
1555
1537
  } else if (alpha < 1 && !once) {
1556
1538
  completed = false;
1557
1539
  }
1558
- rafId = requestAnimationFrame(update);
1559
1540
  }
1541
+ const gate = frameGate(
1542
+ scrollEl ?? window,
1543
+ [triggerEl],
1544
+ () => computeProgress(scrollPos(), tStart, tEnd, Math.min(speed, 1)),
1545
+ update,
1546
+ cacheTriggers
1547
+ );
1560
1548
  cacheTriggers();
1561
1549
  if (tEnd === tStart) {
1562
- warn(
1563
- "scrollAnimate: the trigger window has zero length (start and end resolve to the same scroll position), so this element will never animate. If the target is inside a position:sticky stage, measure the trigger from the container that holds the scroll room via `triggerElement`.",
1564
- el
1565
- );
1550
+ warn("scrollAnimate: zero-length trigger window, so it never animates. In a sticky stage, set triggerElement.", el);
1566
1551
  }
1567
1552
  {
1568
1553
  frozenRaw = computeProgress(scrollPos(), tStart, tEnd, speed);
1569
1554
  currentAlpha = easeFn(frozenRaw);
1570
1555
  applyAlpha(currentAlpha);
1571
1556
  }
1572
- const observer2 = new IntersectionObserver(
1573
- (ioEntries) => {
1574
- ioEntries.forEach((e) => {
1575
- isVisible = e.isIntersecting;
1576
- if (isVisible && !paused) rafId = requestAnimationFrame(update);
1577
- else cancelAnimationFrame(rafId);
1578
- });
1579
- },
1580
- { root: scrollEl ?? null }
1581
- );
1582
- let resizeTimer;
1583
- function onResize() {
1584
- clearTimeout(resizeTimer);
1585
- resizeTimer = setTimeout(cacheTriggers, 150);
1586
- }
1587
- window.addEventListener("resize", onResize);
1588
- window.addEventListener("orientationchange", onResize);
1589
- const unwatch = watchLayout([triggerEl], onResize);
1590
- observer2.observe(el);
1591
1557
  _register(el, {
1592
1558
  type: "animate",
1593
1559
  getProgress: () => currentAlpha,
1594
1560
  getTrigger: () => ({ tStart, tEnd })
1595
1561
  });
1562
+ function halt() {
1563
+ paused = true;
1564
+ gate.hold(true);
1565
+ }
1596
1566
  return {
1597
1567
  destroy() {
1598
- cancelAnimationFrame(rafId);
1599
- observer2.disconnect();
1600
- window.removeEventListener("resize", onResize);
1601
- window.removeEventListener("orientationchange", onResize);
1602
- unwatch();
1603
- clearTimeout(resizeTimer);
1568
+ paused = true;
1569
+ gate.off();
1604
1570
  restoreInline();
1605
1571
  _unregister(el);
1606
1572
  },
@@ -1611,31 +1577,26 @@ var SvgScrollDraw = (() => {
1611
1577
  currentAlpha = 0;
1612
1578
  paused = false;
1613
1579
  applyAlpha(0);
1580
+ gate.hold(false);
1614
1581
  },
1615
- pause() {
1616
- paused = true;
1617
- cancelAnimationFrame(rafId);
1618
- },
1582
+ pause: halt,
1619
1583
  resume() {
1620
1584
  if (!paused) return;
1621
1585
  paused = false;
1622
- if (isVisible) rafId = requestAnimationFrame(update);
1586
+ gate.hold(false);
1623
1587
  },
1624
1588
  seek(p) {
1625
1589
  const clamped = Math.min(1, Math.max(0, p));
1626
1590
  currentAlpha = clamped;
1627
1591
  frozenAlpha = clamped;
1628
1592
  frozenRaw = 0;
1629
- paused = true;
1630
- cancelAnimationFrame(rafId);
1593
+ halt();
1631
1594
  applyAlpha(clamped);
1632
1595
  },
1633
1596
  getProgress() {
1634
1597
  return currentAlpha;
1635
1598
  },
1636
- refresh() {
1637
- cacheTriggers();
1638
- }
1599
+ refresh: gate.remeasure
1639
1600
  };
1640
1601
  }
1641
1602
 
@@ -1735,9 +1696,6 @@ var SvgScrollDraw = (() => {
1735
1696
  let tStart = 0;
1736
1697
  let tEnd = 0;
1737
1698
  let measured = false;
1738
- let rafId = 0;
1739
- let isVisible = false;
1740
- let paused = false;
1741
1699
  let frozenAlpha = -1;
1742
1700
  let frozenRaw = 0;
1743
1701
  let currentAlpha = 0;
@@ -1758,13 +1716,14 @@ var SvgScrollDraw = (() => {
1758
1716
  tStart = result.tStart;
1759
1717
  tEnd = result.tEnd;
1760
1718
  }
1719
+ const savedVar = el.style.getPropertyValue("--scroll-draw-progress");
1761
1720
  function applyAlpha(alpha) {
1762
1721
  el.textContent = fmt(from + (to - from) * alpha);
1763
1722
  el.style.setProperty("--scroll-draw-progress", String(alpha));
1764
1723
  }
1724
+ const at = () => computeProgress(scrollPos(), tStart, tEnd, 1);
1765
1725
  function update() {
1766
- if (!isVisible || paused) return;
1767
- const progress = computeProgress(scrollPos(), tStart, tEnd, 1);
1726
+ const progress = at();
1768
1727
  const alpha = once ? Math.max(frozenAlpha, easeFn(frozenRaw = Math.max(frozenRaw, progress))) : easeFn(progress);
1769
1728
  currentAlpha = alpha;
1770
1729
  applyAlpha(alpha);
@@ -1774,7 +1733,6 @@ var SvgScrollDraw = (() => {
1774
1733
  } else if (alpha < 1 && !once) {
1775
1734
  completed = false;
1776
1735
  }
1777
- rafId = requestAnimationFrame(update);
1778
1736
  }
1779
1737
  cacheTriggers();
1780
1738
  {
@@ -1783,37 +1741,17 @@ var SvgScrollDraw = (() => {
1783
1741
  currentAlpha = initAlpha;
1784
1742
  applyAlpha(initAlpha);
1785
1743
  }
1786
- const observer2 = new IntersectionObserver(
1787
- (entries) => {
1788
- entries.forEach((e) => {
1789
- isVisible = e.isIntersecting;
1790
- if (isVisible && !paused) rafId = requestAnimationFrame(update);
1791
- else cancelAnimationFrame(rafId);
1792
- });
1793
- }
1794
- );
1795
- let resizeTimer;
1796
- function onResize() {
1797
- clearTimeout(resizeTimer);
1798
- resizeTimer = setTimeout(cacheTriggers, 150);
1799
- }
1800
- window.addEventListener("resize", onResize);
1801
- window.addEventListener("orientationchange", onResize);
1802
- const unwatch = watchLayout([el], onResize);
1803
- observer2.observe(el);
1744
+ const gate = frameGate(window, [el], at, update, cacheTriggers);
1804
1745
  _register(el, {
1805
1746
  type: "counter",
1806
1747
  getProgress: () => currentAlpha,
1807
1748
  getTrigger: () => ({ tStart, tEnd })
1808
1749
  });
1750
+ const resume = () => gate.hold(false);
1809
1751
  return {
1810
1752
  destroy() {
1811
- cancelAnimationFrame(rafId);
1812
- observer2.disconnect();
1813
- window.removeEventListener("resize", onResize);
1814
- window.removeEventListener("orientationchange", onResize);
1815
- unwatch();
1816
- clearTimeout(resizeTimer);
1753
+ gate.off();
1754
+ el.style.setProperty("--scroll-draw-progress", savedVar);
1817
1755
  _unregister(el);
1818
1756
  },
1819
1757
  replay() {
@@ -1821,25 +1759,17 @@ var SvgScrollDraw = (() => {
1821
1759
  frozenRaw = 0;
1822
1760
  completed = false;
1823
1761
  currentAlpha = 0;
1824
- paused = false;
1825
1762
  applyAlpha(0);
1763
+ resume();
1826
1764
  },
1827
- pause() {
1828
- paused = true;
1829
- cancelAnimationFrame(rafId);
1830
- },
1831
- resume() {
1832
- if (!paused) return;
1833
- paused = false;
1834
- if (isVisible) rafId = requestAnimationFrame(update);
1835
- },
1765
+ pause: () => gate.hold(true),
1766
+ resume,
1836
1767
  seek(p) {
1837
1768
  const clamped = Math.min(1, Math.max(0, p));
1838
1769
  currentAlpha = clamped;
1839
1770
  frozenAlpha = clamped;
1840
1771
  frozenRaw = 0;
1841
- paused = true;
1842
- cancelAnimationFrame(rafId);
1772
+ gate.hold(true);
1843
1773
  applyAlpha(clamped);
1844
1774
  },
1845
1775
  getProgress() {