glyphdust 0.2.0 → 0.2.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/CHANGELOG.md +47 -0
- package/README.md +59 -5
- package/dist/index.cjs +107 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -6
- package/dist/index.d.ts +58 -6
- package/dist/index.js +107 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **glyphdust** are documented here.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/), and the
|
|
5
|
+
project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.2.1] — 2026-06-26
|
|
8
|
+
|
|
9
|
+
Flexibility & polish release. Glyphdust is no longer scroll-and-hero only — it now
|
|
10
|
+
drops into any box, plays without scroll, and ships tasteful presets you can override.
|
|
11
|
+
**Defaults reproduce 0.2.0 exactly**, so upgrading is non-breaking.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`autoplay` driver** — time-based progress with no scroll choreography. Fits its
|
|
16
|
+
parent box and starts when scrolled into view (`playOnView`, default on). Options:
|
|
17
|
+
`duration`, `delay`, `loop`, `pingpong`, `playOnView`. Exposed
|
|
18
|
+
`computeAutoplayProgress()` for custom rigs.
|
|
19
|
+
- **`preset` prop** — `"default" | "minimal" | "lively" | "glow"`: a tasteful bundle
|
|
20
|
+
of look + motion.
|
|
21
|
+
- **`style` prop** — per-field overrides on top of the preset:
|
|
22
|
+
`size`, `blend` (`"normal" | "additive"`), `drift`, `sparkle`. Backed by new shader
|
|
23
|
+
uniforms (`uSizeScale`, `uDrift`, `uSparkle`); `additive` enables glow blending for
|
|
24
|
+
dark backgrounds.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Particles render finer and crisper on high-DPI screens: point-size base ×0.62,
|
|
29
|
+
clamp lowered to 4–5 px, and `devicePixelRatio` cap raised 2 → 3. (Validated on the
|
|
30
|
+
LINNO corporate site.)
|
|
31
|
+
- Scroll follow no longer lags: stage progress is applied directly instead of an
|
|
32
|
+
internal lerp. Add inertia in your driver (e.g. Lenis) if you want it.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- No more blank gap when the **first keyframe is text** — particles now start in the
|
|
37
|
+
formed glyph and dissolve outward, instead of appearing only after the real text
|
|
38
|
+
fades.
|
|
39
|
+
- `VERSION` export corrected (was a stale `"0.1.0"`).
|
|
40
|
+
|
|
41
|
+
## [0.2.0] — 2026-06-23
|
|
42
|
+
|
|
43
|
+
- Resolve to real DOM elements with pixel alignment; scrollbar & baseline fixes.
|
|
44
|
+
|
|
45
|
+
## [0.1.0]
|
|
46
|
+
|
|
47
|
+
- Initial public release: text → particles → glyph → real-text resolve, scroll-driven.
|
package/README.md
CHANGED
|
@@ -85,7 +85,34 @@ export function Hero() {
|
|
|
85
85
|
- `domSelector` makes particles land exactly on an existing element's box and font — no jump on cross-fade.
|
|
86
86
|
- `resolveToDom` on the final keyframe hands off from particles to crisp real text.
|
|
87
87
|
|
|
88
|
-
###
|
|
88
|
+
### Just drop in text — no scroll choreography
|
|
89
|
+
|
|
90
|
+
For anything that isn't a full-screen scroll hero, use the **`autoplay`** driver. It
|
|
91
|
+
fits its parent box and plays once when it scrolls into view — drop it anywhere and it
|
|
92
|
+
just animates:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<div style={{ width: 480, height: 220 }}>
|
|
96
|
+
<GlyphDust
|
|
97
|
+
driver={{ type: "autoplay", duration: 3.5 }} // loop / pingpong / delay too
|
|
98
|
+
preset="minimal" // tasteful out of the box
|
|
99
|
+
keyframes={[
|
|
100
|
+
{ type: "scatter" },
|
|
101
|
+
{ type: "text", text: "glyphdust", dense: true },
|
|
102
|
+
]}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Pick a look with presets (then tweak)
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<GlyphDust preset="glow" style={{ size: 1.2 }} keyframes={[/* … */]} />
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`preset` is a tasteful starting point; `style` overrides just the fields you name.
|
|
114
|
+
|
|
115
|
+
### Drive it yourself (manual)
|
|
89
116
|
|
|
90
117
|
```tsx
|
|
91
118
|
const [p, setP] = useState(0); // 0 → 1 from time, GSAP, a slider, anything
|
|
@@ -105,7 +132,9 @@ const [p, setP] = useState(0); // 0 → 1 from time, GSAP, a slider, anything
|
|
|
105
132
|
| prop | type | default | description |
|
|
106
133
|
|---|---|---|---|
|
|
107
134
|
| `keyframes` | `Keyframe[]` | — (required) | The animation timeline. Minimum 1; typically `text → scatter → text`. |
|
|
108
|
-
| `driver` | `DriverConfig` | `{ type: "scroll" }` | Progress source: `scroll` or `manual`. |
|
|
135
|
+
| `driver` | `DriverConfig` | `{ type: "scroll" }` | Progress source: `scroll`, `autoplay`, or `manual`. |
|
|
136
|
+
| `preset` | `GlyphPreset` | `"default"` | Look/motion preset: `default`, `minimal`, `lively`, `glow`. |
|
|
137
|
+
| `style` | `GlyphStyle` | — | Per-field overrides on top of `preset` (see below). |
|
|
109
138
|
| `colors` | `GlyphColors` | see below | Particle ink / accent colors. |
|
|
110
139
|
| `count` | `GlyphCount` | `{ desktop: 11000, mobile: 5200 }` | Particle count per device class. |
|
|
111
140
|
| `timing` | `number[]` | even spacing | Normalized time `0..1` per keyframe (interpolation boundaries). Length must match `keyframes`. |
|
|
@@ -142,10 +171,35 @@ type Keyframe = TextKeyframe | ScatterKeyframe;
|
|
|
142
171
|
### Drivers
|
|
143
172
|
|
|
144
173
|
```ts
|
|
145
|
-
{ type: "scroll", triggerHeight?: number } // default triggerHeight: 2 (×100vh)
|
|
174
|
+
{ type: "scroll", triggerHeight?: number } // full-screen sticky hero. default triggerHeight: 2 (×100vh)
|
|
146
175
|
{ type: "manual", progress: number } // you supply 0..1
|
|
176
|
+
{ type: "autoplay", // time-based; fits its parent box
|
|
177
|
+
duration?: number, // seconds for 0→1 (default 4)
|
|
178
|
+
delay?: number, // start delay (default 0)
|
|
179
|
+
loop?: boolean, // repeat (default false)
|
|
180
|
+
pingpong?: boolean, // 0→1→0 when looping (default false)
|
|
181
|
+
playOnView?: boolean, // start when scrolled into view (default true)
|
|
182
|
+
}
|
|
147
183
|
```
|
|
148
184
|
|
|
185
|
+
`scroll` builds a tall sticky wrapper for a full-screen hero. `manual` and `autoplay`
|
|
186
|
+
simply **fill their parent**, so you can place them in any sized container.
|
|
187
|
+
|
|
188
|
+
### Presets & style
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
preset: "default" | "minimal" | "lively" | "glow"
|
|
192
|
+
|
|
193
|
+
style: {
|
|
194
|
+
size?: number, // point-size multiplier (default 1)
|
|
195
|
+
blend?: "normal" | "additive", // "additive" = glow, for dark backgrounds
|
|
196
|
+
drift?: number, // idle/scatter wander 0..1 (default 1; 0 = still)
|
|
197
|
+
sparkle?: number, // sparkle strength 0..1 (default 1; 0 = off)
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`style` always wins over `preset`. Defaults reproduce the original look exactly.
|
|
202
|
+
|
|
149
203
|
### Colors
|
|
150
204
|
|
|
151
205
|
```ts
|
|
@@ -156,7 +210,7 @@ type Keyframe = TextKeyframe | ScatterKeyframe;
|
|
|
156
210
|
|
|
157
211
|
### Low-level helpers
|
|
158
212
|
|
|
159
|
-
For custom rigs, the building blocks are exported too: `buildTextTargets`, `buildDenseTextTargets`, `buildVertexShader`, `FRAGMENT_SHADER`, `createScrollProgress`, `useScrollProgress`, `useReducedMotion`, `prefersReducedMotion`, `viewSizeAtZ0`, `buildGlyphFromDOM`, `computeScreenRect`.
|
|
213
|
+
For custom rigs, the building blocks are exported too: `buildTextTargets`, `buildDenseTextTargets`, `buildVertexShader`, `FRAGMENT_SHADER`, `createScrollProgress`, `useScrollProgress`, `computeAutoplayProgress`, `useReducedMotion`, `prefersReducedMotion`, `viewSizeAtZ0`, `buildGlyphFromDOM`, `computeScreenRect`.
|
|
160
214
|
|
|
161
215
|
---
|
|
162
216
|
|
|
@@ -171,7 +225,7 @@ For custom rigs, the building blocks are exported too: `buildTextTargets`, `buil
|
|
|
171
225
|
|
|
172
226
|
## Status
|
|
173
227
|
|
|
174
|
-
`0.1
|
|
228
|
+
`0.2.1` — the component and API above are implemented and demoed (see [`examples/`](./examples)) and [`CHANGELOG.md`](./CHANGELOG.md). Published from [LINNO](https://linno.co.jp). Semantic-versioned; expect minor API polish before `1.0`.
|
|
175
229
|
|
|
176
230
|
## License
|
|
177
231
|
|
package/dist/index.cjs
CHANGED
|
@@ -182,6 +182,8 @@ function buildVertexShader(keyframeCount) {
|
|
|
182
182
|
uniform vec3 uPointer;
|
|
183
183
|
uniform float uPointerActive;
|
|
184
184
|
uniform float uSize;
|
|
185
|
+
uniform float uSizeScale;
|
|
186
|
+
uniform float uDrift;
|
|
185
187
|
uniform float uPixelRatio;
|
|
186
188
|
|
|
187
189
|
${attributeDecls}
|
|
@@ -217,7 +219,7 @@ ${mixChain}
|
|
|
217
219
|
|
|
218
220
|
// \u30A2\u30A4\u30C9\u30EB\u306E\u6F02\u3044\uFF08\u6574\u5217\u6642 settle / \u5B57\u5F62\u6642 form \u3067\u5F31\u3081\u308B\uFF09\u3002
|
|
219
221
|
vSettle = uSettle;
|
|
220
|
-
float drift = (1.0 - uReduced) * (1.0 - uSettle * 0.9) * (1.0 - uForm);
|
|
222
|
+
float drift = (1.0 - uReduced) * (1.0 - uSettle * 0.9) * (1.0 - uForm) * uDrift;
|
|
221
223
|
pos.x += sin(uTime * 0.35 + ph) * 0.06 * drift;
|
|
222
224
|
pos.y += cos(uTime * 0.30 + ph * 1.7) * 0.06 * drift;
|
|
223
225
|
pos.z += sin(uTime * 0.27 + ph * 2.3) * 0.06 * drift;
|
|
@@ -245,9 +247,11 @@ ${mixChain}
|
|
|
245
247
|
float sizeVar = mix(0.55 + aSeed * 0.9, 0.72 + aSeed * 0.35, uSettle);
|
|
246
248
|
// \u5B57\u5F62\u53CE\u675F\u6642\u306F\u96A3\u63A5\u7C92\u5B50\u3067\u9699\u9593\u3092\u57CB\u3081\u308B\u305F\u3081\u308F\u305A\u304B\u306B\u5927\u304D\u3081\uFF06\u5747\u4E00\u306B\u3002
|
|
247
249
|
sizeVar = mix(sizeVar, 0.95 + aSeed * 0.18, uForm);
|
|
248
|
-
|
|
250
|
+
// \u9AD8 dpr \u74B0\u5883\u3067\u306F\u5C0F\u7C92\u30FB\u4E0A\u9650\u4F4E\u3081\u306E\u65B9\u304C\u30A8\u30C3\u30B8\u304C\u7DE0\u307E\u308A\u9AD8\u7CBE\u7D30\u306B\u898B\u3048\u308B
|
|
251
|
+
// \uFF08\u30B3\u30FC\u30DD\u30EC\u30FC\u30C8\u30B5\u30A4\u30C8\u5B9F\u88C5\u3067\u5B9F\u8A3C\u30020.62 \u3068 clamp 4\u301C5 \u304C\u6700\u3082\u300C\u971E\u307E\u306A\u3044\u300D\uFF09\u3002
|
|
252
|
+
float s = uSize * sizeVar * 0.62 * uSizeScale;
|
|
249
253
|
gl_PointSize = s * uPixelRatio * (1.0 / -mvPosition.z);
|
|
250
|
-
gl_PointSize = clamp(gl_PointSize, 1.0, mix(
|
|
254
|
+
gl_PointSize = clamp(gl_PointSize, 1.0, mix(4.0, 5.0, uForm) * uPixelRatio);
|
|
251
255
|
}
|
|
252
256
|
`
|
|
253
257
|
);
|
|
@@ -257,6 +261,7 @@ var FRAGMENT_SHADER = (
|
|
|
257
261
|
`
|
|
258
262
|
uniform vec3 uColorInk;
|
|
259
263
|
uniform vec3 uColorAccent;
|
|
264
|
+
uniform float uSparkle;
|
|
260
265
|
|
|
261
266
|
varying float vSeed;
|
|
262
267
|
varying float vAccent;
|
|
@@ -278,7 +283,7 @@ var FRAGMENT_SHADER = (
|
|
|
278
283
|
|
|
279
284
|
// \u4E00\u90E8\u306E\u7C92\u306B\u660E\u308B\u3044\u304D\u3089\u3081\u304D\uFF08\u98DB\u6563\u6642\u306B\u6620\u3048\u308B\uFF09\u3002\u6574\u5217\u6642\u306F\u63A7\u3048\u3081\u3002
|
|
280
285
|
float spark = step(0.94, vSeed);
|
|
281
|
-
col = mix(col, uColorAccent, spark * mix(0.45, 0.15, vSettle));
|
|
286
|
+
col = mix(col, uColorAccent, spark * mix(0.45, 0.15, vSettle) * uSparkle);
|
|
282
287
|
|
|
283
288
|
// \u5965\u884C\u304D\u3067\u6FC3\u6DE1\uFF08\u660E\u80CC\u666F\u3067\u306E\u8996\u8A8D\u6027\u78BA\u4FDD\u306E\u305F\u3081\u4E0B\u9650\u3092\u6301\u305F\u305B\u308B\uFF09\u3002
|
|
284
289
|
float floorFade = mix(0.45, 0.78, vSettle);
|
|
@@ -412,6 +417,21 @@ function computeScreenRect(targets, viewportW, viewportH, visibleWorldW) {
|
|
|
412
417
|
};
|
|
413
418
|
}
|
|
414
419
|
var DEFAULT_TRIGGER_HEIGHT = 2;
|
|
420
|
+
function triangle(x) {
|
|
421
|
+
const t = x % 2;
|
|
422
|
+
return t <= 1 ? t : 2 - t;
|
|
423
|
+
}
|
|
424
|
+
function computeAutoplayProgress(elapsedSec, cfg) {
|
|
425
|
+
const duration = cfg.duration && cfg.duration > 0 ? cfg.duration : 4;
|
|
426
|
+
const delay = cfg.delay && cfg.delay > 0 ? cfg.delay : 0;
|
|
427
|
+
const t = elapsedSec - delay;
|
|
428
|
+
if (t <= 0) return 0;
|
|
429
|
+
const raw = t / duration;
|
|
430
|
+
if (cfg.loop) {
|
|
431
|
+
return cfg.pingpong ? triangle(raw) : raw % 1;
|
|
432
|
+
}
|
|
433
|
+
return clamp01(raw);
|
|
434
|
+
}
|
|
415
435
|
function clamp01(x) {
|
|
416
436
|
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
417
437
|
}
|
|
@@ -523,6 +543,7 @@ function GlyphPoints(props) {
|
|
|
523
543
|
keyframes,
|
|
524
544
|
count,
|
|
525
545
|
colors,
|
|
546
|
+
style,
|
|
526
547
|
cameraZ,
|
|
527
548
|
cameraFov,
|
|
528
549
|
pointer: pointerEnabled,
|
|
@@ -606,6 +627,9 @@ function GlyphPoints(props) {
|
|
|
606
627
|
uPointer: { value: new THREE__namespace.Vector3(0, 0, 0) },
|
|
607
628
|
uPointerActive: { value: 0 },
|
|
608
629
|
uSize: { value: 1 },
|
|
630
|
+
uSizeScale: { value: style.size },
|
|
631
|
+
uDrift: { value: style.drift },
|
|
632
|
+
uSparkle: { value: style.sparkle },
|
|
609
633
|
uPixelRatio: { value: 1 },
|
|
610
634
|
uColorInk: { value: colors.ink.clone() },
|
|
611
635
|
uColorAccent: { value: colors.accent.clone() }
|
|
@@ -803,9 +827,19 @@ function GlyphPoints(props) {
|
|
|
803
827
|
const mat = matRef.current;
|
|
804
828
|
if (!mat) return;
|
|
805
829
|
const u = mat.uniforms;
|
|
806
|
-
u.uPixelRatio.value = Math.min(window.devicePixelRatio || 1,
|
|
830
|
+
u.uPixelRatio.value = Math.min(window.devicePixelRatio || 1, 3);
|
|
807
831
|
u.uSize.value = Math.min(size.height / 18, 26);
|
|
808
832
|
}, [size]);
|
|
833
|
+
react.useEffect(() => {
|
|
834
|
+
const mat = matRef.current;
|
|
835
|
+
if (!mat) return;
|
|
836
|
+
const u = mat.uniforms;
|
|
837
|
+
u.uSizeScale.value = style.size;
|
|
838
|
+
u.uDrift.value = style.drift;
|
|
839
|
+
u.uSparkle.value = style.sparkle;
|
|
840
|
+
mat.blending = style.blend === "additive" ? THREE__namespace.AdditiveBlending : THREE__namespace.NormalBlending;
|
|
841
|
+
mat.needsUpdate = true;
|
|
842
|
+
}, [style.size, style.drift, style.sparkle, style.blend]);
|
|
809
843
|
fiber.useFrame((state, delta) => {
|
|
810
844
|
const p = pointsRef.current;
|
|
811
845
|
const mat = matRef.current;
|
|
@@ -813,7 +847,7 @@ function GlyphPoints(props) {
|
|
|
813
847
|
const u = mat.uniforms;
|
|
814
848
|
const d = Math.min(delta, 0.05);
|
|
815
849
|
const raw = THREE__namespace.MathUtils.clamp(getProgress(), 0, 1);
|
|
816
|
-
stage.current =
|
|
850
|
+
stage.current = raw;
|
|
817
851
|
const s = stage.current;
|
|
818
852
|
let settle = 0;
|
|
819
853
|
let burst = 0;
|
|
@@ -830,6 +864,11 @@ function GlyphPoints(props) {
|
|
|
830
864
|
if (lastIsText && n >= 2) {
|
|
831
865
|
form = smooth(times[n - 2] ?? 0, times[n - 1] ?? 1, s);
|
|
832
866
|
}
|
|
867
|
+
const firstIsText = timeline.isText[0] === true;
|
|
868
|
+
if (firstIsText && n >= 2) {
|
|
869
|
+
const formStart = 1 - smooth(times[0] ?? 0, times[1] ?? 1, s);
|
|
870
|
+
form = Math.max(form, formStart);
|
|
871
|
+
}
|
|
833
872
|
const guard = THREE__namespace.MathUtils.clamp(Math.max(settle, form), 0, 1);
|
|
834
873
|
guardRef.current = guard;
|
|
835
874
|
const swapped = raw >= timeline.swapAt ? 1 : 0;
|
|
@@ -880,7 +919,7 @@ function GlyphPoints(props) {
|
|
|
880
919
|
uniforms,
|
|
881
920
|
transparent: true,
|
|
882
921
|
depthWrite: false,
|
|
883
|
-
blending: THREE__namespace.NormalBlending,
|
|
922
|
+
blending: style.blend === "additive" ? THREE__namespace.AdditiveBlending : THREE__namespace.NormalBlending,
|
|
884
923
|
vertexShader,
|
|
885
924
|
fragmentShader: FRAGMENT_SHADER
|
|
886
925
|
}
|
|
@@ -894,6 +933,12 @@ var DEFAULT_COUNT_MOBILE = 5200;
|
|
|
894
933
|
var DEFAULT_CAMERA_Z = 7;
|
|
895
934
|
var DEFAULT_CAMERA_FOV = 42;
|
|
896
935
|
var DEFAULT_DPR = [1, 1.75];
|
|
936
|
+
var PRESETS = {
|
|
937
|
+
default: { size: 1, blend: "normal", drift: 1, sparkle: 1 },
|
|
938
|
+
minimal: { size: 0.92, blend: "normal", drift: 0.35, sparkle: 0 },
|
|
939
|
+
lively: { size: 1.05, blend: "normal", drift: 1.4, sparkle: 1.4 },
|
|
940
|
+
glow: { size: 1.1, blend: "additive", drift: 1.1, sparkle: 1.5 }
|
|
941
|
+
};
|
|
897
942
|
function clamp012(x) {
|
|
898
943
|
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
899
944
|
}
|
|
@@ -912,6 +957,8 @@ function GlyphDust(props) {
|
|
|
912
957
|
const {
|
|
913
958
|
keyframes,
|
|
914
959
|
driver = { type: "scroll" },
|
|
960
|
+
preset = "default",
|
|
961
|
+
style,
|
|
915
962
|
colors,
|
|
916
963
|
count,
|
|
917
964
|
dpr = DEFAULT_DPR,
|
|
@@ -935,15 +982,62 @@ function GlyphDust(props) {
|
|
|
935
982
|
const resolveRef = react.useRef(null);
|
|
936
983
|
const manualRef = react.useRef(0);
|
|
937
984
|
if (driver.type === "manual") manualRef.current = clamp012(driver.progress);
|
|
985
|
+
const autoplay = driver.type === "autoplay" ? driver : null;
|
|
986
|
+
const playingRef = react.useRef(false);
|
|
987
|
+
const startMsRef = react.useRef(null);
|
|
988
|
+
const lastAutoRef = react.useRef(0);
|
|
989
|
+
react.useEffect(() => {
|
|
990
|
+
if (!autoplay) return;
|
|
991
|
+
if (autoplay.playOnView === false) {
|
|
992
|
+
playingRef.current = true;
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const el = wrapperRef.current;
|
|
996
|
+
if (el === null || typeof IntersectionObserver === "undefined") {
|
|
997
|
+
playingRef.current = true;
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const io = new IntersectionObserver(
|
|
1001
|
+
(entries) => {
|
|
1002
|
+
for (const e of entries) {
|
|
1003
|
+
if (e.isIntersecting && !playingRef.current) {
|
|
1004
|
+
playingRef.current = true;
|
|
1005
|
+
startMsRef.current = null;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
{ threshold: 0.25 }
|
|
1010
|
+
);
|
|
1011
|
+
io.observe(el);
|
|
1012
|
+
return () => io.disconnect();
|
|
1013
|
+
}, [autoplay?.playOnView]);
|
|
938
1014
|
const getProgress = react.useCallback(() => {
|
|
939
1015
|
if (driver.type === "manual") return manualRef.current;
|
|
1016
|
+
if (driver.type === "autoplay") {
|
|
1017
|
+
if (!playingRef.current || typeof performance === "undefined") {
|
|
1018
|
+
return lastAutoRef.current;
|
|
1019
|
+
}
|
|
1020
|
+
if (startMsRef.current === null) startMsRef.current = performance.now();
|
|
1021
|
+
const elapsed = (performance.now() - startMsRef.current) / 1e3;
|
|
1022
|
+
lastAutoRef.current = computeAutoplayProgress(elapsed, driver);
|
|
1023
|
+
return lastAutoRef.current;
|
|
1024
|
+
}
|
|
940
1025
|
const el = wrapperRef.current;
|
|
941
1026
|
if (el === null || typeof window === "undefined") return 0;
|
|
942
1027
|
const rect = el.getBoundingClientRect();
|
|
943
1028
|
const total = rect.height - window.innerHeight;
|
|
944
1029
|
if (total <= 0) return 0;
|
|
945
1030
|
return clamp012(-rect.top / total);
|
|
946
|
-
}, [driver
|
|
1031
|
+
}, [driver]);
|
|
1032
|
+
const resolvedStyle = react.useMemo(() => {
|
|
1033
|
+
const base = PRESETS[preset] ?? PRESETS.default;
|
|
1034
|
+
return {
|
|
1035
|
+
size: style?.size ?? base.size,
|
|
1036
|
+
blend: style?.blend ?? base.blend,
|
|
1037
|
+
drift: style?.drift ?? base.drift,
|
|
1038
|
+
sparkle: style?.sparkle ?? base.sparkle
|
|
1039
|
+
};
|
|
1040
|
+
}, [preset, style?.size, style?.blend, style?.drift, style?.sparkle]);
|
|
947
1041
|
const resolvedColors = react.useMemo(
|
|
948
1042
|
() => ({
|
|
949
1043
|
ink: new THREE__namespace.Color(colors?.ink ?? DEFAULT_INK),
|
|
@@ -980,6 +1074,7 @@ function GlyphDust(props) {
|
|
|
980
1074
|
keyframes,
|
|
981
1075
|
count: particleCount,
|
|
982
1076
|
colors: resolvedColors,
|
|
1077
|
+
style: resolvedStyle,
|
|
983
1078
|
cameraZ,
|
|
984
1079
|
cameraFov,
|
|
985
1080
|
pointer: pointerEnabled,
|
|
@@ -1013,10 +1108,11 @@ function GlyphDust(props) {
|
|
|
1013
1108
|
}
|
|
1014
1109
|
) : null
|
|
1015
1110
|
] });
|
|
1016
|
-
if (driver.type === "manual") {
|
|
1111
|
+
if (driver.type === "manual" || driver.type === "autoplay") {
|
|
1017
1112
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1018
1113
|
"div",
|
|
1019
1114
|
{
|
|
1115
|
+
ref: wrapperRef,
|
|
1020
1116
|
className,
|
|
1021
1117
|
style: { position: "relative", width: "100%", height: "100%" },
|
|
1022
1118
|
children: scene
|
|
@@ -1048,7 +1144,7 @@ function GlyphDust(props) {
|
|
|
1048
1144
|
}
|
|
1049
1145
|
|
|
1050
1146
|
// src/index.ts
|
|
1051
|
-
var VERSION = "0.1
|
|
1147
|
+
var VERSION = "0.2.1";
|
|
1052
1148
|
|
|
1053
1149
|
exports.DEFAULT_TRIGGER_HEIGHT = DEFAULT_TRIGGER_HEIGHT;
|
|
1054
1150
|
exports.FRAGMENT_SHADER = FRAGMENT_SHADER;
|
|
@@ -1059,6 +1155,7 @@ exports.buildDenseTextTargets = buildDenseTextTargets;
|
|
|
1059
1155
|
exports.buildGlyphFromDOM = buildGlyphFromDOM;
|
|
1060
1156
|
exports.buildTextTargets = buildTextTargets;
|
|
1061
1157
|
exports.buildVertexShader = buildVertexShader;
|
|
1158
|
+
exports.computeAutoplayProgress = computeAutoplayProgress;
|
|
1062
1159
|
exports.computeScreenRect = computeScreenRect;
|
|
1063
1160
|
exports.createScrollProgress = createScrollProgress;
|
|
1064
1161
|
exports.glyphPositionAttribute = glyphPositionAttribute;
|