gclass-anims 1.0.0-beta.14 → 1.0.0-beta.15
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/AnimToggle.js +2 -2
- package/Animations.js +19 -3
- package/Boot.html +18 -0
- package/Boot.js +145 -0
- package/index.d.ts +13 -1
- package/index.js +1 -0
- package/package.json +3 -1
package/AnimToggle.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import initListeners from './Listeners'
|
|
2
2
|
|
|
3
3
|
// localStorage key controlling whether the GSAP animation system is mounted.
|
|
4
|
-
const STORAGE_KEY = '
|
|
4
|
+
const STORAGE_KEY = 'gclass-animations-enabled'
|
|
5
5
|
// localStorage key for a forced reduced-motion override (see
|
|
6
6
|
// enableReducedMotion / disableReducedMotion).
|
|
7
|
-
const REDUCED_KEY = '
|
|
7
|
+
const REDUCED_KEY = 'gclass-reduced-motion'
|
|
8
8
|
|
|
9
9
|
const reducedMotionQuery = typeof window !== 'undefined'
|
|
10
10
|
? window.matchMedia('(prefers-reduced-motion: reduce)')
|
package/Animations.js
CHANGED
|
@@ -587,27 +587,43 @@ export function radiate (delay , target , amount , dur , ease , zIndex){
|
|
|
587
587
|
tick = true
|
|
588
588
|
requestAnimationFrame(() => { tick = false; applyRect() })
|
|
589
589
|
}
|
|
590
|
+
// Don't animate detached targets — return inert tween
|
|
591
|
+
if (!target.isConnected) {
|
|
592
|
+
return gsap.fromTo(clone, {}, { duration: 0 })
|
|
593
|
+
}
|
|
590
594
|
document.body.appendChild(clone)
|
|
591
595
|
applyRect()
|
|
592
596
|
window.addEventListener("scroll", schedule, { passive: true })
|
|
593
597
|
window.addEventListener("resize", schedule, { passive: true })
|
|
594
598
|
// Killing the tween (teardown, hover/click rebuilds) must clean up exactly
|
|
595
599
|
// like natural completion - otherwise clones + listeners leak.
|
|
600
|
+
let tween = null
|
|
596
601
|
const cleanup = () => {
|
|
597
602
|
clone.remove()
|
|
598
603
|
window.removeEventListener("scroll", schedule)
|
|
599
604
|
window.removeEventListener("resize", schedule)
|
|
605
|
+
if (observer) observer.disconnect()
|
|
600
606
|
}
|
|
607
|
+
// If target is removed from DOM (React unmount, .remove(), SPA navigation),
|
|
608
|
+
// kill the tween and remove the clone — mirrors React useEffect cleanup
|
|
609
|
+
const observer = new MutationObserver(() => {
|
|
610
|
+
if (!target.isConnected) {
|
|
611
|
+
if (tween) tween.kill()
|
|
612
|
+
cleanup()
|
|
613
|
+
}
|
|
614
|
+
})
|
|
615
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
601
616
|
|
|
602
|
-
|
|
617
|
+
tween = gsap.fromTo(clone , {scale:1 , opacity:1} , {
|
|
603
618
|
scale:amount / 10 ,
|
|
604
619
|
opacity:0 ,
|
|
605
620
|
duration:dur ,
|
|
606
621
|
delay:delay ,
|
|
607
622
|
ease:easeOf(ease) ,
|
|
608
|
-
onComplete: cleanup ,
|
|
609
|
-
onInterrupt: cleanup ,
|
|
623
|
+
onComplete: () => { observer.disconnect(); cleanup(); },
|
|
624
|
+
onInterrupt: () => { observer.disconnect(); cleanup(); },
|
|
610
625
|
})
|
|
626
|
+
return tween
|
|
611
627
|
}
|
|
612
628
|
|
|
613
629
|
|
package/Boot.html
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- Boot.html — GClass boot overlay template -->
|
|
2
|
+
<!-- 1. Put your boot HTML here. This file is scanned by Tailwind (v4 auto-detects *.html, v3 add "./Boot.html" to content[]) -->
|
|
3
|
+
<!-- so any Tailwind utility + gclass-anims class (e.g. .spawn-*, .time-*) placed here is preserved in the CSS build. -->
|
|
4
|
+
<!-- 2. Boot.js will fetch and inject this file if no #boot-overlay exists in the DOM. -->
|
|
5
|
+
<!-- 3. Keep the outer id="boot-overlay" and .boot-time-N (N=seconds visible) — Boot.js reads it via readBootTime(). -->
|
|
6
|
+
|
|
7
|
+
<div id="boot-overlay" class="boot-time-2 fixed inset-0 z-[9999] grid place-items-center bg-slate-950" style="position:fixed;inset:0;z-index:9999;display:grid;place-items:center;background:#020617;">
|
|
8
|
+
<div class="flex flex-col items-center gap-4 p-8 text-center">
|
|
9
|
+
<!-- gclass-anims classes work here — Config.js:76 wiring runs inside the overlay -->
|
|
10
|
+
<h1 class="spawn-up time-1 text-4xl font-bold tracking-tight text-cyan-200 radiate radiate-z-9999 compatibility">GClass</h1>
|
|
11
|
+
<p class="spawn-fade time-1 text-sm text-slate-400">GSAP utilities</p>
|
|
12
|
+
|
|
13
|
+
<!-- Tailwind / other CSS lib classes also work — they are detected because this *.html is in tailwind content -->
|
|
14
|
+
<div class="mt-2 h-1 w-32 overflow-hidden rounded-full bg-slate-800">
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
package/Boot.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { initAnimations } from './AnimToggle.js'
|
|
2
|
+
|
|
3
|
+
// Raw JS boot overlay — framework-agnostic, no React.
|
|
4
|
+
// Shows #boot-overlay once per session, delays main DOM wiring until finished,
|
|
5
|
+
// and keeps Config.js animations usable inside the overlay.
|
|
6
|
+
// Works with any framework (vanilla, React, Vue, Svelte, Next, Nuxt, Angular) and with/without Tailwind.
|
|
7
|
+
|
|
8
|
+
let hasBooted = false
|
|
9
|
+
const BOOT_KEY = 'gclass-boot-done' // kept for optional persistence, not used for gate
|
|
10
|
+
|
|
11
|
+
const readBootTime = (el, fallback) => {
|
|
12
|
+
const cls = [...el.classList].find(c => c.startsWith('boot-time-'))
|
|
13
|
+
return cls ? Number(cls.slice('boot-time-'.length)) : fallback
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function initBoot({ enabled = false, time = 2, id = 'boot-overlay', onDone } = {}) {
|
|
17
|
+
if (typeof window === 'undefined') return () => {}
|
|
18
|
+
if (!enabled) {
|
|
19
|
+
initAnimations()
|
|
20
|
+
return () => {}
|
|
21
|
+
}
|
|
22
|
+
if (hasBooted) {
|
|
23
|
+
initAnimations()
|
|
24
|
+
return () => {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let overlay = document.getElementById(id)
|
|
28
|
+
if (!overlay) {
|
|
29
|
+
const tryFetch = async (url) => {
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(url)
|
|
32
|
+
if (res.ok) return await res.text()
|
|
33
|
+
} catch {}
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
let html = await tryFetch(new URL('./Boot.html', import.meta.url).href)
|
|
37
|
+
if (!html) html = await tryFetch('/Boot.html')
|
|
38
|
+
if (!html) html = await tryFetch('./Boot.html')
|
|
39
|
+
if (html) {
|
|
40
|
+
const tpl = document.createElement('template')
|
|
41
|
+
tpl.innerHTML = html.trim()
|
|
42
|
+
const node = tpl.content.firstElementChild
|
|
43
|
+
if (node) {
|
|
44
|
+
document.body.prepend(node)
|
|
45
|
+
overlay = document.getElementById(id) ?? node
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!overlay) {
|
|
50
|
+
initAnimations()
|
|
51
|
+
return () => {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
hasBooted = true
|
|
55
|
+
overlay.style.position = 'fixed'
|
|
56
|
+
overlay.style.inset = '0'
|
|
57
|
+
overlay.style.zIndex = '9999'
|
|
58
|
+
overlay.style.display = 'grid'
|
|
59
|
+
overlay.style.placeItems = 'center'
|
|
60
|
+
if (!overlay.style.background && !overlay.style.backgroundColor) {
|
|
61
|
+
overlay.style.background = '#020617'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Isolate: detach all body content except overlay so first initAnimations() only wires overlay
|
|
65
|
+
// This works for vanilla <main>, React #root, Vue #app, Next #__next, Nuxt, Svelte, Angular
|
|
66
|
+
// Listeners.js scans body * regardless of display:none, so must physically detach
|
|
67
|
+
const toIsolate = [...document.body.children].filter(el => {
|
|
68
|
+
if (el.id === id) return false
|
|
69
|
+
if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE' || el.tagName === 'TEMPLATE' || el.tagName === 'LINK') return false
|
|
70
|
+
if (el.id === 'next-route-announcer' || el.hasAttribute('data-nextjs')) return false
|
|
71
|
+
return true
|
|
72
|
+
})
|
|
73
|
+
const placements = toIsolate.map(el => ({ el, parent: el.parentNode, next: el.nextSibling }))
|
|
74
|
+
placements.forEach(({ el }) => el.remove())
|
|
75
|
+
const prevOverflow = document.body.style.overflow
|
|
76
|
+
document.body.style.overflow = 'hidden'
|
|
77
|
+
|
|
78
|
+
initAnimations()
|
|
79
|
+
placements.forEach(({ parent, next, el }) => {
|
|
80
|
+
if (!parent || el.isConnected) return
|
|
81
|
+
if (next && next.isConnected && next.parentNode === parent) {
|
|
82
|
+
parent.insertBefore(el, next)
|
|
83
|
+
} else {
|
|
84
|
+
parent.appendChild(el)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const duration = readBootTime(overlay, time)
|
|
89
|
+
|
|
90
|
+
const timer = setTimeout(() => {
|
|
91
|
+
overlay.style.transition = 'opacity 0.4s ease'
|
|
92
|
+
overlay.style.opacity = '0'
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
overlay.remove()
|
|
95
|
+
document.body.style.removeProperty('overflow')
|
|
96
|
+
if (prevOverflow) document.body.style.overflow = prevOverflow
|
|
97
|
+
initAnimations()
|
|
98
|
+
onDone?.()
|
|
99
|
+
}, 400)
|
|
100
|
+
}, duration * 1000)
|
|
101
|
+
|
|
102
|
+
return () => {
|
|
103
|
+
clearTimeout(timer)
|
|
104
|
+
document.body.style.removeProperty('overflow')
|
|
105
|
+
if (prevOverflow) document.body.style.overflow = prevOverflow
|
|
106
|
+
placements.forEach(({ el, parent, next }) => {
|
|
107
|
+
if (!el.isConnected && parent) {
|
|
108
|
+
if (next && next.isConnected && next.parentNode === parent) parent.insertBefore(el, next)
|
|
109
|
+
else parent.appendChild(el)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function createBootOverlay(html, opts = {}) {
|
|
116
|
+
if (document.getElementById(opts.id ?? 'boot-overlay')) return initBoot(opts)
|
|
117
|
+
if (!html) {
|
|
118
|
+
const tryFetch = async (url) => {
|
|
119
|
+
try { const r = await fetch(url); if (r.ok) return await r.text() } catch {}
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
html = await tryFetch(new URL('./Boot.html', import.meta.url).href)
|
|
123
|
+
if (!html) html = await tryFetch('/Boot.html')
|
|
124
|
+
}
|
|
125
|
+
const div = document.createElement('div')
|
|
126
|
+
div.id = opts.id ?? 'boot-overlay'
|
|
127
|
+
if (!html) {
|
|
128
|
+
div.className = `boot-time-${opts.time ?? 2}`
|
|
129
|
+
div.style.cssText = 'position:fixed;inset:0;z-index:9999;display:grid;place-items:center;background:#020617;'
|
|
130
|
+
div.innerHTML = '<h1 class="spawn-up time-1">Loading...</h1>'
|
|
131
|
+
} else {
|
|
132
|
+
const tpl = document.createElement('template')
|
|
133
|
+
tpl.innerHTML = html.trim()
|
|
134
|
+
const node = tpl.content.firstElementChild
|
|
135
|
+
if (node) {
|
|
136
|
+
document.body.prepend(node)
|
|
137
|
+
return initBoot(opts)
|
|
138
|
+
}
|
|
139
|
+
div.innerHTML = html
|
|
140
|
+
}
|
|
141
|
+
document.body.prepend(div)
|
|
142
|
+
return initBoot(opts)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export default initBoot
|
package/index.d.ts
CHANGED
|
@@ -219,4 +219,16 @@ export function radiate(delay: number, target: TweenTarget, amount: number, dur:
|
|
|
219
219
|
export function hover(delay: number, target: TweenTarget, amount: number, dur: number, ease: string): any
|
|
220
220
|
export function marquee(target: TweenTarget, dir: string, duration: number, xOffset?: number, yOffset?: number, noRepeat?: boolean): any
|
|
221
221
|
export function flip(state: any, ease: string, dur: number): any
|
|
222
|
-
export function animatecss(target: TweenTarget, dur: number, delay: number, ease: string, propertyS: any, propertySValue: any, propertyE: any, propertyEValue: any): any
|
|
222
|
+
export function animatecss(target: TweenTarget, dur: number, delay: number, ease: string, propertyS: any, propertySValue: any, propertyE: any, propertyEValue: any): any
|
|
223
|
+
// --- Boot ----------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
export interface BootOptions {
|
|
226
|
+
enabled?: boolean
|
|
227
|
+
time?: number
|
|
228
|
+
id?: string
|
|
229
|
+
onDone?: () => void
|
|
230
|
+
}
|
|
231
|
+
export function initBoot(opts?: BootOptions): Promise<() => void>
|
|
232
|
+
export function createBootOverlay(html?: string, opts?: BootOptions): Promise<() => void>
|
|
233
|
+
export default function Boot(opts?: BootOptions): Promise<() => void>
|
|
234
|
+
|
package/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export { initAnimations, toggleAnimations, enableReducedMotion, disableReducedMo
|
|
|
2
2
|
export { default as initListeners, registerComplete } from './Listeners.js'
|
|
3
3
|
export { customAnims } from './CustomAnims.js'
|
|
4
4
|
export { defaults, animations, normalize } from './Config.js'
|
|
5
|
+
export { default as Boot, initBoot, createBootOverlay } from './Boot.js'
|
|
5
6
|
export * from './Animations.js'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gclass-anims",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.15",
|
|
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",
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"Animations.js",
|
|
22
22
|
"Config.js",
|
|
23
23
|
"CustomAnims.js",
|
|
24
|
+
"Boot.js",
|
|
25
|
+
"Boot.html",
|
|
24
26
|
"README.md",
|
|
25
27
|
"LICENSE",
|
|
26
28
|
"CHANGELOG.md"
|