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/AnimToggle.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import initListeners from './Listeners.js'
|
|
2
2
|
import { defaults, animations } from './Config.js'
|
|
3
|
-
import gsap from 'gsap'
|
|
3
|
+
import { gsap } from 'gsap'
|
|
4
4
|
|
|
5
5
|
// localStorage key controlling whether the GSAP animation system is mounted.
|
|
6
6
|
const STORAGE_KEY = 'gclass-animations-enabled'
|
package/Animations.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DrawSVGPlugin, Flip, MotionPathPlugin, ScrambleTextPlugin, SplitText, TextPlugin } from "gsap/all";
|
|
2
|
-
import gsap from "gsap";
|
|
2
|
+
import { gsap } from "gsap";
|
|
3
3
|
import { defaults } from './Config.js'
|
|
4
4
|
|
|
5
5
|
gsap.registerPlugin(Flip)
|
|
@@ -9,6 +9,44 @@ gsap.registerPlugin(DrawSVGPlugin)
|
|
|
9
9
|
gsap.registerPlugin(MotionPathPlugin)
|
|
10
10
|
gsap.registerPlugin(ScrambleTextPlugin)
|
|
11
11
|
|
|
12
|
+
// --- Breakpoint helpers for modifiers (xs/s/m/l/xl) -------------------------
|
|
13
|
+
// Lazy to avoid circular init (Config.js <-> Animations.js)
|
|
14
|
+
const getBpDataMod = () => {
|
|
15
|
+
const entries = Object.entries(defaults.breakpoints || {}).sort((a,b)=>a[1]-b[1])
|
|
16
|
+
const names = entries.map(([k])=>k)
|
|
17
|
+
const map = Object.fromEntries(entries)
|
|
18
|
+
const re = names.length ? new RegExp(`^(${names.join('|')}):(.+)$`) : /^$^/
|
|
19
|
+
return { entries, names, map, re }
|
|
20
|
+
}
|
|
21
|
+
const isBreakpointActiveMod = (bp) => {
|
|
22
|
+
if (!bp) return true
|
|
23
|
+
const { map } = getBpDataMod()
|
|
24
|
+
const px = map[bp]
|
|
25
|
+
if (px == null) return true
|
|
26
|
+
if (typeof window === 'undefined' || !window.matchMedia) return true
|
|
27
|
+
return window.matchMedia(`(min-width: ${px}px)`).matches
|
|
28
|
+
}
|
|
29
|
+
const hasGClassMod = (el, name) => {
|
|
30
|
+
if (el.classList.contains(name)) return true
|
|
31
|
+
const { names } = getBpDataMod()
|
|
32
|
+
for (const bp of names) if (el.classList.contains(`${bp}:${name}`) && isBreakpointActiveMod(bp)) return true
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
const getActivePrefixedClassMod = (el, prefix) => {
|
|
36
|
+
const { names, map, re } = getBpDataMod()
|
|
37
|
+
let best = null, bestPx = -2
|
|
38
|
+
for (const c of el.classList) {
|
|
39
|
+
let bp = null, core = c
|
|
40
|
+
const m = c.match(re)
|
|
41
|
+
if (m) { bp = m[1]; core = m[2] }
|
|
42
|
+
if (!core.startsWith(prefix)) continue
|
|
43
|
+
if (bp && !isBreakpointActiveMod(bp)) continue
|
|
44
|
+
const px = bp ? map[bp] : -1
|
|
45
|
+
if (px > bestPx) { best = c; bestPx = px }
|
|
46
|
+
}
|
|
47
|
+
return best
|
|
48
|
+
}
|
|
49
|
+
|
|
12
50
|
// A tasteful fallback whenever a call site omits an ease, so the animation
|
|
13
51
|
// never lapses into the raw "none" look. Callers still override this freely.
|
|
14
52
|
const DEFAULT_EASE = "power3.out";
|
|
@@ -210,9 +248,9 @@ export function countTargetVars (target){
|
|
|
210
248
|
const end = match ? parseFloat(match[0]) : 0
|
|
211
249
|
const decimals = match?.[0].includes(".") ? (match[0].split(".")[1] || "").length : 0
|
|
212
250
|
// Count FROM `.spawn-num-N` (N = the starting number) up to `end`. When the
|
|
213
|
-
// class is absent, fall back to 0.
|
|
214
|
-
const startCls =
|
|
215
|
-
const start = startCls ? parseFloat(startCls.slice("spawn-num-".length)) : 0
|
|
251
|
+
// class is absent, fall back to 0. Supports `m:spawn-num-10` etc. (mobile-first)
|
|
252
|
+
const startCls = getActivePrefixedClassMod(target, "spawn-num-")
|
|
253
|
+
const start = startCls ? parseFloat(startCls.slice(startCls.indexOf("spawn-num-") + "spawn-num-".length)) : 0
|
|
216
254
|
return target._countTarget = { start, end, decimals }
|
|
217
255
|
}
|
|
218
256
|
|
|
@@ -231,12 +269,12 @@ export function countUp (target , delay , dur, ease){
|
|
|
231
269
|
// the interior fills. `fill-time-N` / `fill-ease-NAME` override the fill
|
|
232
270
|
// phase; otherwise the fill takes half of `dur` and reuses the draw ease.
|
|
233
271
|
const fillTimeOf = (el , fallback) => {
|
|
234
|
-
const m =
|
|
235
|
-
return m ? Number(m.slice("fill-time-".length)) : fallback * 0.5
|
|
272
|
+
const m = getActivePrefixedClassMod(el, "fill-time-")
|
|
273
|
+
return m ? Number(m.slice(m.indexOf("fill-time-") + "fill-time-".length)) : fallback * 0.5
|
|
236
274
|
}
|
|
237
275
|
const fillEaseOf = (el , fallbackEase) => {
|
|
238
|
-
const m =
|
|
239
|
-
return m ? m.slice("fill-ease-".length) : fallbackEase
|
|
276
|
+
const m = getActivePrefixedClassMod(el, "fill-ease-")
|
|
277
|
+
return m ? m.slice(m.indexOf("fill-ease-") + "fill-ease-".length) : fallbackEase
|
|
240
278
|
}
|
|
241
279
|
|
|
242
280
|
// Stroke-draw reveal (strokes only - filled SVGs are deliberately out of
|
|
@@ -248,13 +286,13 @@ const fillEaseOf = (el , fallbackEase) => {
|
|
|
248
286
|
export function drawsvg (target , delay , dur , ease){
|
|
249
287
|
const e = easeOf(ease)
|
|
250
288
|
const first = gsap.utils.toArray(target)[0]
|
|
251
|
-
const hasFill = !!first
|
|
289
|
+
const hasFill = !!first && hasGClassMod(first, "fill-svg")
|
|
252
290
|
if (!hasFill) {
|
|
253
291
|
return gsap.fromTo(target , {drawSVG:"0%"} , {ease:e , duration:dur , delay:delay , drawSVG:"100%"})
|
|
254
292
|
}
|
|
255
293
|
const fillDur = fillTimeOf(first , dur)
|
|
256
294
|
const fillEase = fillEaseOf(first , ease)
|
|
257
|
-
const fillTargets = gsap.utils.toArray(target).filter(el => el
|
|
295
|
+
const fillTargets = gsap.utils.toArray(target).filter(el => hasGClassMod(el, "fill-svg"))
|
|
258
296
|
const tl = gsap.timeline({ delay })
|
|
259
297
|
// Keep fill invisible while the stroke draws
|
|
260
298
|
if (fillTargets.length) gsap.set(fillTargets , { fillOpacity: 0 })
|
|
@@ -332,7 +370,7 @@ export function splitPaths (paths){
|
|
|
332
370
|
export function drawsvgSplit (target , delay , dur , ease){
|
|
333
371
|
const e = easeOf(ease)
|
|
334
372
|
const first = gsap.utils.toArray(target)[0]
|
|
335
|
-
const hasFill = !!first
|
|
373
|
+
const hasFill = !!first && hasGClassMod(first, "fill-svg")
|
|
336
374
|
const fillDur = hasFill ? fillTimeOf(first , dur) : 0
|
|
337
375
|
const fillEase = hasFill ? fillEaseOf(first , ease) : ease
|
|
338
376
|
const tl = gsap.timeline({ delay })
|
|
@@ -397,13 +435,29 @@ export const scrambleSegments = (target) => {
|
|
|
397
435
|
// .reveal-delay-N -> revealDelay in seconds (default defaults.revealDelay)
|
|
398
436
|
// .chars-[...] -> character pool taken verbatim from inside the brackets
|
|
399
437
|
// (default defaults.characterlist)
|
|
438
|
+
// Supports `m:amount-N`, `l:chars-[...]` etc. (largest active wins)
|
|
400
439
|
export function scrambleVars (target){
|
|
401
440
|
const num = (prefix , fallback) => {
|
|
402
|
-
const
|
|
403
|
-
|
|
441
|
+
const c = getActivePrefixedClassMod(target, prefix)
|
|
442
|
+
if (!c) return fallback
|
|
443
|
+
const idx = c.indexOf(prefix)
|
|
444
|
+
const n = Number(c.slice(idx + prefix.length))
|
|
445
|
+
return Number.isNaN(n) ? fallback : n
|
|
404
446
|
}
|
|
405
447
|
// Greedy up to the LAST "]" so pools containing "]" survive intact.
|
|
406
|
-
|
|
448
|
+
// Check for `m:chars-[...]` then fallback to `chars-[...]`
|
|
449
|
+
let charsCls = null
|
|
450
|
+
let bestPx = -2
|
|
451
|
+
const { re: reChars, map: mapChars } = getBpDataMod()
|
|
452
|
+
for (const c of target.classList) {
|
|
453
|
+
let bp = null, core = c
|
|
454
|
+
const m = c.match(reChars)
|
|
455
|
+
if (m) { bp = m[1]; core = m[2] }
|
|
456
|
+
if (!/^chars-\[(.*)\]$/.test(core)) continue
|
|
457
|
+
if (bp && !isBreakpointActiveMod(bp)) continue
|
|
458
|
+
const px = bp ? mapChars[bp] : -1
|
|
459
|
+
if (px > bestPx) { charsCls = core; bestPx = px }
|
|
460
|
+
}
|
|
407
461
|
return {
|
|
408
462
|
segs: scrambleSegments(target) ,
|
|
409
463
|
chars: charsCls ? charsCls.slice("chars-[".length , -1) : defaults.characterlist ,
|
|
@@ -411,7 +465,7 @@ export function scrambleVars (target){
|
|
|
411
465
|
revealDelay: num("reveal-delay-" , defaults.revealDelay) ,
|
|
412
466
|
// .scramble-rtl flips the reveal direction (ScrambleTextPlugin's
|
|
413
467
|
// rightToLeft) so the sweep travels right -> left.
|
|
414
|
-
rtl: target
|
|
468
|
+
rtl: hasGClassMod(target, "scramble-rtl") ,
|
|
415
469
|
}
|
|
416
470
|
}
|
|
417
471
|
|
|
@@ -430,9 +484,9 @@ export function scrambleVars (target){
|
|
|
430
484
|
// ScrambleTextPlugin resolve).
|
|
431
485
|
// .scramble-rtl - reveal travels right -> left.
|
|
432
486
|
export function scramble (target , delay , dur , ease){
|
|
433
|
-
const e =
|
|
487
|
+
const e = getActivePrefixedClassMod(target, "ease-") ? easeOf(ease) : "none"
|
|
434
488
|
const { segs , chars , speed , revealDelay , rtl } = scrambleVars(target)
|
|
435
|
-
const all = target
|
|
489
|
+
const all = hasGClassMod(target, "scramble-all")
|
|
436
490
|
const tl = gsap.timeline({ delay })
|
|
437
491
|
segs.forEach(({ t , text }) => {
|
|
438
492
|
if (all) {
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `gclass-anims` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.0-beta.22.1] - 2026-09-06
|
|
6
|
+
- Fixed `.order` stagger grouping bug — `Listeners.js:76` `wrapQAll` now preserves DOM order via `body *` filter + `elementMatchesSel` (was `Set([...spawn-up], [...spawn-down])` grouping by type, now top-to-bottom as `qAll` does). Fixes `doc` `spawn` and landing `order` appearing all over.
|
|
7
|
+
- No API change from `beta.22`.
|
|
8
|
+
|
|
9
|
+
## [1.0.0-beta.22] - 2026-09-06
|
|
10
|
+
- Added responsive breakpoints — `Config.js:75` `defaults.breakpoints {xs:475,s:640,m:768,l:1024,xl:1280}` (single-letter `s/m/l` avoids Tailwind `sm/md/lg` collision). Usage `m:spawn-up`, `l:float`, `xs:spawn-up`. Gating is live via `gsap.matchMedia` (`Listeners.js:26,202`).
|
|
11
|
+
- Added breakpoint-aware modifiers — `m:amount-20`, `m:time-2`, `m:ease-bounce`, `m:priority-3`, `m:chars-[...]`, `m:spawn-num-10` etc. Mobile-first largest active wins (`Listeners.js:237` `getActivePrefixedClass`, `Animations.js:13` lazy helpers). Covers `amount-`, `time-`, `priority-`, `edelay-`, `etime-`, `stagger-`, `fill-time-`, `reveal-delay-`, `spawn-num-`, `progress-start-`, etc.
|
|
12
|
+
- Fixed runtime `customAnims` re-normalization — `Listeners.js:190` now calls `normalize(customAnims)` inside `initListeners()` so `customAnims.push()` before next `init` is picked up.
|
|
13
|
+
- Added ESM + CJS dual build — `vite.lib.config.js` (Vite lib) builds `dist/gclass.esm.js` + `dist/gclass.cjs` (`gsap` external); `package.json:4` bumped to `beta.22`, `main/module` point to `dist/`, `exports: {import, require}`, `sideEffects:false`, `prepublishOnly: build`.
|
|
14
|
+
- Fixed CJS `gsap` interop — `Animations.js:2`/`Listeners.js:1`/`AnimToggle.js:3`/`CustomAnims.js:1` now `import {gsap} from 'gsap'` (named import) for correct `require('gsap').gsap` interop.
|
|
15
|
+
- Added `dev-react-strict` test harness — `vitest` + `jsdom` + `src/__tests__/breakpoints.test.jsx` (14 tests: gating, modifiers, Tailwind coexistence, StrictMode) + `BreakpointHarness.jsx` visual; `vite.config.js` test config.
|
|
16
|
+
- Documented breakpoints — `doc/src/app/documentation/responsive-design/page.js` (was duplicate `optimization`).
|
|
17
|
+
|
|
5
18
|
## [1.0.0-beta.21] - 2026-9-3
|
|
6
19
|
- Added a `gclassOpts()` function that controls the animation fps and observer throttling
|
|
7
20
|
|
package/Config.js
CHANGED
package/CustomAnims.js
CHANGED