gclass-anims 1.0.0-beta.6 → 1.0.0-beta.7

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/Animations.js CHANGED
@@ -28,6 +28,23 @@ export const finalOpacity = (target) => {
28
28
  return isNaN(v) ? 1 : v
29
29
  }
30
30
 
31
+ // TextPlugin tweens take their endpoints from the LIVE DOM: the `.typewriter`
32
+ // play callbacks pass `el.innerHTML` as the text to type. The tween's from
33
+ // state ("") is applied the instant the tween is created, and a teardown that
34
+ // kills the tween mid-flight leaves that wiped state behind — so a later
35
+ // engine re-init reading `el.innerHTML` again would type an empty (or
36
+ // partially-typed) string forever. Stash the full HTML on first sight and
37
+ // reuse it. The stash only refreshes from SETTLED content: never while a
38
+ // typewriter tween on the element is actively rendering partial progress, and
39
+ // never from a blank DOM. Legit content changes (React re-renders, dynamic
40
+ // `.appear` elements) therefore update the stash naturally.
41
+ export const stashText = (el) => {
42
+ const busy = (el.typewriter || el._scrollTween)?.isActive?.()
43
+ const html = el.innerHTML
44
+ if (!busy && html && html.trim()) el._gcText = html
45
+ return el._gcText !== undefined ? el._gcText : html
46
+ }
47
+
31
48
 
32
49
  //Spawn animations
33
50
 
package/Config.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  spinCCW, spinCW, expandA, typewriter, bell, spawnBlur, spawnFade, spawnXDown,
4
4
  spawnXUp, spawnYRight, spawnYLeft, pulse, radiate, hover, expandRight,
5
5
  expandLeft, expandUp, expandDown, marquee, countUp,
6
- spawnClipReveal, curtainHorizontal, curtainVertical,
6
+ spawnClipReveal, curtainHorizontal, curtainVertical, stashText,
7
7
  } from './Animations.js'
8
8
 
9
9
  // ---------------------------------------------------------------------------
