gclass-anims 1.0.0-beta.21 → 1.0.0-beta.22.1
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 +1 -1
- package/Animations.js +71 -17
- package/CHANGELOG.md +13 -0
- package/Config.js +1 -0
- package/CustomAnims.js +1 -1
- package/Listeners.js +250 -68
- package/dist/gclass.cjs +3259 -0
- package/dist/gclass.esm.js +3199 -0
- package/package.json +12 -5
package/Listeners.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import gsap from 'gsap'
|
|
1
|
+
import { gsap } from 'gsap'
|
|
2
2
|
import { SpawnV, verticalmove, expandmove, magnet, magnet3d, reset, typewriter, countTargetVars, stashText, scrambleVars } from './Animations.js'
|
|
3
3
|
import { customAnims } from './CustomAnims.js'
|
|
4
4
|
import { defaults, normalize } from './Config.js'
|
|
@@ -12,7 +12,79 @@ const TEXT_PREFIX_LEN = TEXT_PREFIX.length
|
|
|
12
12
|
// The engine is fully config-driven. All animation definitions live in
|
|
13
13
|
// Config.js; here we just normalise them into the two internal views the
|
|
14
14
|
// machinery consumes (spawn/entrance + loop) plus the raw `all` list.
|
|
15
|
-
|
|
15
|
+
// NOTE: normalized inside initListeners per-call (see beta.22) so runtime
|
|
16
|
+
// customAnims.push() before next init is picked up.
|
|
17
|
+
|
|
18
|
+
// --- Breakpoint support (non-conflicting with Tailwind/Bootstrap) -----------
|
|
19
|
+
// defaults.breakpoints = {xs:475, s:640, m:768, l:1024, xl:1280} -> min-width
|
|
20
|
+
// Single-letter s/m/l avoids collision with Tailwind's sm/md/lg. Usage:
|
|
21
|
+
// <div class="spawn-up"> always
|
|
22
|
+
// <div class="m:spawn-up"> from 768px up
|
|
23
|
+
// <div class="l:spawn-up"> from 1024px up
|
|
24
|
+
// <div class="xs:spawn-up"> from 475px up
|
|
25
|
+
// Colon is valid in classList (class="m:spawn-up") and checked via
|
|
26
|
+
// classList.contains - never via unescaped querySelector (":pseudo" would break).
|
|
27
|
+
const bpEntries = Object.entries(defaults.breakpoints || {}).sort((a, b) => a[1] - b[1])
|
|
28
|
+
const bpNames = bpEntries.map(([k]) => k)
|
|
29
|
+
const bpMap = Object.fromEntries(bpEntries)
|
|
30
|
+
const bpPrefixRE = bpNames.length ? new RegExp(`^(${bpNames.join('|')}):(.+)$`) : /^$^/
|
|
31
|
+
const isBreakpointActive = (bp) => {
|
|
32
|
+
if (!bp) return true
|
|
33
|
+
const px = bpMap[bp]
|
|
34
|
+
if (px == null) return true
|
|
35
|
+
if (typeof window === 'undefined' || !window.matchMedia) return true
|
|
36
|
+
return window.matchMedia(`(min-width: ${px}px)`).matches
|
|
37
|
+
}
|
|
38
|
+
const mqForBp = (bp) => `(min-width: ${bpMap[bp]}px)`
|
|
39
|
+
// Does el carry `bp:base` and is that bp currently active? Also handles base without prefix.
|
|
40
|
+
const elementMatchesSel = (el, sel) => {
|
|
41
|
+
const base = sel.slice(1) // ".spawn-up" -> "spawn-up"
|
|
42
|
+
if (el.classList.contains(base)) return true
|
|
43
|
+
for (const bp of bpNames) {
|
|
44
|
+
if (el.classList.contains(`${bp}:${base}`) && isBreakpointActive(bp)) return true
|
|
45
|
+
}
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
const hasGClass = (el, name) => {
|
|
49
|
+
if (el.classList.contains(name)) return true
|
|
50
|
+
for (const bp of bpNames) if (el.classList.contains(`${bp}:${name}`) && isBreakpointActive(bp)) return true
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
// If el has ANY bp:* class, it is breakpoint-gated. When gated, require at least one active variant.
|
|
54
|
+
const isElBreakpointActive = (el) => {
|
|
55
|
+
let hasBp = false
|
|
56
|
+
for (const c of el.classList) {
|
|
57
|
+
const m = c.match(bpPrefixRE)
|
|
58
|
+
if (!m) continue
|
|
59
|
+
hasBp = true
|
|
60
|
+
if (isBreakpointActive(m[1])) return true
|
|
61
|
+
}
|
|
62
|
+
return !hasBp // no bp prefix -> always active
|
|
63
|
+
}
|
|
64
|
+
// Collect all elements matching sel OR its bp variants that are currently active.
|
|
65
|
+
// Uses manual classList scan instead of querySelector(".m\\:spawn-up") to avoid escaping issues.
|
|
66
|
+
const qAllBp = (sel, qAll) => {
|
|
67
|
+
const base = sel.slice(1)
|
|
68
|
+
const all = qAll("body *")
|
|
69
|
+
return all.filter(el => elementMatchesSel(el, sel) || (() => {
|
|
70
|
+
// also handle spawn-text-* where TEXT_PREFIX is retained: sel=".spawn-text-spawn-up" -> base="spawn-text-spawn-up"
|
|
71
|
+
// already covered by elementMatchesSel
|
|
72
|
+
return false
|
|
73
|
+
})())
|
|
74
|
+
}
|
|
75
|
+
// For generic qAll(sel) calls that should be breakpoint-aware, use this wrapper.
|
|
76
|
+
// Preserves DOM order (critical for .order stagger) and respects root scoping (boot screen).
|
|
77
|
+
const wrapQAll = (qAll) => (sel) => {
|
|
78
|
+
if (sel === "body *") return qAll(sel)
|
|
79
|
+
const parts = sel.split(',').map(s => s.trim()).filter(Boolean)
|
|
80
|
+
// Filter all candidates in DOM order, matching base or active bp: variant via elementMatchesSel
|
|
81
|
+
// This keeps .order sequence top-to-bottom instead of grouping by sel type.
|
|
82
|
+
return qAll("body *").filter(el => parts.some(p => {
|
|
83
|
+
// p is like ".spawn-up" or ".spawn-text-spawn-up"
|
|
84
|
+
// elementMatchesSel handles both base and bp:base when active
|
|
85
|
+
try { return elementMatchesSel(el, p) } catch { return false }
|
|
86
|
+
}))
|
|
87
|
+
}
|
|
16
88
|
|
|
17
89
|
// --- Named onComplete handler registry -------------------------------------
|
|
18
90
|
// `on-<kind>-complete-<name>` classes resolve `<name>` to a function here
|
|
@@ -101,6 +173,8 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
101
173
|
}
|
|
102
174
|
throttlePerFrame = Number(throttlePerFrame) || 0 // 0 = no throttling (default)
|
|
103
175
|
gsap.registerPlugin(TextPlugin, ScrollTrigger, SplitText)
|
|
176
|
+
// Re-normalize per init so runtime customAnims.push() is picked up (beta.22)
|
|
177
|
+
const { all: animAll, spawnConfigs, loopConfigs } = normalize(customAnims)
|
|
104
178
|
|
|
105
179
|
// helper to scope queries to root (for boot screen: only boot-up subtree animates during boot)
|
|
106
180
|
const qAll = (sel) => {
|
|
@@ -113,6 +187,44 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
113
187
|
return els
|
|
114
188
|
} catch { return gsap.utils.toArray(sel) }
|
|
115
189
|
}
|
|
190
|
+
// breakpoint-aware helpers (s/m/l/xl) - s/m/l avoids Tailwind sm/md/lg collision
|
|
191
|
+
const mm = gsap.matchMedia()
|
|
192
|
+
const breakpointContexts = [] // track mm contexts for teardown
|
|
193
|
+
const getGateBpForSel = (el, sel) => {
|
|
194
|
+
const base = sel.slice(1)
|
|
195
|
+
for (const bp of bpNames) if (el.classList.contains(`${bp}:${base}`)) return bp
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
const getElGateBp = (el) => {
|
|
199
|
+
for (const c of el.classList) {
|
|
200
|
+
const m = c.match(bpPrefixRE)
|
|
201
|
+
if (m) return m[1]
|
|
202
|
+
}
|
|
203
|
+
return null
|
|
204
|
+
}
|
|
205
|
+
const runWithBreakpoint = (el, fn) => {
|
|
206
|
+
const bp = getElGateBp(el)
|
|
207
|
+
if (!bp) { fn(); return }
|
|
208
|
+
const mq = mqForBp(bp)
|
|
209
|
+
const ret = mm.add(mq, fn)
|
|
210
|
+
breakpointContexts.push(ret)
|
|
211
|
+
}
|
|
212
|
+
const runWithBreakpointForSel = (el, sel, fn) => {
|
|
213
|
+
const bp = getGateBpForSel(el, sel)
|
|
214
|
+
if (!bp) { fn(); return }
|
|
215
|
+
const mq = mqForBp(bp)
|
|
216
|
+
const ret = mm.add(mq, fn)
|
|
217
|
+
breakpointContexts.push(ret)
|
|
218
|
+
}
|
|
219
|
+
// All elements that have base OR any bp:base (regardless of active) - for mm registration
|
|
220
|
+
const qAllAllVariants = (sel) => {
|
|
221
|
+
const base = sel.slice(1)
|
|
222
|
+
// include base + any variant
|
|
223
|
+
const all = qAll("body *")
|
|
224
|
+
return all.filter(el => el.classList.contains(base) || bpNames.some(bp => el.classList.contains(`${bp}:${base}`)))
|
|
225
|
+
}
|
|
226
|
+
// Active-only view (used for order calculations that must reflect current viewport)
|
|
227
|
+
const qAllActive = wrapQAll(qAll)
|
|
116
228
|
|
|
117
229
|
const registeredListeners = []
|
|
118
230
|
const onCompleteTweens = []
|
|
@@ -120,13 +232,41 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
120
232
|
el.addEventListener(type, fn)
|
|
121
233
|
registeredListeners.push({ el, type, fn })
|
|
122
234
|
}
|
|
235
|
+
// --- Breakpoint-aware modifiers (amount-N, time-N, priority-N, etc.) --------
|
|
236
|
+
// Supports `m:amount-20`, `l:time-2`, `xl:ease-bounce` etc.
|
|
237
|
+
// Mobile-first: larger active breakpoint wins over smaller/base.
|
|
238
|
+
// e.g. class="amount-10 m:amount-20 l:amount-30" -> 10@xs/s, 20@m, 30@l/xl
|
|
239
|
+
const getActivePrefixedClass = (el, prefix) => {
|
|
240
|
+
let best = null
|
|
241
|
+
let bestPx = -2
|
|
242
|
+
for (const c of el.classList) {
|
|
243
|
+
let bp = null
|
|
244
|
+
let core = c
|
|
245
|
+
const m = c.match(bpPrefixRE)
|
|
246
|
+
if (m) { bp = m[1]; core = m[2] }
|
|
247
|
+
if (!core.startsWith(prefix)) continue
|
|
248
|
+
if (bp && !isBreakpointActive(bp)) continue
|
|
249
|
+
const px = bp ? bpMap[bp] : -1 // base = -1, xs=475 etc.
|
|
250
|
+
if (px > bestPx) { best = c; bestPx = px; }
|
|
251
|
+
}
|
|
252
|
+
return best
|
|
253
|
+
}
|
|
254
|
+
const extractNumber = (cls, prefix) => {
|
|
255
|
+
const idx = cls.indexOf(prefix)
|
|
256
|
+
if (idx === -1) return NaN
|
|
257
|
+
return Number(cls.slice(idx + prefix.length))
|
|
258
|
+
}
|
|
123
259
|
const readClassNumber = (el, prefix, fallback) => {
|
|
124
|
-
const match =
|
|
125
|
-
|
|
260
|
+
const match = getActivePrefixedClass(el, prefix)
|
|
261
|
+
if (!match) return fallback
|
|
262
|
+
const n = extractNumber(match, prefix)
|
|
263
|
+
return Number.isNaN(n) ? fallback : n
|
|
126
264
|
}
|
|
127
265
|
const getEase = (el) => {
|
|
128
|
-
const match =
|
|
129
|
-
|
|
266
|
+
const match = getActivePrefixedClass(el, "ease-")
|
|
267
|
+
if (!match) return defaults.ease
|
|
268
|
+
const idx = match.indexOf("ease-")
|
|
269
|
+
return match.slice(idx + "ease-".length) || defaults.ease
|
|
130
270
|
}
|
|
131
271
|
|
|
132
272
|
// Reduced-motion support. `.reduced` is a per-element opt-out: when the
|
|
@@ -134,7 +274,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
134
274
|
// completely un-animated (its spawn/loop/click/scroll/setup all skip).
|
|
135
275
|
const reducedMotion = () =>
|
|
136
276
|
(typeof window !== "undefined" && window.matchMedia?.(`(prefers-reduced-motion: reduce)`)?.matches) ?? false
|
|
137
|
-
const isReduced = (el) => reducedMotion() && el
|
|
277
|
+
const isReduced = (el) => reducedMotion() && hasGClass(el, "reduced")
|
|
138
278
|
|
|
139
279
|
// `.preserve` keeps an already-rendered element (e.g. one that persists
|
|
140
280
|
// in a shared layout across route changes) from being re-animated when
|
|
@@ -177,11 +317,20 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
177
317
|
// Leave animations derive from spawnConfigs so adding an entry here
|
|
178
318
|
// automatically enables its leave/exit reverse too (single source of truth).
|
|
179
319
|
const findSpawn = (el) => {
|
|
180
|
-
const direct = spawnConfigs.find(({ sel }) => el
|
|
320
|
+
const direct = spawnConfigs.find(({ sel }) => elementMatchesSel(el, sel))
|
|
181
321
|
if (direct) return direct
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
322
|
+
// handle TEXT_PREFIX with optional bp: prefix (e.g. "m:spawn-text-spawn-up")
|
|
323
|
+
for (const c of el.classList) {
|
|
324
|
+
let base = c
|
|
325
|
+
let bp = null
|
|
326
|
+
const m = c.match(bpPrefixRE)
|
|
327
|
+
if (m) { bp = m[1]; base = m[2] }
|
|
328
|
+
if (!base.startsWith(TEXT_PREFIX)) continue
|
|
329
|
+
if (bp && !isBreakpointActive(bp)) continue
|
|
330
|
+
const found = spawnConfigs.find(({ sel }) => sel === "." + base.slice(TEXT_PREFIX_LEN))
|
|
331
|
+
if (found) return found
|
|
332
|
+
}
|
|
333
|
+
return null
|
|
185
334
|
}
|
|
186
335
|
|
|
187
336
|
const isGhost = (el) => el?.dataset?.gsapGhost === "1"
|
|
@@ -196,7 +345,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
196
345
|
const leaveStates = new WeakMap()
|
|
197
346
|
|
|
198
347
|
const captureLeave = (node) => {
|
|
199
|
-
if (!node
|
|
348
|
+
if (!hasGClass(node, "leave")) return
|
|
200
349
|
const config = findSpawn(node)
|
|
201
350
|
if (!config || config.typewriter) return
|
|
202
351
|
leaveStates.set(node, {
|
|
@@ -227,7 +376,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
227
376
|
// Capture the element's first (resting) bounds. A later layout change
|
|
228
377
|
// morphs from this snapshot to the live position - a vanilla FLIP.
|
|
229
378
|
const captureFlip = (node) => {
|
|
230
|
-
if (!node
|
|
379
|
+
if (!hasGClass(node, "flip")) return
|
|
231
380
|
const config = findSpawn(node)
|
|
232
381
|
if (!config || config.typewriter) return
|
|
233
382
|
if (flipping.has(node)) return
|
|
@@ -350,8 +499,14 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
350
499
|
|
|
351
500
|
const collectLeave = (node) => {
|
|
352
501
|
if (!node || node.nodeType !== 1) return []
|
|
353
|
-
if (node.classList
|
|
354
|
-
|
|
502
|
+
if (node.classList && hasGClass(node, "leave")) return [node]
|
|
503
|
+
// also consider bp:leave variants - fallback to manual filter
|
|
504
|
+
const found = gsap.utils.toArray(node.querySelectorAll?.(".leave") || [])
|
|
505
|
+
// add bp:leave matches
|
|
506
|
+
qAll("body *").filter(el => hasGClass(el, "leave") && node.contains?.(el) && !found.includes(el)).forEach(el => found.push(el))
|
|
507
|
+
// check for bp:leave on node itself via manual scan if not in found
|
|
508
|
+
if (hasGClass(node, "leave") && !found.includes(node)) found.unshift(node)
|
|
509
|
+
return found
|
|
355
510
|
}
|
|
356
511
|
|
|
357
512
|
// Refresh the cached rect to the element's RESTING position (after its
|
|
@@ -376,14 +531,14 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
376
531
|
spawnConfigs.map(({ sel }) => "." + TEXT_PREFIX + sel.slice(1)).join(",")
|
|
377
532
|
|
|
378
533
|
const getOrderDelay = (el, priority) => {
|
|
379
|
-
const samepri =
|
|
534
|
+
const samepri = qAllActive(orderSelector())
|
|
380
535
|
.filter((e) => {
|
|
381
|
-
if (!e
|
|
536
|
+
if (!hasGClass(e, "order")) return false
|
|
382
537
|
const match = [...e.classList].find(p => p.startsWith("priority-"))
|
|
383
538
|
return (match ? Number(match.split("-")[1]) : 0) === priority
|
|
384
539
|
})
|
|
385
540
|
let order = samepri.indexOf(el)
|
|
386
|
-
if (el
|
|
541
|
+
if (hasGClass(el, "reverse")) {
|
|
387
542
|
order = samepri.length - 1 - order
|
|
388
543
|
}
|
|
389
544
|
return order / defaults.orderDivide
|
|
@@ -392,7 +547,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
392
547
|
const readTiming = (el) => {
|
|
393
548
|
const priority = readClassNumber(el, "priority-", 0)
|
|
394
549
|
return {
|
|
395
|
-
delay: el
|
|
550
|
+
delay: hasGClass(el, "order")
|
|
396
551
|
? getOrderDelay(el, priority)
|
|
397
552
|
: priority * defaults.spawnDelayMultiplier,
|
|
398
553
|
duration: readClassNumber(el, "time-", 1),
|
|
@@ -465,9 +620,9 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
465
620
|
// default). Default (no class) is per-WORD: far fewer split nodes, so
|
|
466
621
|
// the per-part spawn is much cheaper to animate and paint.
|
|
467
622
|
const getGranularity = (el) => {
|
|
468
|
-
if (el
|
|
469
|
-
if (el
|
|
470
|
-
if (el
|
|
623
|
+
if (hasGClass(el, "lines")) return "lines"
|
|
624
|
+
if (hasGClass(el, "words")) return "words"
|
|
625
|
+
if (hasGClass(el, "letter")) return "chars"
|
|
471
626
|
return "words"
|
|
472
627
|
}
|
|
473
628
|
// Cursive RTL scripts (Arabic/Persian) render each letter as a distinct
|
|
@@ -673,7 +828,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
673
828
|
// finishes. Unless the author opts out with `.no-revert`, this frees the
|
|
674
829
|
// hundreds of per-letter elements so the browser stops reflowing them.
|
|
675
830
|
const revertSplit = (el) => {
|
|
676
|
-
if (el
|
|
831
|
+
if (hasGClass(el, "no-revert")) return
|
|
677
832
|
const s = splitCache.get(el)
|
|
678
833
|
if (!s) return
|
|
679
834
|
splitCache.delete(el)
|
|
@@ -722,7 +877,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
722
877
|
return gsap.fromTo(parts, effFrom, {
|
|
723
878
|
...computeTo(effFrom), ease, duration, delay, stagger,
|
|
724
879
|
onComplete: () => {
|
|
725
|
-
if (el
|
|
880
|
+
if (hasGClass(el, "leave")) refreshLeaveRect(el)
|
|
726
881
|
revertSplit(el)
|
|
727
882
|
fireOnComplete(el, "spawn")
|
|
728
883
|
},
|
|
@@ -758,7 +913,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
758
913
|
// classes are present (N = how many % into view to engage, and how far
|
|
759
914
|
// out of view to release), else the full `top top` -> `bottom bottom`.
|
|
760
915
|
const setupPin = (el) => {
|
|
761
|
-
if (!el
|
|
916
|
+
if (!hasGClass(el, "pin") || el.dataset.gsapPinned) return
|
|
762
917
|
const clamp = (n) => Math.min(100, Math.max(0, n))
|
|
763
918
|
const startClass = readClassNumber(el, "progress-start-", null)
|
|
764
919
|
const endClass = readClassNumber(el, "progress-end-", null)
|
|
@@ -780,7 +935,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
780
935
|
// created before any scroll/scroll-progress trigger measures its position.
|
|
781
936
|
// Setting them up here (before the trigger pass below) keeps offsets correct
|
|
782
937
|
// and lets the single ScrollTrigger.refresh() at the end reconcile layout.
|
|
783
|
-
|
|
938
|
+
qAllAllVariants(".pin").forEach(el => runWithBreakpointForSel(el, ".pin", () => setupPin(el)))
|
|
784
939
|
|
|
785
940
|
// Scroll-driven extras - class-driven ScrollTrigger behaviours that don't
|
|
786
941
|
// fit the spawn/loop machinery (no `play`/`build`), handled like `.pin`:
|
|
@@ -803,9 +958,9 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
803
958
|
const cls = [...el.classList]
|
|
804
959
|
const clamp = (n) => Math.min(100, Math.max(0, n))
|
|
805
960
|
|
|
806
|
-
const parallaxCls =
|
|
961
|
+
const parallaxCls = getActivePrefixedClass(el, "parallax-")
|
|
807
962
|
if (parallaxCls) {
|
|
808
|
-
const factor = parseFloat(parallaxCls.slice("parallax-".length)) || 1
|
|
963
|
+
const factor = parseFloat(parallaxCls.slice(parallaxCls.indexOf("parallax-") + "parallax-".length)) || 1
|
|
809
964
|
if (factor === 1) return
|
|
810
965
|
const amt = (factor - 1) * 50
|
|
811
966
|
const t = gsap.fromTo(el,
|
|
@@ -819,13 +974,13 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
819
974
|
return
|
|
820
975
|
}
|
|
821
976
|
|
|
822
|
-
if (el
|
|
977
|
+
if (hasGClass(el, "progress-bar") || hasGClass(el, "scroll-fill")) {
|
|
823
978
|
const startClass = readClassNumber(el, "progress-start-", null)
|
|
824
979
|
const endClass = readClassNumber(el, "progress-end-", null)
|
|
825
980
|
// `.progress-reverse` runs the fill in reverse (full -> empty).
|
|
826
981
|
// GSAP's ScrollTrigger has no `reversed` config; swap the from/to
|
|
827
982
|
// so the scrub maps in the opposite direction instead.
|
|
828
|
-
const reverse = el
|
|
983
|
+
const reverse = hasGClass(el, "progress-reverse")
|
|
829
984
|
const t = gsap.fromTo(el,
|
|
830
985
|
{ scaleX: reverse ? 1 : 0 },
|
|
831
986
|
{
|
|
@@ -842,7 +997,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
842
997
|
return
|
|
843
998
|
}
|
|
844
999
|
|
|
845
|
-
if (el
|
|
1000
|
+
if (hasGClass(el, "scroll-fade-bg")) {
|
|
846
1001
|
const t = gsap.fromTo(el,
|
|
847
1002
|
{ backgroundPosition: "0% 0%" },
|
|
848
1003
|
{
|
|
@@ -854,7 +1009,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
854
1009
|
return
|
|
855
1010
|
}
|
|
856
1011
|
|
|
857
|
-
if (el
|
|
1012
|
+
if (hasGClass(el, "scroll-horizontal")) {
|
|
858
1013
|
const track = el.querySelector(".scroll-track")
|
|
859
1014
|
if (!track) return
|
|
860
1015
|
const getAmount = () => track.scrollWidth - el.clientWidth
|
|
@@ -873,7 +1028,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
873
1028
|
scrollTriggers.push(t.scrollTrigger)
|
|
874
1029
|
}
|
|
875
1030
|
}
|
|
876
|
-
qAll(
|
|
1031
|
+
qAll("body *").filter(el => hasGClass(el,"progress-bar") || hasGClass(el,"scroll-fill") || hasGClass(el,"scroll-fade-bg") || hasGClass(el,"scroll-horizontal") || [...el.classList].some(c=>c.startsWith("parallax-") || bpNames.some(bp=>c.startsWith(`${bp}:parallax-`)))).forEach(el => setupScrollDriven(el))
|
|
877
1032
|
|
|
878
1033
|
// Scroller resolution: a `.scroll`/`.scroll-progress` element inside a
|
|
879
1034
|
// `.scroll-frame` container binds its trigger to THAT box instead of the
|
|
@@ -899,7 +1054,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
899
1054
|
if (!config) return
|
|
900
1055
|
const { from, typewriter: isTypewriter, typewriterSplit, play } = config
|
|
901
1056
|
|
|
902
|
-
if (el
|
|
1057
|
+
if (hasGClass(el, "scroll-progress")) {
|
|
903
1058
|
const ease = isTypewriter
|
|
904
1059
|
? ([...el.classList].find(c => c.startsWith("ease-"))?.split("-")[1] ?? "none")
|
|
905
1060
|
: getEase(el)
|
|
@@ -920,7 +1075,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
920
1075
|
// `.progress-reverse` runs the scrub in reverse (revealed -> hidden).
|
|
921
1076
|
// GSAP's ScrollTrigger ignores a `reversed` config; swap from/to so
|
|
922
1077
|
// the scrub maps in the opposite direction instead.
|
|
923
|
-
const reverse = el
|
|
1078
|
+
const reverse = hasGClass(el, "progress-reverse")
|
|
924
1079
|
// .randomize-* applies to the HIDDEN start only (non-reverse):
|
|
925
1080
|
// a scrub's resting end must stay deterministic or the element
|
|
926
1081
|
// would sit permanently off-pose after being scrolled through.
|
|
@@ -966,7 +1121,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
966
1121
|
} else if (typewriterSplit) {
|
|
967
1122
|
const parts = getParts(el, getGranularity(el))
|
|
968
1123
|
if (parts.length) tl.fromTo(parts, { opacity: reverse ? 1 : 0 }, { opacity: reverse ? 0 : 1, ease })
|
|
969
|
-
} else if (el
|
|
1124
|
+
} else if (hasGClass(el, "fill-svg") && (hasGClass(el, "draw") || hasGClass(el, "draw-split"))) {
|
|
970
1125
|
// fill-svg modifier for draw: stroke first, then fill, sequential scrub.
|
|
971
1126
|
// For scrub the two phases are sequential tweens so scroll maps
|
|
972
1127
|
// draw → fill. Reverse swaps order so unfill happens before undraw.
|
|
@@ -991,7 +1146,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
991
1146
|
return
|
|
992
1147
|
}
|
|
993
1148
|
|
|
994
|
-
if (!el
|
|
1149
|
+
if (!hasGClass(el, "scroll")) return
|
|
995
1150
|
const { delay, duration } = readTiming(el)
|
|
996
1151
|
const ease = isTypewriter
|
|
997
1152
|
? ([...el.classList].find(c => c.startsWith("ease-"))?.split("-")[1] ?? "none")
|
|
@@ -1042,7 +1197,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1042
1197
|
})
|
|
1043
1198
|
scrollTriggers.push(st)
|
|
1044
1199
|
}
|
|
1045
|
-
qAll(".scroll,
|
|
1200
|
+
qAll("body *").filter(el => hasGClass(el, "scroll") || hasGClass(el, "scroll-progress")).forEach(el => runWithBreakpoint(el, () => setupScroll(el)))
|
|
1046
1201
|
|
|
1047
1202
|
// SplitText scroll variants: `.spawn-text-<spawn>.scroll` plays the per-part
|
|
1048
1203
|
// tween when the element enters the viewport and reverses on exit.
|
|
@@ -1050,7 +1205,8 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1050
1205
|
if (isTypewriter || text === false) return
|
|
1051
1206
|
const tSel = "." + TEXT_PREFIX + sel.slice(1)
|
|
1052
1207
|
|
|
1053
|
-
qAll(tSel
|
|
1208
|
+
qAll("body *").filter(el => elementMatchesSel(el, tSel) && hasGClass(el, "scroll") && !hasGClass(el, "scroll-progress")).forEach((el) => {
|
|
1209
|
+
const run = () => {
|
|
1054
1210
|
if (isReduced(el)) return
|
|
1055
1211
|
const { delay, duration } = readTiming(el)
|
|
1056
1212
|
const ease = getEase(el)
|
|
@@ -1073,9 +1229,11 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1073
1229
|
onLeave: reverseToStart,
|
|
1074
1230
|
onLeaveBack: reverseToStart,
|
|
1075
1231
|
}))
|
|
1232
|
+
}; runWithBreakpoint(el, run)
|
|
1076
1233
|
})
|
|
1077
1234
|
|
|
1078
|
-
qAll(tSel
|
|
1235
|
+
qAll("body *").filter(el => elementMatchesSel(el, tSel) && hasGClass(el, "scroll-progress")).forEach((el) => {
|
|
1236
|
+
const run = () => {
|
|
1079
1237
|
if (isReduced(el)) return
|
|
1080
1238
|
const ease = getEase(el)
|
|
1081
1239
|
const parts = getParts(el, getGranularity(el))
|
|
@@ -1096,11 +1254,12 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1096
1254
|
// `.progress-reverse` runs the split scrub in reverse; swap from/to.
|
|
1097
1255
|
// Randomize applies to the hidden (non-reverse) start only, same
|
|
1098
1256
|
// rule as the element-level scrub above.
|
|
1099
|
-
const reverse = el
|
|
1257
|
+
const reverse = hasGClass(el, "progress-reverse")
|
|
1100
1258
|
const rnd = !reverse && hasRandom(el) ? randomVars(el) : null
|
|
1101
1259
|
tl.fromTo(parts, { ...(reverse ? to : from), ...rnd },
|
|
1102
1260
|
{ ...(rnd ? randomEnds(Object.keys(rnd)) : null), ...(reverse ? from : to), ease })
|
|
1103
1261
|
scrollTriggers.push(tl.scrollTrigger)
|
|
1262
|
+
}; runWithBreakpoint(el, run)
|
|
1104
1263
|
})
|
|
1105
1264
|
})
|
|
1106
1265
|
ScrollTrigger.refresh()
|
|
@@ -1128,14 +1287,18 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1128
1287
|
|
|
1129
1288
|
spawnConfigs.forEach((config) => {
|
|
1130
1289
|
const { sel, typewriter: isTypewriter, typewriterSplit } = config
|
|
1131
|
-
|
|
1132
|
-
|
|
1290
|
+
qAllAllVariants(sel).forEach((el) => {
|
|
1291
|
+
const run = () => {
|
|
1292
|
+
if (hasGClass(el, "scroll") || hasGClass(el, "scroll-progress")) return
|
|
1133
1293
|
if (isPreserved(el)) return
|
|
1134
1294
|
if (isReduced(el)) return
|
|
1295
|
+
// if this sel is gated via bp:sel and not active, elementMatchesSel will have already filtered,
|
|
1296
|
+
// but qAllAllVariants includes inactive; check active before building
|
|
1297
|
+
if (!elementMatchesSel(el, sel)) return
|
|
1135
1298
|
const { delay, duration } = readTiming(el)
|
|
1136
1299
|
if (isTypewriter) {
|
|
1137
|
-
const easeClass = [...el.classList].find(c => c.startsWith("ease-"))
|
|
1138
|
-
const elEase = easeClass ? easeClass.
|
|
1300
|
+
const easeClass = getActivePrefixedClass(el, "ease-") || [...el.classList].find(c => c.startsWith("ease-"))
|
|
1301
|
+
const elEase = easeClass ? easeClass.slice(easeClass.indexOf("ease-") + 5) : "none"
|
|
1139
1302
|
if (typewriterSplit) {
|
|
1140
1303
|
el._spawnTween = playTypewriterSplit(el, delay, duration, elEase)
|
|
1141
1304
|
} else {
|
|
@@ -1145,8 +1308,8 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1145
1308
|
} else {
|
|
1146
1309
|
el._spawnTween = invokePlay(config, el, delay, duration, getEase(el))
|
|
1147
1310
|
el._spawnTween.eventCallback("onComplete", () => {
|
|
1148
|
-
if (el
|
|
1149
|
-
if (el
|
|
1311
|
+
if (hasGClass(el, "leave")) refreshLeaveRect(el)
|
|
1312
|
+
if (hasGClass(el, "flip")) captureFlip(el)
|
|
1150
1313
|
if (isCompatibility(el)) resumeCompatLoops(el)
|
|
1151
1314
|
scheduleRefresh()
|
|
1152
1315
|
fireOnComplete(el, "spawn")
|
|
@@ -1154,6 +1317,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1154
1317
|
}
|
|
1155
1318
|
markPreserved(el)
|
|
1156
1319
|
el.dataset.gsapWired = "1"
|
|
1320
|
+
}; runWithBreakpointForSel(el, sel, run)
|
|
1157
1321
|
})
|
|
1158
1322
|
})
|
|
1159
1323
|
|
|
@@ -1163,14 +1327,17 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1163
1327
|
spawnConfigs.forEach(({ sel, from, typewriter: isTypewriter, text }) => {
|
|
1164
1328
|
if (isTypewriter || text === false) return
|
|
1165
1329
|
const tSel = "." + TEXT_PREFIX + sel.slice(1)
|
|
1166
|
-
|
|
1167
|
-
|
|
1330
|
+
qAllAllVariants(tSel).forEach((el) => {
|
|
1331
|
+
const run = () => {
|
|
1332
|
+
if (hasGClass(el, "scroll") || hasGClass(el, "scroll-progress")) return
|
|
1168
1333
|
if (isPreserved(el)) return
|
|
1169
1334
|
if (isReduced(el)) return
|
|
1335
|
+
if (!elementMatchesSel(el, tSel)) return
|
|
1170
1336
|
const { delay, duration } = readTiming(el)
|
|
1171
1337
|
el._spawnTween = playText(el, from, delay, duration, getEase(el))
|
|
1172
1338
|
markPreserved(el)
|
|
1173
1339
|
el.dataset.gsapWired = "1"
|
|
1340
|
+
}; runWithBreakpointForSel(el, tSel, run)
|
|
1174
1341
|
})
|
|
1175
1342
|
})
|
|
1176
1343
|
|
|
@@ -1218,8 +1385,8 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1218
1385
|
}
|
|
1219
1386
|
|
|
1220
1387
|
const setupMagnet = (el) => {
|
|
1221
|
-
if (!el
|
|
1222
|
-
const threeD = el
|
|
1388
|
+
if (!hasGClass(el, "magnet") && !hasGClass(el, "magnet3d")) return
|
|
1389
|
+
const threeD = hasGClass(el, "magnet3d")
|
|
1223
1390
|
const duration = readClassNumber(el, "mtime-", 0.4)
|
|
1224
1391
|
const pull = readClassNumber(el, "amount-", 0.3)
|
|
1225
1392
|
const grow = readClassNumber(el, "mgrow-", 1.1)
|
|
@@ -1244,7 +1411,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1244
1411
|
// a hover/click is active we pause every tracked loop tween on the element
|
|
1245
1412
|
// and resume it once the interaction ends. Loops are only tracked when the
|
|
1246
1413
|
// `.compatibility` class is present, so nothing else changes behaviour.
|
|
1247
|
-
const isCompatibility = (el) => el
|
|
1414
|
+
const isCompatibility = (el) => hasGClass(el, "compatibility")
|
|
1248
1415
|
const compatLoopsOf = (el) => {
|
|
1249
1416
|
if (!el._gsapCompatLoops) el._gsapCompatLoops = []
|
|
1250
1417
|
return el._gsapCompatLoops
|
|
@@ -1259,7 +1426,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1259
1426
|
const setupClicks = (el) => {
|
|
1260
1427
|
if (isReduced(el)) return
|
|
1261
1428
|
setupMagnet(el)
|
|
1262
|
-
if (el
|
|
1429
|
+
if (hasGClass(el, "click-hover")) {
|
|
1263
1430
|
const area = wrapTarget(el)
|
|
1264
1431
|
let touch = false
|
|
1265
1432
|
const duration = readClassNumber(el, "ctime-", defaults.clickDuration)
|
|
@@ -1276,7 +1443,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1276
1443
|
touch = true, verticalmove(el, 0, duration, elEase), setTimeout(() => { touch = false }, 0)
|
|
1277
1444
|
})
|
|
1278
1445
|
}
|
|
1279
|
-
if (el
|
|
1446
|
+
if (hasGClass(el, "click-expand")) {
|
|
1280
1447
|
let touch = false
|
|
1281
1448
|
const duration = readClassNumber(el, "ctime-", defaults.clickDuration)
|
|
1282
1449
|
const lift = readClassNumber(el, "amount-", defaults.clickExpandOffset)
|
|
@@ -1313,7 +1480,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1313
1480
|
if (isReduced(el)) return
|
|
1314
1481
|
const ctx = readLoopCtx(el)
|
|
1315
1482
|
loopConfigs.forEach(({ sel, build, key, loop }) => {
|
|
1316
|
-
if (el
|
|
1483
|
+
if (elementMatchesSel(el, sel)) {
|
|
1317
1484
|
el[key]?.kill()
|
|
1318
1485
|
el[key] = trackCompatLoop(el, build(el, ctx))
|
|
1319
1486
|
if (loop) el[key].repeat(-1)
|
|
@@ -1368,7 +1535,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1368
1535
|
// shifts under the cursor. Marquee is skipped (its build restructures the
|
|
1369
1536
|
// DOM).
|
|
1370
1537
|
const wrapTarget = (el) => {
|
|
1371
|
-
if (!el
|
|
1538
|
+
if (!hasGClass(el, "wrapdiv")) return el
|
|
1372
1539
|
if (el._gsapWrap) return el._gsapWrap
|
|
1373
1540
|
const area = document.createElement("div")
|
|
1374
1541
|
el.before(area)
|
|
@@ -1383,7 +1550,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1383
1550
|
loopConfigs.forEach(({ sel, build, key }) => {
|
|
1384
1551
|
if (sel.startsWith(".marquee")) return
|
|
1385
1552
|
const name = sel.slice(1)
|
|
1386
|
-
if (el
|
|
1553
|
+
if (hasGClass(el, "hover-" + name)) {
|
|
1387
1554
|
const area = wrapTarget(el)
|
|
1388
1555
|
addListener(area, "mouseenter", () => {
|
|
1389
1556
|
pauseCompatLoops(el)
|
|
@@ -1396,7 +1563,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1396
1563
|
el[key] = reset(el, readClassNumber(el, "etime-", defaults.effectDuration), getEase(el))
|
|
1397
1564
|
resumeCompatLoops(el)
|
|
1398
1565
|
})
|
|
1399
|
-
} else if (el
|
|
1566
|
+
} else if (hasGClass(el, "click-" + name)) {
|
|
1400
1567
|
const area = wrapTarget(el)
|
|
1401
1568
|
addListener(area, "mousedown", () => {
|
|
1402
1569
|
pauseCompatLoops(el)
|
|
@@ -1519,7 +1686,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1519
1686
|
if (isReduced(el)) return
|
|
1520
1687
|
const ctx = readLoopCtx(el)
|
|
1521
1688
|
animAll.forEach((a) => {
|
|
1522
|
-
if (a.setup && el
|
|
1689
|
+
if (a.setup && elementMatchesSel(el, a.sel)) {
|
|
1523
1690
|
const teardown = a.setup(el, ctx)
|
|
1524
1691
|
if (typeof teardown === "function") setupTeardowns.push(teardown)
|
|
1525
1692
|
}
|
|
@@ -1571,13 +1738,13 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1571
1738
|
|
|
1572
1739
|
|
|
1573
1740
|
const animateAppear = (el) => {
|
|
1574
|
-
if (!el
|
|
1741
|
+
if (!hasGClass(el, "appear") || el._appeared) return
|
|
1575
1742
|
// A `.scroll`/`.scroll-progress` element is owned by its ScrollTrigger
|
|
1576
1743
|
// (see setupScroll); `.appear` must not also fire, or it plays on mount
|
|
1577
1744
|
// AND again on scroll-enter. Text elements are the exception: their
|
|
1578
1745
|
// `.scroll` triggers are wired once at init, so a re-added (reset) text
|
|
1579
1746
|
// element has no trigger to conflict with and must animate via `.appear`.
|
|
1580
|
-
if (!isTextElement(el) && (el
|
|
1747
|
+
if (!isTextElement(el) && (hasGClass(el, "scroll") || hasGClass(el, "scroll-progress"))) return
|
|
1581
1748
|
if (isReduced(el)) return
|
|
1582
1749
|
el._appeared = true
|
|
1583
1750
|
const { delay, duration, ease } = readTiming(el)
|
|
@@ -1598,8 +1765,8 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1598
1765
|
} else {
|
|
1599
1766
|
el._spawnTween = invokePlay(config, el, delay, duration, ease)
|
|
1600
1767
|
el._spawnTween.eventCallback("onComplete", () => {
|
|
1601
|
-
if (el
|
|
1602
|
-
if (el
|
|
1768
|
+
if (hasGClass(el, "leave")) refreshLeaveRect(el)
|
|
1769
|
+
if (hasGClass(el, "flip")) captureFlip(el)
|
|
1603
1770
|
fireOnComplete(el, "spawn")
|
|
1604
1771
|
})
|
|
1605
1772
|
}
|
|
@@ -1630,7 +1797,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1630
1797
|
els.forEach((el) => {
|
|
1631
1798
|
// `.appear` is the opt-in gate for dynamically-added
|
|
1632
1799
|
// elements: without it a newly inserted node is ignored.
|
|
1633
|
-
if (!el
|
|
1800
|
+
if (!hasGClass(el, "appear")) return
|
|
1634
1801
|
animateAppear(el)
|
|
1635
1802
|
setupScroll(el)
|
|
1636
1803
|
setupScrollDriven(el)
|
|
@@ -1710,7 +1877,7 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1710
1877
|
appearObserver.observe(document.body, { childList: true, subtree: true })
|
|
1711
1878
|
|
|
1712
1879
|
// Capture any .leave elements already present so they can exit later
|
|
1713
|
-
qAll(".leave").forEach(captureLeave)
|
|
1880
|
+
qAll("body *").filter(el => hasGClass(el, "leave")).forEach(captureLeave)
|
|
1714
1881
|
|
|
1715
1882
|
leaveObserver = new MutationObserver((mutations) => {
|
|
1716
1883
|
handleLeaveBatch(mutations)
|
|
@@ -1720,16 +1887,17 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1720
1887
|
}
|
|
1721
1888
|
if (throttlePerFrame > 0) {
|
|
1722
1889
|
// capture for throttled path (was inside else branch above for non-throttled)
|
|
1723
|
-
qAll(".leave").forEach(captureLeave)
|
|
1890
|
+
qAll("body *").filter(el => hasGClass(el, "leave")).forEach(captureLeave)
|
|
1724
1891
|
}
|
|
1725
1892
|
|
|
1726
|
-
// Keep the captured position fresh (throttled to one pass per frame)
|
|
1893
|
+
// Keep the captured position fresh (throttled to one pass per frame) - include bp:leave
|
|
1894
|
+
const qAllLeaves = () => qAll("body *").filter(el => hasGClass(el, "leave"))
|
|
1727
1895
|
let positionTick = false
|
|
1728
1896
|
const refreshLeavePositions = () => {
|
|
1729
1897
|
if (positionTick) return
|
|
1730
1898
|
positionTick = true
|
|
1731
1899
|
requestAnimationFrame(() => {
|
|
1732
|
-
|
|
1900
|
+
qAllLeaves().forEach((el) => {
|
|
1733
1901
|
const s = leaveStates.get(el)
|
|
1734
1902
|
if (s) s.rect = el.getBoundingClientRect()
|
|
1735
1903
|
})
|
|
@@ -1738,6 +1906,18 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1738
1906
|
}
|
|
1739
1907
|
window.addEventListener("scroll", refreshLeavePositions, { passive: true })
|
|
1740
1908
|
window.addEventListener("resize", refreshLeavePositions, { passive: true })
|
|
1909
|
+
// Breakpoint reactivity: when crossing a breakpoint, modifiers like m:amount-20 or m:time-2
|
|
1910
|
+
// need to be re-evaluated (GSAP tweens built with old values are stale). Each breakpoint
|
|
1911
|
+
// matchMedia entry is naturally handled by runWithBreakpoint/mm.add for gated animations,
|
|
1912
|
+
// but modifier-only changes (e.g. `amount-10 m:amount-30` on same element) require a rebuild.
|
|
1913
|
+
// Listen to all breakpoints and refresh ScrollTrigger + re-evaluate active prefixed classes.
|
|
1914
|
+
const bpMqls = bpEntries.map(([bp, px]) => {
|
|
1915
|
+
const mql = typeof window !== 'undefined' && window.matchMedia ? window.matchMedia(`(min-width: ${px}px)`) : null
|
|
1916
|
+
if (!mql) return null
|
|
1917
|
+
const fn = () => ScrollTrigger.refresh()
|
|
1918
|
+
mql.addEventListener?.('change', fn)
|
|
1919
|
+
return { mql, fn }
|
|
1920
|
+
}).filter(Boolean)
|
|
1741
1921
|
|
|
1742
1922
|
return () => {
|
|
1743
1923
|
appearObserver?.disconnect()
|
|
@@ -1748,6 +1928,8 @@ export default function initListeners(root = document, throttlePerFrame) {
|
|
|
1748
1928
|
if (flipPendingRaf) cancelAnimationFrame(flipPendingRaf)
|
|
1749
1929
|
window.removeEventListener("scroll", refreshLeavePositions)
|
|
1750
1930
|
window.removeEventListener("resize", refreshLeavePositions)
|
|
1931
|
+
bpMqls.forEach(({ mql, fn }) => mql.removeEventListener?.('change', fn))
|
|
1932
|
+
mm.revert()
|
|
1751
1933
|
window.removeEventListener("load", ScrollTrigger.refresh)
|
|
1752
1934
|
clearTimeout(refreshTimer)
|
|
1753
1935
|
scrollTriggers.forEach((t) => {
|