gclass-anims 1.0.0-beta.15 → 1.0.0-beta.16
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 +97 -1
- package/Animations.js +5 -1
- package/Boot.js +9 -142
- package/CHANGELOG.md +5 -0
- package/Config.js +2 -1
- package/Listeners.js +35 -15
- package/index.d.ts +2 -10
- package/index.js +1 -1
- package/package.json +1 -2
- package/Boot.html +0 -18
package/AnimToggle.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import initListeners from './Listeners'
|
|
2
|
+
import { defaults, animations } from './Config.js'
|
|
3
|
+
import gsap from 'gsap'
|
|
2
4
|
|
|
3
5
|
// localStorage key controlling whether the GSAP animation system is mounted.
|
|
4
6
|
const STORAGE_KEY = 'gclass-animations-enabled'
|
|
@@ -111,12 +113,106 @@ export function disableReducedMotion() {
|
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
let cleanup = null
|
|
116
|
+
let bootCleanup = null
|
|
117
|
+
let bootTimeout = null
|
|
118
|
+
let bootStyle = null
|
|
119
|
+
|
|
120
|
+
const readBootTime = (els, fallback) => {
|
|
121
|
+
let max = null
|
|
122
|
+
for (const el of els) {
|
|
123
|
+
const cls = [...el.classList].find(c => c.startsWith('boot-time-'))
|
|
124
|
+
if (cls) {
|
|
125
|
+
const n = Number(cls.slice('boot-time-'.length))
|
|
126
|
+
if (!Number.isNaN(n)) max = max === null ? n : Math.max(max, n)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return max ?? fallback
|
|
130
|
+
}
|
|
114
131
|
|
|
115
132
|
// Boots the GSAP animation system unless animations are disabled (stored "off"
|
|
116
133
|
// or reduced-motion fallback with no explicit choice). Idempotent: calling it
|
|
117
134
|
// again tears down any previous run first.
|
|
135
|
+
// Now also handles boot screen: any HTML/JSX with `.boot-up` anywhere is treated as the boot overlay.
|
|
136
|
+
// No separate initBoot needed - just call initAnimations().
|
|
137
|
+
// Boot stops all DOM rendering for defaults.bootTime (overwritten by boot-time-N class).
|
|
118
138
|
export function initAnimations() {
|
|
119
139
|
if (typeof window === 'undefined' || !getEnabled()) return
|
|
120
|
-
if (cleanup) cleanup()
|
|
140
|
+
if (cleanup) { cleanup(); cleanup = null }
|
|
141
|
+
if (bootCleanup) { bootCleanup(); bootCleanup = null }
|
|
142
|
+
if (bootTimeout) { clearTimeout(bootTimeout); bootTimeout = null }
|
|
143
|
+
if (bootStyle) { bootStyle.remove(); bootStyle = null; document.documentElement.classList.remove('gclass-booting') }
|
|
144
|
+
|
|
145
|
+
const bootEls = typeof document !== 'undefined' ? document.querySelectorAll(".boot-up") : []
|
|
146
|
+
if (!bootEls.length) {
|
|
147
|
+
// no .boot-up -> completely skip boot
|
|
148
|
+
} else if (bootEls.length > 1) {
|
|
149
|
+
console.error(`[initAnimations] Multiple .boot-up elements detected (${bootEls.length}) - skipping all boot animations`, bootEls)
|
|
150
|
+
bootEls.forEach(el => el.remove())
|
|
151
|
+
// fall through to normal initListeners without pausing DOM
|
|
152
|
+
} else {
|
|
153
|
+
const bootEl = bootEls[0]
|
|
154
|
+
const hasBootEnd = [...bootEl.classList].some(c => c.startsWith('boot-end-'))
|
|
155
|
+
if (!hasBootEnd) {
|
|
156
|
+
// no boot-end-* -> completely skip boot animation (still pause? spec says skip it)
|
|
157
|
+
// spec: skip boot-end animation if no class, but still do boot pause? user said "completely skip it if no .boot-end-<name> class is present"
|
|
158
|
+
// interpret as skip the exit animation only, still pause for bootTime
|
|
159
|
+
// To match "completely skip it" for boot-end, we just don't play exit tween
|
|
160
|
+
}
|
|
161
|
+
const bootTime = readBootTime(bootEls, defaults.bootTime ?? 2)
|
|
162
|
+
console.log(`[initAnimations] .boot-up found: ${bootEls.length} - pausing DOM for ${bootTime}s`, bootEls)
|
|
163
|
+
// stop all DOM rendering except .boot-up
|
|
164
|
+
bootStyle = document.createElement('style')
|
|
165
|
+
bootStyle.id = 'gclass-boot-style'
|
|
166
|
+
bootStyle.textContent = `html.gclass-booting{visibility:hidden} html.gclass-booting .boot-up,html.gclass-booting .boot-up *{visibility:visible} html.gclass-booting .boot-up{position:fixed;inset:0;z-index:9999;display:grid;place-items:center}`
|
|
167
|
+
document.head.appendChild(bootStyle)
|
|
168
|
+
document.documentElement.classList.add('gclass-booting')
|
|
169
|
+
// ensure boot els are visible even if nested inside hidden ancestors
|
|
170
|
+
bootEls.forEach(el => { el.style.visibility = 'visible' })
|
|
171
|
+
|
|
172
|
+
// animations inside boot screen must play while rest of DOM is hidden - init scoped to boot-up
|
|
173
|
+
bootCleanup = initListeners(bootEl)
|
|
174
|
+
|
|
175
|
+
bootTimeout = setTimeout(() => {
|
|
176
|
+
const bootEndCls = [...bootEl.classList].find(c => c.startsWith('boot-end-'))
|
|
177
|
+
if (!bootEndCls) {
|
|
178
|
+
// no boot-end -> skip exit animation, just remove
|
|
179
|
+
bootCleanup?.(); bootCleanup = null
|
|
180
|
+
document.documentElement.classList.remove('gclass-booting')
|
|
181
|
+
bootStyle?.remove(); bootStyle = null
|
|
182
|
+
bootEls.forEach(el => el.remove())
|
|
183
|
+
bootTimeout = null
|
|
184
|
+
cleanup = initListeners()
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
const name = bootEndCls.slice('boot-end-'.length) // e.g. spawn-blur
|
|
188
|
+
const cfg = animations.find(a => a.sel === '.' + name)
|
|
189
|
+
const from = cfg?.from
|
|
190
|
+
const easeCl = [...bootEl.classList].find(c => c.startsWith('ease-'))
|
|
191
|
+
const ease = easeCl ? easeCl.split('-')[1] : defaults.ease
|
|
192
|
+
const dur = readBootTime([bootEl], defaults.effectDuration ?? 1) // reuse boot-time- or fallback to effectDuration; if boot-time used for pause, reuse same value for exit unless overridden
|
|
193
|
+
// Actually use boot-end-time-N if present, else effectDuration
|
|
194
|
+
const endTimeCls = [...bootEl.classList].find(c => c.startsWith('boot-end-time-'))
|
|
195
|
+
const endDur = endTimeCls ? Number(endTimeCls.slice('boot-end-time-'.length)) : dur
|
|
196
|
+
|
|
197
|
+
const finish = () => {
|
|
198
|
+
bootCleanup?.(); bootCleanup = null
|
|
199
|
+
document.documentElement.classList.remove('gclass-booting')
|
|
200
|
+
bootStyle?.remove(); bootStyle = null
|
|
201
|
+
bootEls.forEach(el => el.remove())
|
|
202
|
+
bootTimeout = null
|
|
203
|
+
cleanup = initListeners()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!cfg || !from) {
|
|
207
|
+
console.warn(`[initAnimations] boot-end-${name} has no from state - removing without animation`)
|
|
208
|
+
finish()
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
// play spawn in reverse (visible -> hidden) before removing
|
|
212
|
+
gsap.to(bootEl, { ...from, duration: endDur, ease, onComplete: finish })
|
|
213
|
+
}, bootTime * 1000)
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
121
217
|
cleanup = initListeners()
|
|
122
218
|
}
|
package/Animations.js
CHANGED
|
@@ -79,7 +79,11 @@ export function expandA (target , delay , dur , ease){
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
export function typewriter (target , text , dur , delay , ease){
|
|
82
|
-
|
|
82
|
+
// Any element using TextPlugin gets pre-wrap so "\n" from VSCode Enter or JS strings
|
|
83
|
+
// renders as a real line break and any innerHTML formatting is preserved.
|
|
84
|
+
gsap.utils.toArray(target).forEach(el => { if (el.style) el.style.whiteSpace = "pre-wrap" })
|
|
85
|
+
const value = String(text).replace(/\n/g, "<br>")
|
|
86
|
+
return gsap.fromTo(target , {text:""} , {ease:easeOf(ease) , duration:dur , delay:delay , text:{value}})
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
export function spawnSpinCCW (target , delay , dur , ease){
|
package/Boot.js
CHANGED
|
@@ -1,145 +1,12 @@
|
|
|
1
1
|
import { initAnimations } from './AnimToggle.js'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
//
|
|
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
|
-
}
|
|
3
|
+
export function Boot(){
|
|
4
|
+
const els = document.querySelectorAll(".boot-up")
|
|
5
|
+
console.log(`[Boot -> initAnimations] .boot-up count: ${els.length}`, els)
|
|
6
|
+
// rewired: Boot now just delegates to initAnimations (single entry point)
|
|
7
|
+
return initAnimations()
|
|
113
8
|
}
|
|
114
|
-
|
|
115
|
-
export
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
9
|
+
// keep named exports wired so index.js / dev don't break during test
|
|
10
|
+
export const initBoot = Boot
|
|
11
|
+
export const createBootOverlay = Boot
|
|
12
|
+
export default Boot
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `gclass-anims` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.0-beta.16] - 2026-09-1
|
|
6
|
+
- Added a new `.boot-up` class for boot up animations
|
|
7
|
+
- Fixed text animations not taking formatting into account
|
|
8
|
+
|
|
9
|
+
|
|
5
10
|
## [1.0.0-beta.13] - 2026-08-27
|
|
6
11
|
- Added a new `.fill-svg` modifier for the `.draw` and `.draw-split` classes that fills the SVG after it has been drawn.
|
|
7
12
|
|
package/Config.js
CHANGED
|
@@ -70,7 +70,8 @@ export const defaults = {
|
|
|
70
70
|
typewriterSplitCharDuration: 0.05,
|
|
71
71
|
minTextPartDuration: 0.3,
|
|
72
72
|
revealDelay:0,
|
|
73
|
-
characterlist:"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
|
|
73
|
+
characterlist:"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz",
|
|
74
|
+
bootTime: 5,
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
export const animations = [
|
package/Listeners.js
CHANGED
|
@@ -93,9 +93,21 @@ const invokePlay = (config, el, delay, dur, ease) => {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
export default function initListeners() {
|
|
96
|
+
export default function initListeners(root = document) {
|
|
97
97
|
gsap.registerPlugin(TextPlugin, ScrollTrigger, SplitText)
|
|
98
98
|
|
|
99
|
+
// helper to scope queries to root (for boot screen: only boot-up subtree animates during boot)
|
|
100
|
+
const qAll = (sel) => {
|
|
101
|
+
if (root === document || root === document.documentElement || root === document.body) return gsap.utils.toArray(sel)
|
|
102
|
+
if (sel === "body *") return gsap.utils.toArray(root.querySelectorAll("*"))
|
|
103
|
+
try {
|
|
104
|
+
const els = [...(root.querySelectorAll ? root.querySelectorAll(sel) : [])]
|
|
105
|
+
if (root.matches?.(sel)) els.unshift(root)
|
|
106
|
+
// handle comma selectors where root itself may match one part
|
|
107
|
+
return els
|
|
108
|
+
} catch { return gsap.utils.toArray(sel) }
|
|
109
|
+
}
|
|
110
|
+
|
|
99
111
|
const registeredListeners = []
|
|
100
112
|
const onCompleteTweens = []
|
|
101
113
|
const addListener = (el, type, fn) => {
|
|
@@ -358,7 +370,7 @@ export default function initListeners() {
|
|
|
358
370
|
spawnConfigs.map(({ sel }) => "." + TEXT_PREFIX + sel.slice(1)).join(",")
|
|
359
371
|
|
|
360
372
|
const getOrderDelay = (el, priority) => {
|
|
361
|
-
const samepri =
|
|
373
|
+
const samepri = qAll(orderSelector())
|
|
362
374
|
.filter((e) => {
|
|
363
375
|
if (!e.classList.contains("order")) return false
|
|
364
376
|
const match = [...e.classList].find(p => p.startsWith("priority-"))
|
|
@@ -472,7 +484,10 @@ export default function initListeners() {
|
|
|
472
484
|
// would render it in its connecting form instead of its correct END form),
|
|
473
485
|
// but it should still be split into its own span so it animates too.
|
|
474
486
|
const RTL_LETTER = /[\u0621-\u064A\u066E-\u06D5\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/
|
|
475
|
-
const getRTLCharSplit = (el) =>
|
|
487
|
+
const getRTLCharSplit = (el) => {
|
|
488
|
+
// Any element using SplitText gets pre-wrap so "\n" and formatting is preserved
|
|
489
|
+
if (el.style) el.style.whiteSpace = "pre-wrap"
|
|
490
|
+
return new SplitText(el, {
|
|
476
491
|
type: "words",
|
|
477
492
|
linesClass: "gsap-line",
|
|
478
493
|
wordsClass: "gsap-word",
|
|
@@ -555,6 +570,7 @@ export default function initListeners() {
|
|
|
555
570
|
self.words.length = 0
|
|
556
571
|
},
|
|
557
572
|
})
|
|
573
|
+
}
|
|
558
574
|
|
|
559
575
|
// Flex targets can't be split directly: the split parts would become
|
|
560
576
|
// flex ITEMS, so justify-content/gap would apply per letter, whitespace-
|
|
@@ -595,6 +611,7 @@ export default function initListeners() {
|
|
|
595
611
|
}
|
|
596
612
|
|
|
597
613
|
const getSplit = (el, gran) => {
|
|
614
|
+
if (el.style) el.style.whiteSpace = "pre-wrap"
|
|
598
615
|
let s = splitCache.get(el)
|
|
599
616
|
const rtlChars = gran === "chars" && isRTLText(el)
|
|
600
617
|
const key = rtlChars ? "rtl-chars" : gran
|
|
@@ -604,6 +621,9 @@ export default function initListeners() {
|
|
|
604
621
|
? getRTLCharSplit(el)
|
|
605
622
|
: new SplitText(el, {
|
|
606
623
|
type: gran,
|
|
624
|
+
tag: "span",
|
|
625
|
+
reduceWhiteSpace: false,
|
|
626
|
+
|
|
607
627
|
linesClass: "gsap-line",
|
|
608
628
|
wordsClass: "gsap-word",
|
|
609
629
|
charsClass: "gsap-char",
|
|
@@ -739,7 +759,7 @@ export default function initListeners() {
|
|
|
739
759
|
// created before any scroll/scroll-progress trigger measures its position.
|
|
740
760
|
// Setting them up here (before the trigger pass below) keeps offsets correct
|
|
741
761
|
// and lets the single ScrollTrigger.refresh() at the end reconcile layout.
|
|
742
|
-
|
|
762
|
+
qAll(".pin").forEach(setupPin)
|
|
743
763
|
|
|
744
764
|
// Scroll-driven extras - class-driven ScrollTrigger behaviours that don't
|
|
745
765
|
// fit the spawn/loop machinery (no `play`/`build`), handled like `.pin`:
|
|
@@ -832,7 +852,7 @@ export default function initListeners() {
|
|
|
832
852
|
scrollTriggers.push(t.scrollTrigger)
|
|
833
853
|
}
|
|
834
854
|
}
|
|
835
|
-
|
|
855
|
+
qAll('[class^="parallax-"],[class*=" parallax-"], .progress-bar, .scroll-fill, .scroll-fade-bg, .scroll-horizontal').forEach(setupScrollDriven)
|
|
836
856
|
|
|
837
857
|
// Scroller resolution: a `.scroll`/`.scroll-progress` element inside a
|
|
838
858
|
// `.scroll-frame` container binds its trigger to THAT box instead of the
|
|
@@ -1001,7 +1021,7 @@ export default function initListeners() {
|
|
|
1001
1021
|
})
|
|
1002
1022
|
scrollTriggers.push(st)
|
|
1003
1023
|
}
|
|
1004
|
-
|
|
1024
|
+
qAll(".scroll, .scroll-progress").forEach(setupScroll)
|
|
1005
1025
|
|
|
1006
1026
|
// SplitText scroll variants: `.spawn-text-<spawn>.scroll` plays the per-part
|
|
1007
1027
|
// tween when the element enters the viewport and reverses on exit.
|
|
@@ -1009,7 +1029,7 @@ export default function initListeners() {
|
|
|
1009
1029
|
if (isTypewriter || text === false) return
|
|
1010
1030
|
const tSel = "." + TEXT_PREFIX + sel.slice(1)
|
|
1011
1031
|
|
|
1012
|
-
|
|
1032
|
+
qAll(tSel + ".scroll:not(.scroll-progress)").forEach((el) => {
|
|
1013
1033
|
if (isReduced(el)) return
|
|
1014
1034
|
const { delay, duration } = readTiming(el)
|
|
1015
1035
|
const ease = getEase(el)
|
|
@@ -1034,7 +1054,7 @@ export default function initListeners() {
|
|
|
1034
1054
|
}))
|
|
1035
1055
|
})
|
|
1036
1056
|
|
|
1037
|
-
|
|
1057
|
+
qAll(tSel + ".scroll-progress").forEach((el) => {
|
|
1038
1058
|
if (isReduced(el)) return
|
|
1039
1059
|
const ease = getEase(el)
|
|
1040
1060
|
const parts = getParts(el, getGranularity(el))
|
|
@@ -1087,7 +1107,7 @@ export default function initListeners() {
|
|
|
1087
1107
|
|
|
1088
1108
|
spawnConfigs.forEach((config) => {
|
|
1089
1109
|
const { sel, typewriter: isTypewriter, typewriterSplit } = config
|
|
1090
|
-
|
|
1110
|
+
qAll(sel).forEach((el) => {
|
|
1091
1111
|
if (el.classList.contains("scroll") || el.classList.contains("scroll-progress")) return
|
|
1092
1112
|
if (isPreserved(el)) return
|
|
1093
1113
|
if (isReduced(el)) return
|
|
@@ -1122,7 +1142,7 @@ export default function initListeners() {
|
|
|
1122
1142
|
spawnConfigs.forEach(({ sel, from, typewriter: isTypewriter, text }) => {
|
|
1123
1143
|
if (isTypewriter || text === false) return
|
|
1124
1144
|
const tSel = "." + TEXT_PREFIX + sel.slice(1)
|
|
1125
|
-
|
|
1145
|
+
qAll(tSel).forEach((el) => {
|
|
1126
1146
|
if (el.classList.contains("scroll") || el.classList.contains("scroll-progress")) return
|
|
1127
1147
|
if (isPreserved(el)) return
|
|
1128
1148
|
if (isReduced(el)) return
|
|
@@ -1487,7 +1507,7 @@ export default function initListeners() {
|
|
|
1487
1507
|
|
|
1488
1508
|
// Bind click + loop animations to every element present at load, tagging
|
|
1489
1509
|
// them so the MutationObserver below never double-binds a dynamic one.
|
|
1490
|
-
|
|
1510
|
+
qAll("body *").forEach((el) => {
|
|
1491
1511
|
if (el.dataset?.gsapSetup) return
|
|
1492
1512
|
el.dataset.gsapSetup = "1"
|
|
1493
1513
|
setupClicks(el)
|
|
@@ -1608,7 +1628,7 @@ export default function initListeners() {
|
|
|
1608
1628
|
appearObserver.observe(document.body, { childList: true, subtree: true })
|
|
1609
1629
|
|
|
1610
1630
|
// Capture any .leave elements already present so they can exit later
|
|
1611
|
-
|
|
1631
|
+
qAll(".leave").forEach(captureLeave)
|
|
1612
1632
|
|
|
1613
1633
|
const leaveObserver = new MutationObserver((mutations) => {
|
|
1614
1634
|
mutations.forEach((mutation) => {
|
|
@@ -1625,7 +1645,7 @@ export default function initListeners() {
|
|
|
1625
1645
|
if (positionTick) return
|
|
1626
1646
|
positionTick = true
|
|
1627
1647
|
requestAnimationFrame(() => {
|
|
1628
|
-
|
|
1648
|
+
qAll(".leave").forEach((el) => {
|
|
1629
1649
|
const s = leaveStates.get(el)
|
|
1630
1650
|
if (s) s.rect = el.getBoundingClientRect()
|
|
1631
1651
|
})
|
|
@@ -1666,7 +1686,7 @@ export default function initListeners() {
|
|
|
1666
1686
|
// so dynamically-added .appear elements can animate under the new
|
|
1667
1687
|
// engine run. data-gsap-preserved (cross-reset memory) and
|
|
1668
1688
|
// data-gsap-ghost (detached .leave clones) are intentionally KEPT.
|
|
1669
|
-
|
|
1689
|
+
qAll('[data-gsap-scroll],[data-gsap-setup],[data-gsap-pinned],[data-gsap-scroll-driven]').forEach((el) => {
|
|
1670
1690
|
delete el.dataset.gsapScroll
|
|
1671
1691
|
delete el.dataset.gsapSetup
|
|
1672
1692
|
delete el.dataset.gsapPinned
|
|
@@ -1681,7 +1701,7 @@ export default function initListeners() {
|
|
|
1681
1701
|
document.querySelectorAll('[data-gsap-radiate]').forEach((n) => n.remove())
|
|
1682
1702
|
cssTweens.forEach((t) => t?.kill())
|
|
1683
1703
|
cssTweens.length = 0
|
|
1684
|
-
|
|
1704
|
+
qAll(".typewriter, .scramble").forEach(el => {
|
|
1685
1705
|
// Preserved-region typewriters/scrambles stay as-is (already at
|
|
1686
1706
|
// their end state); finalize the rest so a mid-type/mid-scramble
|
|
1687
1707
|
// kill can't strand partial or garbled text where the next run's
|
package/index.d.ts
CHANGED
|
@@ -220,15 +220,7 @@ export function hover(delay: number, target: TweenTarget, amount: number, dur: n
|
|
|
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
222
|
export function animatecss(target: TweenTarget, dur: number, delay: number, ease: string, propertyS: any, propertySValue: any, propertyE: any, propertyEValue: any): any
|
|
223
|
-
// --- Boot ----------------------------------------------------------------
|
|
224
223
|
|
|
225
|
-
|
|
226
|
-
|
|
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>
|
|
224
|
+
|
|
225
|
+
export default function Boot()
|
|
234
226
|
|
package/index.js
CHANGED
|
@@ -2,5 +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 {
|
|
5
|
+
export { Boot } from './Boot.js'
|
|
6
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.16",
|
|
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",
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"Config.js",
|
|
23
23
|
"CustomAnims.js",
|
|
24
24
|
"Boot.js",
|
|
25
|
-
"Boot.html",
|
|
26
25
|
"README.md",
|
|
27
26
|
"LICENSE",
|
|
28
27
|
"CHANGELOG.md"
|
package/Boot.html
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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>
|