@@ -91,7 +91,7 @@ export const animations = [
91
91
  { sel: ".expand-up", from: { opacity: 0, scaleY: 0 }, play: (el, delay, dur, ease) => expandUp(el, delay, dur, ease) },
92
92
  { sel: ".expand-down", from: { opacity: 0, scaleY: 0 }, play: (el, delay, dur, ease) => expandDown(el, delay, dur, ease) },
93
93
  { sel: ".expand-all", from: { opacity: 0, scale: 0 }, play: (el, delay, dur, ease) => expandA(el, delay, dur, ease) },
94
- { sel: ".typewriter", typewriter: true, from: { text: "" }, play: (el, delay, dur, ease) => typewriter(el, el.innerHTML, dur, delay, ease) },
94
+ { sel: ".typewriter", typewriter: true, from: { text: "" }, play: (el, delay, dur, ease) => typewriter(el, stashText(el), dur, delay, ease) },
95
95
  { sel: ".typewriter-split", typewriter: true, typewriterSplit: true, from: { opacity: 0 }, play: (el, delay, dur, ease) => null },
96
96
 
97
97
  // Custom-function animation: counts from the `.spawn-num-N` value (N = the
package/Listeners.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import gsap from 'gsap'
2
- import { SpawnV, verticalmove, expandmove, magnet, magnet3d, reset, typewriter, countTargetVars } from './Animations'
2
+ import { SpawnV, verticalmove, expandmove, magnet, magnet3d, reset, typewriter, countTargetVars, stashText } from './Animations'
3
3
  import { customAnims } from './CustomAnims'
4
4
  import { defaults, normalize } from './Config'
5
5
  import { TextPlugin, ScrollTrigger, SplitText } from 'gsap/all'
@@ -724,7 +724,7 @@ export default function initListeners() {
724
724
  for (const [key] of Object.entries(from)) {
725
725
  if (key === "opacity") to[key] = 1
726
726
  else if (key === "filter") to[key] = "blur(0px)"
727
- else if (key === "text") to[key] = el.innerHTML
727
+ else if (key === "text") to[key] = stashText(el)
728
728
  else if (key === "clipPath") to[key] = "inset(0% 0% 0% 0%)"
729
729
  else to[key] = key.startsWith("scale") ? 1 : 0
730
730
  }
@@ -767,7 +767,7 @@ export default function initListeners() {
767
767
  ? ([...el.classList].find(c => c.startsWith("ease-"))?.split("-")[1] ?? "none")
768
768
  : getEase(el)
769
769
 
770
- const fullText = el.innerHTML
770
+ const fullText = stashText(el)
771
771
 
772
772
  const enter = () => {
773
773
  if (el._scrollTween) el._scrollTween.kill()
@@ -902,7 +902,7 @@ export default function initListeners() {
902
902
  el._spawnTween = playTypewriterSplit(el, delay, duration, elEase)
903
903
  } else {
904
904
  el.typewriter?.kill()
905
- el.typewriter = typewriter(el, el.innerHTML, duration, delay, elEase)
905
+ el.typewriter = typewriter(el, stashText(el), duration, delay, elEase)
906
906
  }
907
907
  } else {
908
908
  el._spawnTween = play(el, delay, duration, getEase(el))
@@ -1442,18 +1442,44 @@ export default function initListeners() {
1442
1442
  window.removeEventListener("load", ScrollTrigger.refresh)
1443
1443
  clearTimeout(refreshTimer)
1444
1444
  scrollTriggers.forEach((t) => {
1445
- t.kill()
1445
+ const tw = t.trigger._scrollTween
1446
+ // Finalize a mid-flight typewriter entrance at its end state
1447
+ // BEFORE killing: stranded partial text would otherwise be read as
1448
+ // settled content by the next run's stash.
1449
+ if (tw && !tw.reversed() && t.trigger.classList?.contains("typewriter")) tw.progress(1)
1450
+ // kill(true): revert pinning (remove pin-spacers, restore inline
1451
+ // styles) so a later init can re-pin cleanly instead of nesting a
1452
+ // second spacer inside the leaked first one.
1453
+ t.kill(true)
1446
1454
  t.trigger._scrollTween?.kill()
1447
1455
  delete t.trigger._scrollTween
1448
1456
  })
1449
1457
  ScrollTrigger.refresh()
1458
+ // Clear per-element wire-up tags. Without this, any engine restart
1459
+ // (initAnimations() called again, StrictMode remounts, route-level
1460
+ // re-mounts that keep DOM nodes alive) would silently skip rewiring:
1461
+ // .scroll/.pin/parallax triggers stay dead (their old ones were just
1462
+ // killed) and clicks/loops/hover never re-bind. `_appeared` goes too,
1463
+ // so dynamically-added .appear elements can animate under the new
1464
+ // engine run. data-gsap-preserved (cross-reset memory) and
1465
+ // data-gsap-ghost (detached .leave clones) are intentionally KEPT.
1466
+ gsap.utils.toArray('[data-gsap-scroll],[data-gsap-setup],[data-gsap-pinned],[data-gsap-scroll-driven]').forEach((el) => {
1467
+ delete el.dataset.gsapScroll
1468
+ delete el.dataset.gsapSetup
1469
+ delete el.dataset.gsapPinned
1470
+ delete el.dataset.gsapScrollDriven
1471
+ delete el._appeared
1472
+ })
1450
1473
  registeredListeners.forEach(({ el, type, fn }) => el.removeEventListener(type, fn))
1451
1474
  magnetListeners.forEach(({ el, type, fn }) => el.removeEventListener(type, fn))
1452
1475
  magnetQuery?.removeEventListener("change", applyMagnet)
1453
1476
  loopEls.forEach(({ el, key }) => el[key]?.kill())
1454
1477
  cssTweens.forEach((t) => t?.kill())
1455
1478
  cssTweens.length = 0
1456
- gsap.utils.toArray(".typewriter").forEach(el => el.typewriter?.kill())
1479
+ gsap.utils.toArray(".typewriter").forEach(el => {
1480
+ if (el.typewriter && !el.typewriter.reversed()) el.typewriter.progress(1)
1481
+ el.typewriter?.kill()
1482
+ })
1457
1483
  textSplits.forEach((s) => s.revert())
1458
1484
  textSplits.length = 0
1459
1485
  onCompleteTweens.forEach((t) => t?.kill())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gclass-anims",
3
- "version": "1.0.0-beta.6",
3
+ "version": "1.0.0-beta.7",
4
4
  "description": "A Tailwind-style utility layer on top of GSAP. Framework-agnostic — works in vanilla JS, React, Vue, Svelte, or any bundler.",
5
5
  "type": "module",
6
6
  "main": "index.js",