glyphdust 0.1.0 → 0.2.0
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/dist/index.cjs +120 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +120 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -248,7 +248,6 @@ ${mixChain}
|
|
|
248
248
|
float s = uSize * sizeVar;
|
|
249
249
|
gl_PointSize = s * uPixelRatio * (1.0 / -mvPosition.z);
|
|
250
250
|
gl_PointSize = clamp(gl_PointSize, 1.0, mix(7.0, 9.0, uForm) * uPixelRatio);
|
|
251
|
-
gl_PointSize = 10.0; // DEBUG4
|
|
252
251
|
}
|
|
253
252
|
`
|
|
254
253
|
);
|
|
@@ -341,10 +340,15 @@ function buildGlyphFromDOM(count, lines, opts) {
|
|
|
341
340
|
} catch {
|
|
342
341
|
}
|
|
343
342
|
}
|
|
344
|
-
const
|
|
343
|
+
const fm = ctx.measureText(lines[0] ?? "M");
|
|
344
|
+
const fbAsc = fm.fontBoundingBoxAscent;
|
|
345
|
+
const fbDesc = fm.fontBoundingBoxDescent;
|
|
346
|
+
const useMetrics = Number.isFinite(fbAsc) && Number.isFinite(fbDesc);
|
|
347
|
+
const fallbackAscent = fontSize * (opts.ascentRatio ?? 0.82);
|
|
345
348
|
lines.forEach((line, i) => {
|
|
346
349
|
const lineTop = i * lineHeight;
|
|
347
|
-
|
|
350
|
+
const baseline = useMetrics ? lineTop + (lineHeight - (fbAsc + fbDesc)) / 2 + fbAsc : lineTop + (lineHeight - fontSize) / 2 + fallbackAscent;
|
|
351
|
+
ctx.fillText(line, 0, baseline);
|
|
348
352
|
});
|
|
349
353
|
const { data } = ctx.getImageData(0, 0, cw, ch);
|
|
350
354
|
const pts = [];
|
|
@@ -356,8 +360,8 @@ function buildGlyphFromDOM(count, lines, opts) {
|
|
|
356
360
|
}
|
|
357
361
|
const filled = pts.length / 2;
|
|
358
362
|
if (filled === 0) return null;
|
|
359
|
-
const vpW = window.innerWidth;
|
|
360
|
-
const vpH = window.innerHeight;
|
|
363
|
+
const vpW = opts.viewportW ?? window.innerWidth;
|
|
364
|
+
const vpH = opts.viewportH ?? window.innerHeight;
|
|
361
365
|
const { worldW, worldH } = viewSizeAtZ0(vpW, vpH, opts.fovDeg, opts.cameraZ);
|
|
362
366
|
const pxToWorld = worldW / vpW;
|
|
363
367
|
const thickness = opts.thickness ?? 0.14;
|
|
@@ -525,9 +529,11 @@ function GlyphPoints(props) {
|
|
|
525
529
|
drag: dragEnabled,
|
|
526
530
|
getProgress,
|
|
527
531
|
timing,
|
|
528
|
-
resolveRef
|
|
532
|
+
resolveRef,
|
|
533
|
+
resolveDomSelector
|
|
529
534
|
} = props;
|
|
530
535
|
const pointsRef = react.useRef(null);
|
|
536
|
+
const resolveDomElRef = react.useRef(null);
|
|
531
537
|
const matRef = react.useRef(null);
|
|
532
538
|
const { size, gl } = fiber.useThree();
|
|
533
539
|
const pointer = react.useRef({ x: 0, y: 0, active: 0 });
|
|
@@ -617,11 +623,93 @@ function GlyphPoints(props) {
|
|
|
617
623
|
const { worldW: visW } = viewSizeAtZ0(vpW, vpH, cameraFov, cameraZ);
|
|
618
624
|
const rect = computeScreenRect(finalBuf, vpW, vpH, visW);
|
|
619
625
|
if (!rect) return;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
626
|
+
const finalKf = keyframes[n - 1];
|
|
627
|
+
const fontStr = finalKf?.type === "text" && finalKf.font ? finalKf.font : DEFAULT_DENSE_FONT;
|
|
628
|
+
const fontMatch = fontStr.match(/^\s*(\d+)\s+[\d.]+px\s+(.+)$/);
|
|
629
|
+
const fontWeight = fontMatch?.[1] ?? "900";
|
|
630
|
+
const fontFamily = fontMatch?.[2] ?? "sans-serif";
|
|
631
|
+
const text = timeline.resolveText;
|
|
632
|
+
const ctx = document.createElement("canvas").getContext("2d", {
|
|
633
|
+
willReadFrequently: true
|
|
634
|
+
});
|
|
635
|
+
let positioned = false;
|
|
636
|
+
if (ctx && text) {
|
|
637
|
+
const baseSize = 200;
|
|
638
|
+
ctx.font = `${fontWeight} ${baseSize}px ${fontFamily}`;
|
|
639
|
+
const advBase = ctx.measureText(text).width;
|
|
640
|
+
const pad = Math.ceil(baseSize * 0.6);
|
|
641
|
+
const cw = Math.ceil(advBase + pad * 2);
|
|
642
|
+
const ch = Math.ceil(baseSize * 1.8);
|
|
643
|
+
const oc = document.createElement("canvas");
|
|
644
|
+
oc.width = cw;
|
|
645
|
+
oc.height = ch;
|
|
646
|
+
const octx = oc.getContext("2d", { willReadFrequently: true });
|
|
647
|
+
if (octx) {
|
|
648
|
+
const drawX = pad;
|
|
649
|
+
const drawY = Math.round(ch * 0.72);
|
|
650
|
+
octx.font = `${fontWeight} ${baseSize}px ${fontFamily}`;
|
|
651
|
+
octx.textAlign = "left";
|
|
652
|
+
octx.textBaseline = "alphabetic";
|
|
653
|
+
octx.fillStyle = "#000";
|
|
654
|
+
octx.fillText(text, drawX, drawY);
|
|
655
|
+
const data = octx.getImageData(0, 0, cw, ch).data;
|
|
656
|
+
let minX = cw;
|
|
657
|
+
let maxX = 0;
|
|
658
|
+
let minY = ch;
|
|
659
|
+
let maxY = 0;
|
|
660
|
+
let found = 0;
|
|
661
|
+
for (let y = 0; y < ch; y++) {
|
|
662
|
+
for (let x = 0; x < cw; x++) {
|
|
663
|
+
if (data[(y * cw + x) * 4 + 3] > 20) {
|
|
664
|
+
if (x < minX) minX = x;
|
|
665
|
+
if (x > maxX) maxX = x;
|
|
666
|
+
if (y < minY) minY = y;
|
|
667
|
+
if (y > maxY) maxY = y;
|
|
668
|
+
found++;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (found > 0) {
|
|
673
|
+
const fontSize = baseSize * (rect.width / (maxX - minX));
|
|
674
|
+
const scale = fontSize / baseSize;
|
|
675
|
+
const inkCenterXFromStart = ((minX + maxX) / 2 - drawX) * scale;
|
|
676
|
+
const inkCenterYFromBaseline = ((minY + maxY) / 2 - drawY) * scale;
|
|
677
|
+
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
678
|
+
const fm = ctx.measureText(text);
|
|
679
|
+
const leading = fontSize - (fm.fontBoundingBoxAscent + fm.fontBoundingBoxDescent);
|
|
680
|
+
const baselineFromTop = leading / 2 + fm.fontBoundingBoxAscent;
|
|
681
|
+
const targetCx = rect.left + rect.width / 2;
|
|
682
|
+
const targetCy = rect.top + rect.height / 2;
|
|
683
|
+
el.style.display = "block";
|
|
684
|
+
el.style.textAlign = "left";
|
|
685
|
+
el.style.whiteSpace = "nowrap";
|
|
686
|
+
el.style.width = "auto";
|
|
687
|
+
el.style.height = "auto";
|
|
688
|
+
el.style.fontFamily = fontFamily;
|
|
689
|
+
el.style.fontWeight = fontWeight;
|
|
690
|
+
el.style.fontSize = `${fontSize}px`;
|
|
691
|
+
el.style.left = `${targetCx - inkCenterXFromStart}px`;
|
|
692
|
+
el.style.top = `${targetCy - inkCenterYFromBaseline - baselineFromTop}px`;
|
|
693
|
+
positioned = true;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (!positioned) {
|
|
698
|
+
const measureSize = 100;
|
|
699
|
+
let fontSize = rect.height * 0.92;
|
|
700
|
+
if (ctx && text) {
|
|
701
|
+
ctx.font = `${fontWeight} ${measureSize}px ${fontFamily}`;
|
|
702
|
+
const w = ctx.measureText(text).width;
|
|
703
|
+
if (w > 0) fontSize = measureSize * (rect.width / w);
|
|
704
|
+
}
|
|
705
|
+
el.style.left = `${rect.left}px`;
|
|
706
|
+
el.style.top = `${rect.top}px`;
|
|
707
|
+
el.style.width = `${rect.width}px`;
|
|
708
|
+
el.style.height = `${rect.height}px`;
|
|
709
|
+
el.style.fontFamily = fontFamily;
|
|
710
|
+
el.style.fontWeight = fontWeight;
|
|
711
|
+
el.style.fontSize = `${fontSize}px`;
|
|
712
|
+
}
|
|
625
713
|
};
|
|
626
714
|
const rebuildDomGlyphs = () => {
|
|
627
715
|
keyframes.forEach((kf, i) => {
|
|
@@ -629,7 +717,11 @@ function GlyphPoints(props) {
|
|
|
629
717
|
const next = buildGlyphFromDOM(count, kf.text.split("\n"), {
|
|
630
718
|
selector: kf.domSelector,
|
|
631
719
|
fovDeg: cameraFov,
|
|
632
|
-
cameraZ
|
|
720
|
+
cameraZ,
|
|
721
|
+
// 粒子がレンダリングされる canvas の実寸(CSS px)。
|
|
722
|
+
// window.innerWidth だとスクロールバー分ずれるため size を使う。
|
|
723
|
+
viewportW: size.width,
|
|
724
|
+
viewportH: size.height
|
|
633
725
|
});
|
|
634
726
|
if (!next) return;
|
|
635
727
|
const attr = built.geo.getAttribute(glyphPositionAttribute(i));
|
|
@@ -742,6 +834,7 @@ function GlyphPoints(props) {
|
|
|
742
834
|
guardRef.current = guard;
|
|
743
835
|
const swapped = raw >= timeline.swapAt ? 1 : 0;
|
|
744
836
|
const resolve = timeline.hasResolve ? smooth(0.9, 0.98, raw) : 0;
|
|
837
|
+
const textReveal = timeline.hasResolve ? smooth(0.92, 1, raw) : 0;
|
|
745
838
|
u.uTime.value = state.clock.elapsedTime;
|
|
746
839
|
u.uStage.value = s;
|
|
747
840
|
u.uForm.value = form;
|
|
@@ -769,8 +862,16 @@ function GlyphPoints(props) {
|
|
|
769
862
|
rot.current.x = THREE__namespace.MathUtils.lerp(rot.current.x, 0, 0.04 + guard * 0.14);
|
|
770
863
|
p.rotation.x = rot.current.x;
|
|
771
864
|
p.rotation.y = rot.current.y;
|
|
772
|
-
|
|
773
|
-
|
|
865
|
+
if (timeline.hasResolve) {
|
|
866
|
+
let target = resolveRef?.current ?? null;
|
|
867
|
+
if (!target && resolveDomSelector) {
|
|
868
|
+
if (!resolveDomElRef.current) {
|
|
869
|
+
resolveDomElRef.current = document.querySelector(resolveDomSelector);
|
|
870
|
+
}
|
|
871
|
+
target = resolveDomElRef.current;
|
|
872
|
+
}
|
|
873
|
+
if (target) target.style.opacity = String(textReveal);
|
|
874
|
+
}
|
|
774
875
|
});
|
|
775
876
|
return /* @__PURE__ */ jsxRuntime.jsx("points", { ref: pointsRef, geometry: built.geo, frustumCulled: false, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
776
877
|
"shaderMaterial",
|
|
@@ -858,6 +959,8 @@ function GlyphDust(props) {
|
|
|
858
959
|
const dragEnabled = interaction?.drag ?? true;
|
|
859
960
|
const finalKf = keyframes[keyframes.length - 1];
|
|
860
961
|
const hasResolve = finalKf?.type === "text" && finalKf.resolveToDom === true;
|
|
962
|
+
const resolveDomSelector = finalKf?.type === "text" && finalKf.resolveToDom === true && finalKf.domSelector ? finalKf.domSelector : void 0;
|
|
963
|
+
const useOwnOverlay = hasResolve && !resolveDomSelector;
|
|
861
964
|
const resolveText = finalKf?.type === "text" ? finalKf.text.replace(/\n/g, " ") : "";
|
|
862
965
|
if (reduced || !webgl) {
|
|
863
966
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
@@ -883,12 +986,13 @@ function GlyphDust(props) {
|
|
|
883
986
|
drag: dragEnabled,
|
|
884
987
|
getProgress,
|
|
885
988
|
timing,
|
|
886
|
-
resolveRef:
|
|
989
|
+
resolveRef: useOwnOverlay ? resolveRef : void 0,
|
|
990
|
+
resolveDomSelector
|
|
887
991
|
}
|
|
888
992
|
)
|
|
889
993
|
}
|
|
890
994
|
),
|
|
891
|
-
|
|
995
|
+
useOwnOverlay ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
892
996
|
"div",
|
|
893
997
|
{
|
|
894
998
|
ref: resolveRef,
|