dom-sync-gl 0.0.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/LICENSE +21 -0
- package/README.md +273 -0
- package/dist/Camera.d.ts +16 -0
- package/dist/Core.d.ts +205 -0
- package/dist/Dom3DObject.d.ts +74 -0
- package/dist/DomPlane.d.ts +130 -0
- package/dist/DomPositionCalculator.d.ts +45 -0
- package/dist/EffectComposer.d.ts +72 -0
- package/dist/Light.d.ts +18 -0
- package/dist/PlaneComposer.d.ts +70 -0
- package/dist/RafScroll.d.ts +104 -0
- package/dist/ScrollSync.d.ts +156 -0
- package/dist/constants.d.ts +27 -0
- package/dist/effects/BaseEffect.d.ts +58 -0
- package/dist/index.cjs +31 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +1695 -0
- package/dist/index.js.map +1 -0
- package/dist/scenes/BaseScene.d.ts +12 -0
- package/dist/types.d.ts +132 -0
- package/package.json +60 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { default as GUI } from 'lil-gui';
|
|
2
|
+
import { CreatePlaneOptions } from './types';
|
|
3
|
+
import { DomPositionCalculator } from './DomPositionCalculator';
|
|
4
|
+
import { BaseEffect } from './effects/BaseEffect';
|
|
5
|
+
import * as THREE from "three";
|
|
6
|
+
export declare class DomPlane {
|
|
7
|
+
element: HTMLElement | null;
|
|
8
|
+
texture: THREE.Texture | null;
|
|
9
|
+
mesh: THREE.Mesh;
|
|
10
|
+
geometry: THREE.PlaneGeometry;
|
|
11
|
+
material: THREE.ShaderMaterial;
|
|
12
|
+
scene: THREE.Scene;
|
|
13
|
+
clock: THREE.Clock;
|
|
14
|
+
positionCalculator: DomPositionCalculator | null;
|
|
15
|
+
canvasRect: DOMRect;
|
|
16
|
+
isVisible: boolean;
|
|
17
|
+
private updateRectEveryFrame;
|
|
18
|
+
private observer;
|
|
19
|
+
private destroyed;
|
|
20
|
+
private planeComposer;
|
|
21
|
+
private renderer;
|
|
22
|
+
private effects;
|
|
23
|
+
/**
|
|
24
|
+
* effect.update に渡す mouse UV のスクラッチ。
|
|
25
|
+
* `material.uniforms.uMouseUV.value` を直接渡すと effect 側で `.set()` 等の破壊的操作で
|
|
26
|
+
* plane の uniform が書き換わる事故が起きうるため、毎フレ copy したスクラッチを渡す。
|
|
27
|
+
*/
|
|
28
|
+
private readonly _effectMouseUV;
|
|
29
|
+
/**
|
|
30
|
+
* effect の `setupGUI` 呼び出し時に root GUI を渡すための provider。
|
|
31
|
+
* WebGLApp.createPlane() でセットされる。null の時は GUI を作らない(showGUI: false 等)。
|
|
32
|
+
*
|
|
33
|
+
* lil-gui は optional peer の dynamic import なので Promise を返す。
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
private guiProvider;
|
|
37
|
+
/** 自前で load した texture かどうか。destroy() で dispose してよいか判定する。 */
|
|
38
|
+
private ownsTexture;
|
|
39
|
+
/**
|
|
40
|
+
* options.crossOrigin を指定された場合のみ生成する per-instance loader。
|
|
41
|
+
* 指定なしの場合は sharedTextureLoader をそのまま使う。
|
|
42
|
+
*/
|
|
43
|
+
private crossOrigin;
|
|
44
|
+
constructor(el: HTMLElement | null, scene: THREE.Scene, canvasRect: DOMRect, renderer: THREE.WebGLRenderer, options?: CreatePlaneOptions, sharedClock?: THREE.Clock);
|
|
45
|
+
/**
|
|
46
|
+
* Phase B-read (DOM read): getBoundingClientRect 等のレイアウト読み取りのみを行う。
|
|
47
|
+
* Core.animate では paint 直前にまとめて呼ばれ、その直後に _tickApply が走る。
|
|
48
|
+
*
|
|
49
|
+
* **更新条件**: `updateRectEveryFrame: true` のときのみ毎フレ更新する。
|
|
50
|
+
* static 要素は document 座標で固定 / fixed 要素は viewport 座標で固定なので、
|
|
51
|
+
* 自身の CSS animation 等で動的に位置が変わるケースのみフラグを true に。
|
|
52
|
+
* (旧仕様では isFixed のとき自動的に毎フレ更新していたが、多くの場合不要な
|
|
53
|
+
* layout 強制になっていたため明示フラグ駆動に変更)
|
|
54
|
+
* @internal Core.animate から呼ばれる。
|
|
55
|
+
*/
|
|
56
|
+
_tickRead(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Phase B-apply: mesh.position と uniforms の書き込み。DOM には触れない。
|
|
59
|
+
* @internal Core.animate から呼ばれる。
|
|
60
|
+
*/
|
|
61
|
+
_tickApply(elapsedTime: number, scrollX: number, scrollY: number): void;
|
|
62
|
+
/**
|
|
63
|
+
* Phase C (PlaneComposer render): main scene レンダリング前に
|
|
64
|
+
* 自前の plane をローカル FBO に焼いてエフェクトを通す。
|
|
65
|
+
* @internal Core.animate から呼ばれる。
|
|
66
|
+
*/
|
|
67
|
+
_tickRenderComposer(): void;
|
|
68
|
+
private init;
|
|
69
|
+
private loadTexture;
|
|
70
|
+
private updateSize;
|
|
71
|
+
private setPosition;
|
|
72
|
+
setCanvasRect(canvasRect: DOMRect): void;
|
|
73
|
+
resize(): void;
|
|
74
|
+
getMesh(): THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>, THREE.Material | THREE.Material[], THREE.Object3DEventMap>;
|
|
75
|
+
/**
|
|
76
|
+
* テクスチャを差し替える。
|
|
77
|
+
* 直接 `material.uniforms.uTexture.value = tex` する代わりに使うと、
|
|
78
|
+
* 旧テクスチャの所有権(自前 load かどうか)を考慮して安全に dispose してくれる。
|
|
79
|
+
*
|
|
80
|
+
* @param texture 新しい texture
|
|
81
|
+
* @param takeOwnership true なら destroy() 時にこの texture も dispose する。
|
|
82
|
+
* 他で使い回す texture を渡すときは false。
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* 現在の `data-texture` 属性を再読込してマテリアルに反映する。
|
|
86
|
+
* SPA で plane を貼ったまま画像 URL だけ差し替えたいケース用。
|
|
87
|
+
* 旧 texture が自前 load の場合は dispose する。属性が無ければ何もしない。
|
|
88
|
+
*/
|
|
89
|
+
reloadTexture(): void;
|
|
90
|
+
setTexture(texture: THREE.Texture, takeOwnership?: boolean): void;
|
|
91
|
+
/**
|
|
92
|
+
* 紐づけられたエフェクトに update を流す。Core.animate から呼ばれる。
|
|
93
|
+
*
|
|
94
|
+
* 引数 `globalMouse` は canvas 全体の UV(0〜1, Y-up)。これを **plane ローカルの UV** に変換して
|
|
95
|
+
* effect.update に渡す。これで FluidEffect 等が plane の中の座標として mouse を扱える。
|
|
96
|
+
*
|
|
97
|
+
* - フルスクリーン plane(element 無し): そのまま globalMouse を渡す
|
|
98
|
+
* - DOM 配置 plane: 自分の DOM rect と canvasRect から local UV を算出
|
|
99
|
+
* - マウスが plane の外にいる時は **last value を据え置き**(uv 更新せず)。
|
|
100
|
+
* これにより solver 側で delta が 0 になり force が立たない、自然な挙動になる。
|
|
101
|
+
*
|
|
102
|
+
* 注意: raycast には依存しない。PlaneComposer が sourceMesh を main scene から外すと
|
|
103
|
+
* raycast が思った通り当たらないケースがあるため。
|
|
104
|
+
*
|
|
105
|
+
* scrollX/Y は Core.animate が 1 rAF tick 上で 1 回確定したスナップショット値を渡す。
|
|
106
|
+
* ここで `window.scrollX/Y` を直接読むと
|
|
107
|
+
*(同一 frame で全コンポーネントが同じ scroll 値を共有する)を壊すため必ず引数経由。
|
|
108
|
+
* 後方互換のため省略可能だが、その場合は window から読む (= 古い挙動)。
|
|
109
|
+
*/
|
|
110
|
+
updateEffects(time: number, globalMouse?: THREE.Vector2, scrollX?: number, scrollY?: number): void;
|
|
111
|
+
/** 直近の plane ローカル UV を取得(updateEffects 内で算出された値、0〜1)。 */
|
|
112
|
+
getMouseUV(): THREE.Vector2;
|
|
113
|
+
/** マウスが現在 plane の上に乗っているかどうか。 */
|
|
114
|
+
isHovered(): boolean;
|
|
115
|
+
setHoverInfo(isHovered: boolean, uv: THREE.Vector2 | null): void;
|
|
116
|
+
private enableEffects;
|
|
117
|
+
addEffect<T extends BaseEffect>(effect: T): T;
|
|
118
|
+
/**
|
|
119
|
+
* `addEffect()` で登録した effect を取り除き、pass material を dispose する。
|
|
120
|
+
* 登録されていない effect を渡した時は何もしない(戻り値 false)。
|
|
121
|
+
*/
|
|
122
|
+
removeEffect(effect: BaseEffect): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* WebGLApp.createPlane() から呼ばれる。GUI lazy 取得関数を渡す。
|
|
125
|
+
* null を渡すと GUI 統合を無効化(showGUI: false 相当)。
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
128
|
+
_setGuiProvider(provider: (() => Promise<GUI>) | null): void;
|
|
129
|
+
destroy(): void;
|
|
130
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM要素の位置計算を担当するユーティリティクラス
|
|
3
|
+
*/
|
|
4
|
+
export declare class DomPositionCalculator {
|
|
5
|
+
private element;
|
|
6
|
+
private canvasRect;
|
|
7
|
+
private positionInfo;
|
|
8
|
+
rect: DOMRect;
|
|
9
|
+
private readonly _outPosition;
|
|
10
|
+
constructor(element: HTMLElement, canvasRect: DOMRect);
|
|
11
|
+
/**
|
|
12
|
+
* DOM要素の位置情報を更新(毎フレーム呼ばれる)
|
|
13
|
+
*/
|
|
14
|
+
updatePositionInfo(): void;
|
|
15
|
+
/**
|
|
16
|
+
* position: fixed または sticky かどうかを再チェック(初期化・リサイズ時のみ呼ぶ)。
|
|
17
|
+
* fixed / sticky 要素は document 上で位置が固定されないため、毎フレーム rect.top を
|
|
18
|
+
* viewport 座標として扱う isFixed branch を使う。
|
|
19
|
+
*/
|
|
20
|
+
refreshPositionType(): void;
|
|
21
|
+
/**
|
|
22
|
+
* WebGL座標系での位置を計算。
|
|
23
|
+
* 戻り値は内部バッファを使い回すため、保持する場合は呼び出し側でコピーすること。
|
|
24
|
+
*/
|
|
25
|
+
calculateWebGLPosition(scrollX?: number, scrollY?: number): {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Canvas矩形を更新
|
|
31
|
+
*/
|
|
32
|
+
setCanvasRect(canvasRect: DOMRect): void;
|
|
33
|
+
/**
|
|
34
|
+
* position: fixedかどうか
|
|
35
|
+
*/
|
|
36
|
+
get isFixed(): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* ページトップ位置
|
|
39
|
+
*/
|
|
40
|
+
get pageTop(): number;
|
|
41
|
+
/**
|
|
42
|
+
* ページレフト位置
|
|
43
|
+
*/
|
|
44
|
+
get pageLeft(): number;
|
|
45
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* `WebGLApp.setPostEffect()` で差し込めるポストエフェクトの最小契約。
|
|
4
|
+
* `EffectComposer` は実装している。自前で差し込みたい場合は構造的にこれを満たせばよい。
|
|
5
|
+
*/
|
|
6
|
+
export interface EffectLike {
|
|
7
|
+
render(scene: THREE.Scene, camera: THREE.Camera): void;
|
|
8
|
+
resize(width: number, height: number): void;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
11
|
+
export interface EffectOptions {
|
|
12
|
+
fragmentShader: string;
|
|
13
|
+
uniforms?: {
|
|
14
|
+
[key: string]: THREE.IUniform;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface EffectTarget {
|
|
18
|
+
addEffect(options: EffectOptions): EffectPass;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* addEffect() で返されるハンドル。uniform の更新に使う。
|
|
22
|
+
*/
|
|
23
|
+
export declare class EffectPass {
|
|
24
|
+
readonly material: THREE.ShaderMaterial;
|
|
25
|
+
/** false の時、EffectComposer はこのパスをスキップする(パススルー扱い)。 */
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
constructor(material: THREE.ShaderMaterial);
|
|
28
|
+
setUniform(key: string, value: unknown): void;
|
|
29
|
+
getUniform(key: string): THREE.IUniform | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 複数のポストエフェクトをチェーンで適用するクラス。
|
|
33
|
+
*
|
|
34
|
+
* RenderTarget はピンポン方式で2つのみ使用。
|
|
35
|
+
* エフェクト数に関わらずメモリ使用量は一定。
|
|
36
|
+
*
|
|
37
|
+
* render flow (エフェクト3つの場合):
|
|
38
|
+
* scene → TargetA
|
|
39
|
+
* pass1: A → B
|
|
40
|
+
* pass2: B → A
|
|
41
|
+
* pass3: A → canvas (null)
|
|
42
|
+
*/
|
|
43
|
+
export declare class EffectComposer implements EffectTarget, EffectLike {
|
|
44
|
+
private renderer;
|
|
45
|
+
private passes;
|
|
46
|
+
private targetA;
|
|
47
|
+
private targetB;
|
|
48
|
+
private postScene;
|
|
49
|
+
private postCamera;
|
|
50
|
+
private postMesh;
|
|
51
|
+
private geometry;
|
|
52
|
+
/**
|
|
53
|
+
* dispose 後の API 呼び出しを no-op にするフラグ。
|
|
54
|
+
* dispose 済みの RT に render すると INVALID_OPERATION になるため。
|
|
55
|
+
*/
|
|
56
|
+
private _disposed;
|
|
57
|
+
constructor(renderer: THREE.WebGLRenderer, width: number, height: number);
|
|
58
|
+
addEffect(options: EffectOptions): EffectPass;
|
|
59
|
+
/**
|
|
60
|
+
* 登録済みパスを 1 つ取り除いて material を dispose する。
|
|
61
|
+
* `addEffect()` の戻り値か、`BaseEffect.getPass()` の値を渡す。
|
|
62
|
+
* 存在しない pass を渡した時は何もしない。
|
|
63
|
+
*/
|
|
64
|
+
removeEffect(pass: EffectPass): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* シーンをレンダリングし、登録されたエフェクトを順に適用する。
|
|
67
|
+
* エフェクトが0個の場合は通常レンダリング。
|
|
68
|
+
*/
|
|
69
|
+
render(scene: THREE.Scene, camera: THREE.Camera): void;
|
|
70
|
+
resize(width: number, height: number): void;
|
|
71
|
+
dispose(): void;
|
|
72
|
+
}
|
package/dist/Light.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
/**
|
|
3
|
+
* シンプルな環境光 + 平行光の組み合わせを scene に追加するクラス。
|
|
4
|
+
*
|
|
5
|
+
* **制約**:
|
|
6
|
+
* - shadow(影)は無効。`castShadow` / `receiveShadow` を有効化したい場合は
|
|
7
|
+
* `directionalLight.castShadow = true` を外から設定し、別途 shadow map 設定が必要。
|
|
8
|
+
*
|
|
9
|
+
* 簡易ライティングで十分なケースを想定。複雑なライト設計が必要なら、
|
|
10
|
+
* `WebGLApp.getLight()` から内部 light を取り出して直接操作するか、
|
|
11
|
+
* `WebGLApp.getScene()` に独自ライトを追加する。
|
|
12
|
+
*/
|
|
13
|
+
export declare class Light {
|
|
14
|
+
readonly ambientLight: THREE.AmbientLight;
|
|
15
|
+
readonly directionalLight: THREE.DirectionalLight;
|
|
16
|
+
private readonly scene;
|
|
17
|
+
constructor(scene: THREE.Scene);
|
|
18
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { EffectOptions, EffectTarget, EffectPass } from './EffectComposer';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
/**
|
|
4
|
+
* DomPlane 単体にエフェクトチェーンを適用するクラス。
|
|
5
|
+
*
|
|
6
|
+
* render flow:
|
|
7
|
+
* plane.mesh (local scene) → targetA
|
|
8
|
+
* pass1: A → B
|
|
9
|
+
* pass2: B → A
|
|
10
|
+
* ...
|
|
11
|
+
* final target → proxyMesh.material.map (main scene に表示)
|
|
12
|
+
*
|
|
13
|
+
* plane.mesh はコンストラクト時に main scene から取り出し local scene へ移動。
|
|
14
|
+
* proxyMesh がその位置に差し替わり、エフェクト適用後のテクスチャを表示する。
|
|
15
|
+
*
|
|
16
|
+
* **制約・落とし穴**:
|
|
17
|
+
* - main scene に置く `proxyMesh` は固定で `MeshBasicMaterial({ transparent: true })`。
|
|
18
|
+
* `DomPlane` 本体の `ShaderMaterial` の depthWrite / depthTest / blending 設定は反映されない。
|
|
19
|
+
* 不透明背景のシーンに混ぜると z 順がおかしく見えることがある(透過レイヤ前提のデザイン推奨)。
|
|
20
|
+
* - `localMesh` は `sourceMesh.geometry` / `material` を**共有**して描画する。
|
|
21
|
+
* `DomPlane.setHoverInfo()` 等で uniform を更新すれば自動でこちらにも反映される。
|
|
22
|
+
* ただし raycast の hit ターゲットは main scene にある `proxyMesh`(material が違う)に
|
|
23
|
+
* なるため、`uv` 結果は plane の物理位置に対して正しく出る。
|
|
24
|
+
* - `addEffect` を 0 回しか呼ばないケースでも `render()` は targetA 経由でプロキシに焼く。
|
|
25
|
+
* この場合 proxy はエフェクトなしの sourceMesh の見た目をそのまま表示する。
|
|
26
|
+
*/
|
|
27
|
+
export declare class PlaneComposer implements EffectTarget {
|
|
28
|
+
private renderer;
|
|
29
|
+
private sourceMesh;
|
|
30
|
+
private passes;
|
|
31
|
+
private targetA;
|
|
32
|
+
private targetB;
|
|
33
|
+
private localScene;
|
|
34
|
+
private localCamera;
|
|
35
|
+
private localMesh;
|
|
36
|
+
private postScene;
|
|
37
|
+
private postCamera;
|
|
38
|
+
private postMesh;
|
|
39
|
+
private postGeo;
|
|
40
|
+
private proxyMesh;
|
|
41
|
+
private proxyMaterial;
|
|
42
|
+
private proxyGeo;
|
|
43
|
+
private mainScene;
|
|
44
|
+
/**
|
|
45
|
+
* 全 effect が enabled=false の時、PlaneComposer をスキップして sourceMesh を
|
|
46
|
+
* 直接 main scene で描画する状態(bypass)。 fullscreen pass 1 回ぶんを丸ごと
|
|
47
|
+
* 削減できる。effect が再度 enable された時は通常レンダリングに復帰する。
|
|
48
|
+
*/
|
|
49
|
+
private _bypassed;
|
|
50
|
+
/** dispose 済みフラグ。dispose 後の render / addEffect を no-op にする。 */
|
|
51
|
+
private _disposed;
|
|
52
|
+
constructor(renderer: THREE.WebGLRenderer, sourceMesh: THREE.Mesh, mainScene: THREE.Scene, width: number, height: number);
|
|
53
|
+
addEffect(options: EffectOptions): EffectPass;
|
|
54
|
+
/**
|
|
55
|
+
* 登録済みパスを 1 つ取り除いて material を dispose する。
|
|
56
|
+
* 存在しない pass を渡した時は何もしない。
|
|
57
|
+
*/
|
|
58
|
+
removeEffect(pass: EffectPass): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* bypass 状態に切り替える: sourceMesh を main scene に戻し proxyMesh を隠す。
|
|
61
|
+
* 全 effect が disabled の時に呼ばれ、PlaneComposer.render の fullscreen pass を
|
|
62
|
+
* まるごと省く (= 1 pass / 6.35M フラグメント分の節約)。
|
|
63
|
+
*/
|
|
64
|
+
private enterBypass;
|
|
65
|
+
/** bypass 状態から通常レンダリングへ復帰する。 */
|
|
66
|
+
private exitBypass;
|
|
67
|
+
render(): void;
|
|
68
|
+
resize(width: number, height: number): void;
|
|
69
|
+
dispose(): void;
|
|
70
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RafScroll — rAF 同期 virtual scroll の最小実装。
|
|
3
|
+
*
|
|
4
|
+
* wheel / touch を `preventDefault` で受け取り、内部 accumulator に加算した値を
|
|
5
|
+
* 毎 rAF tick で `window.scrollTo()` に流す。
|
|
6
|
+
*
|
|
7
|
+
* これにより:
|
|
8
|
+
* - `window.scrollY` が **rAF tick 上でのみ更新**される
|
|
9
|
+
* - JS rAF 時の `window.scrollY` と paint 時の actual scroll が同一フレームで揃う
|
|
10
|
+
* - ScrollSync の cancel (container.transform と sceneY 計算で同じ scrollY を
|
|
11
|
+
* 共有する不変条件) が paint 時まで厳密に成立する
|
|
12
|
+
*
|
|
13
|
+
* Lenis から「rAF 同期に必要な最小機能」だけ抜き出した実装。wheel/drag 中の smoothing
|
|
14
|
+
* は無し (`lerp: 1` 相当)、accessibility (キーボードスクロール) や programmatic scroll
|
|
15
|
+
* (`window.scrollTo` 外部呼び出し) との同期は対応しない。
|
|
16
|
+
*
|
|
17
|
+
* **touch 慣性 (momentum)**: ネイティブ touch scroll を `preventDefault` で抑止する以上、
|
|
18
|
+
* OS が提供する慣性も失われる。代わりに自前で実装する:
|
|
19
|
+
* - `touchmove` で指の速度 (px/ms) を EMA 平滑化しつつ追跡
|
|
20
|
+
* - `touchend` 時点の velocity を初速として rAF tick 上で指数減衰させて scrollY に積む
|
|
21
|
+
* - 端到達 (clamp) / 次の touchstart / wheel 入力で慣性は即キャンセル
|
|
22
|
+
*
|
|
23
|
+
* @see https://github.com/darkroomengineering/lenis (元設計)
|
|
24
|
+
*/
|
|
25
|
+
export interface RafScrollOptions {
|
|
26
|
+
/**
|
|
27
|
+
* `WheelEvent.deltaMode === DOM_DELTA_LINE` のとき 1 行あたりの pixel 数。
|
|
28
|
+
* Firefox の wheel が deltaMode=1 を返すケースで使われる。
|
|
29
|
+
* @default 16
|
|
30
|
+
*/
|
|
31
|
+
lineHeight?: number;
|
|
32
|
+
/**
|
|
33
|
+
* touch リリース後の慣性減衰率。16.67ms 換算 1 フレームあたり velocity に乗算される値。
|
|
34
|
+
* 1 に近いほど慣性が長く続き、0 に近いほどすぐ止まる。0 で慣性無効 (即時停止)。
|
|
35
|
+
* @default 0.95
|
|
36
|
+
*/
|
|
37
|
+
touchFriction?: number;
|
|
38
|
+
}
|
|
39
|
+
export declare class RafScroll {
|
|
40
|
+
private _scrollY;
|
|
41
|
+
/**
|
|
42
|
+
* 直近 `window.scrollTo` に渡した値。scrollY が変わっていないフレで
|
|
43
|
+
* scrollTo を no-op で呼ぶと scroll event が連発して page 側 listener を
|
|
44
|
+
* 無駄に叩くので、差分があるときだけ呼び出すための diff キャッシュ。
|
|
45
|
+
*/
|
|
46
|
+
private _lastAppliedY;
|
|
47
|
+
private _maxScroll;
|
|
48
|
+
private _rafId;
|
|
49
|
+
private _enabled;
|
|
50
|
+
private _lineHeight;
|
|
51
|
+
private _friction;
|
|
52
|
+
private _touchPrevY;
|
|
53
|
+
private _touchPrevTime;
|
|
54
|
+
/** 直近の touch velocity (px/ms)。touchend 後はこの値を初速に慣性が走る。 */
|
|
55
|
+
private _velocityY;
|
|
56
|
+
private _isTouching;
|
|
57
|
+
/** tick() の dt 計算用。0 のとき初回 tick (dt は FRAME_MS で初期化)。 */
|
|
58
|
+
private _lastTickTime;
|
|
59
|
+
private _eventAbort;
|
|
60
|
+
private _resizeObserver;
|
|
61
|
+
private _destroyed;
|
|
62
|
+
constructor(options?: RafScrollOptions);
|
|
63
|
+
private calcMaxScroll;
|
|
64
|
+
private setupEventListeners;
|
|
65
|
+
/**
|
|
66
|
+
* document の scrollHeight が動的に変わる場合 (画像 lazy load、SPA でのコンテンツ追加 等)
|
|
67
|
+
* に maxScroll を追従させる。ResizeObserver で `<html>` を観察する。
|
|
68
|
+
*/
|
|
69
|
+
private setupResizeObserver;
|
|
70
|
+
private onWheel;
|
|
71
|
+
private onTouchStart;
|
|
72
|
+
private onTouchMove;
|
|
73
|
+
private onTouchEnd;
|
|
74
|
+
private onResize;
|
|
75
|
+
private clamp;
|
|
76
|
+
/**
|
|
77
|
+
* 毎 rAF で `window.scrollTo` を呼ぶ。これが Lenis 方式の核心:
|
|
78
|
+
* scrollY の更新タイミングを「JS rAF tick 上だけ」に集約することで、
|
|
79
|
+
* paint 時に見える scrollY と JS が知る scrollY が完全一致する。
|
|
80
|
+
*
|
|
81
|
+
* 同値時は no-op で呼ばない (scroll event の連発で page 側 listener を
|
|
82
|
+
* 叩くのを避ける)。
|
|
83
|
+
*
|
|
84
|
+
* touch リリース後の慣性: `_velocityY` が閾値以上ある間、毎 tick で
|
|
85
|
+
* `scrollY += velocity * dt` を積みつつ `velocity *= friction^(dt/FRAME_MS)` で減衰。
|
|
86
|
+
*/
|
|
87
|
+
private tick;
|
|
88
|
+
/** 現在の virtual scrollY。 */
|
|
89
|
+
get scrollY(): number;
|
|
90
|
+
/**
|
|
91
|
+
* 入力受付の有効/無効。
|
|
92
|
+
*
|
|
93
|
+
* disable 中は wheel/touch ハンドラが **preventDefault する前に** early-return するため、
|
|
94
|
+
* native スクロールが復活する(virtual scroll を一時停止して通常スクロールに戻したい
|
|
95
|
+
* ケース用)。accumulator (`_scrollY`) も更新されず、慣性も止まる。
|
|
96
|
+
*
|
|
97
|
+
* 再 enable 時は `_scrollY` / `_lastAppliedY` を現在の `window.scrollY` に再同期して
|
|
98
|
+
* disable 中の native scroll とのズレを吸収する(同期しないと次の wheel/touch 入力で
|
|
99
|
+
* 古い `_scrollY` からの delta になり巨大ジャンプする)。
|
|
100
|
+
*/
|
|
101
|
+
set enabled(value: boolean);
|
|
102
|
+
get enabled(): boolean;
|
|
103
|
+
destroy(): void;
|
|
104
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScrollSync — WebGL スクロール同期。
|
|
3
|
+
*
|
|
4
|
+
* container 要素を `position: absolute; top: 0; left: 0` で document に貼り、毎 rAF で
|
|
5
|
+
* 1 回読んだ `scrollX/Y` を transform に流す。**同じ scrollX/Y を DomPlane の sceneY 計算
|
|
6
|
+
* にも渡してもらう**ことで、paint 時に scroll が進んで rAF↔paint Δ が出ても container と
|
|
7
|
+
* plane が一緒にズレる → 視覚的に DOM ↔ mesh は完璧に一致する。
|
|
8
|
+
*
|
|
9
|
+
* **scrollHeight 非侵襲**:
|
|
10
|
+
* 単純に `height = viewport * (1 + 2*padding)` を貼ると、container の layout box が
|
|
11
|
+
* `1 + 2*padding` 倍の高さを占有し、body の scrollHeight を底上げしてしまう
|
|
12
|
+
* (コンテンツが短いページや、スクロール末尾で「実コンテンツを越えて余白までスクロール」
|
|
13
|
+
* できる現象)。
|
|
14
|
+
*
|
|
15
|
+
* 対策として **毎フレ effective padding を `body.scrollHeight - scrollY - viewport`
|
|
16
|
+
* でクランプ**する(`#updateCanvasSize` と同じ formula)。
|
|
17
|
+
* - 長いページ + 上の方にいる: requested = effective(フル padding)
|
|
18
|
+
* - 末尾に近づく: max が縮み effective も縮む → container も縮む
|
|
19
|
+
* - 末尾ぴったり: effective = 0 → container = viewport ぴったり
|
|
20
|
+
*
|
|
21
|
+
* canvas drawing buffer も同期して resize する必要があるため、resize 通知 callback を
|
|
22
|
+
* `setResizeCallback()` で受け取る。Core 側でこの callback に `renderer.setSize` /
|
|
23
|
+
* `camera.resize` / `postEffect.resize` を繋ぐ。
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
export interface ScrollSyncOptions {
|
|
27
|
+
/**
|
|
28
|
+
* 上下パディング比率(0〜1)。canvas の高さを viewport * (1 + padding * 2) にし、
|
|
29
|
+
* rAF↔paint 間の scroll 進行で canvas が viewport から欠ける現象を吸収する。
|
|
30
|
+
* 末尾に近づくと effective 値は自動でクランプされ、body.scrollHeight を底上げしない。
|
|
31
|
+
* @default 0
|
|
32
|
+
*/
|
|
33
|
+
padding?: number;
|
|
34
|
+
/**
|
|
35
|
+
* スクロール強度(速度)を strength getter で提供するか。
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
trackStrength?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* strength の減衰速度。
|
|
41
|
+
* @default 10
|
|
42
|
+
*/
|
|
43
|
+
strengthDecay?: number;
|
|
44
|
+
}
|
|
45
|
+
export declare class ScrollSync {
|
|
46
|
+
private container;
|
|
47
|
+
private _padding;
|
|
48
|
+
private _trackStrength;
|
|
49
|
+
private _strengthDecay;
|
|
50
|
+
private _strength;
|
|
51
|
+
private _prevScrollY;
|
|
52
|
+
private _prevTime;
|
|
53
|
+
private _viewportWidth;
|
|
54
|
+
private _viewportHeight;
|
|
55
|
+
private _enabled;
|
|
56
|
+
/**
|
|
57
|
+
* container の **元 CSS 指定** を window に対する比率として保持する。
|
|
58
|
+
*
|
|
59
|
+
* 例: container の CSS が `width: 100vw; height: 110vh` なら
|
|
60
|
+
* _widthRatio = 1.0, _heightRatio = 1.1
|
|
61
|
+
*
|
|
62
|
+
* `applyContainerStyles()` で container は `position: absolute; width/height = JS制御`
|
|
63
|
+
* に書き換えられて以降「元の CSS による intrinsic size」は測れなくなる。よって
|
|
64
|
+
* **constructor 進入時** に getBoundingClientRect から ratio を snapshot し、以降の
|
|
65
|
+
* resize ではこの ratio を window 寸法に掛けて canvas 物理サイズを決める。
|
|
66
|
+
*
|
|
67
|
+
* これにより `#canvas { width: 100vw; height: 110vh; }` のような CSS が
|
|
68
|
+
* window resize / orientation 変更後も「100vw × 110vh」相当を保ち続ける。
|
|
69
|
+
*/
|
|
70
|
+
private _widthRatio;
|
|
71
|
+
private _heightRatio;
|
|
72
|
+
/**
|
|
73
|
+
* 現在 transform / canvas 寸法に反映している padding 値(px)。
|
|
74
|
+
* クランプの出力。`_padding * viewportHeight` の上限と、document の余り
|
|
75
|
+
* 領域の最小から決まる。
|
|
76
|
+
*/
|
|
77
|
+
private _effectivePaddingPx;
|
|
78
|
+
/** 計算用の canvas rect(scroll transform を含まない論理 rect、effective padding 基準) */
|
|
79
|
+
private _logicalRect;
|
|
80
|
+
/**
|
|
81
|
+
* effective padding が変わって canvas drawing buffer の resize が必要な時に呼ぶ callback。
|
|
82
|
+
* 引数は `{ width, height }`(drawing buffer の新サイズ = container の新サイズ)。
|
|
83
|
+
* Core 側でこれに `renderer.setSize` / `camera.resize` / `postEffect.resize` をぶら下げる。
|
|
84
|
+
*/
|
|
85
|
+
private _onResize;
|
|
86
|
+
/**
|
|
87
|
+
* destroy 時の復元用に、constructor 進入時の inline style を退避しておく。
|
|
88
|
+
* ユーザーが先に `container.style.position = 'relative'` などを当てていた場合に、
|
|
89
|
+
* destroy で「空文字に潰す」ではなく元の値に戻すために必要。
|
|
90
|
+
*/
|
|
91
|
+
private _originalStyles;
|
|
92
|
+
constructor(container: HTMLElement, options?: ScrollSyncOptions);
|
|
93
|
+
private applyContainerStyles;
|
|
94
|
+
/**
|
|
95
|
+
* ビューポートサイズが変わった時に呼ぶ。effective padding は requested で初期化し、
|
|
96
|
+
* 次の update() 呼び出しでクランプが効く。
|
|
97
|
+
*
|
|
98
|
+
* 引数を省略した場合は、constructor で snapshot した container の CSS ratio を
|
|
99
|
+
* window 寸法に掛けて自動算出する。明示的に値を渡せば override 可能(scrollbar を
|
|
100
|
+
* 差し引いた幅にしたい等のケース)。
|
|
101
|
+
*/
|
|
102
|
+
updateSize(wrapperWidth?: number, wrapperHeight?: number): void;
|
|
103
|
+
/**
|
|
104
|
+
* 毎 rAF で呼ぶ。**plane の sceneY 計算と同一の scrollX/Y を渡すこと**。
|
|
105
|
+
* これが cancel の不変条件。
|
|
106
|
+
*
|
|
107
|
+
* padding clamp:
|
|
108
|
+
* maxPaddingY = body.scrollHeight - scrollY - viewportHeight
|
|
109
|
+
* effective = clamp(requested, 0, maxPaddingY)
|
|
110
|
+
* effective が変わったら canvas drawing buffer も同期して resize する。
|
|
111
|
+
*/
|
|
112
|
+
update(scrollX: number, scrollY: number): void;
|
|
113
|
+
private applyTransform;
|
|
114
|
+
/** スクロール速度ベースの strength 値を更新。 */
|
|
115
|
+
private updateStrength;
|
|
116
|
+
/**
|
|
117
|
+
* canvas drawing buffer の resize が必要な時に呼ばれる callback を登録する。
|
|
118
|
+
* @internal Core 側で renderer.setSize / camera.resize / postEffect.resize を繋ぐ。
|
|
119
|
+
*/
|
|
120
|
+
setResizeCallback(cb: (size: {
|
|
121
|
+
width: number;
|
|
122
|
+
height: number;
|
|
123
|
+
}) => void): void;
|
|
124
|
+
/**
|
|
125
|
+
* DomPositionCalculator 用の **論理** rect。
|
|
126
|
+
*
|
|
127
|
+
* **注意**: これは「ブラウザ上での canvas の getBoundingClientRect」ではない。
|
|
128
|
+
* `top = -effectivePadding`, `height = viewport + 2 * effectivePadding` で、
|
|
129
|
+
* scroll に追従して動く container 内のローカル座標系を表す。effective padding は
|
|
130
|
+
* 末尾近くで自動的に縮む。
|
|
131
|
+
*/
|
|
132
|
+
get logicalRect(): DOMRect;
|
|
133
|
+
/** 現在の effective padding(px)。テスト・デバッグ用。 */
|
|
134
|
+
get effectivePadding(): number;
|
|
135
|
+
/**
|
|
136
|
+
* 現在のスクロール速度(0〜1 にクランプ)。
|
|
137
|
+
*
|
|
138
|
+
* **`trackStrength: false` で構築している場合は常に 0 を返す**。値を使いたい場合は
|
|
139
|
+
* options で trackStrength を true にして構築すること。DEV では誤用防止のため
|
|
140
|
+
* 1 度だけ console.warn を出す。
|
|
141
|
+
*/
|
|
142
|
+
get strength(): number;
|
|
143
|
+
private _warnedStrength;
|
|
144
|
+
get padding(): number;
|
|
145
|
+
/**
|
|
146
|
+
* 入力受付の有効/無効。
|
|
147
|
+
*
|
|
148
|
+
* disable 中は `update()` が no-op になり container の transform は触らない(disable 直前の
|
|
149
|
+
* 位置で固定される)。enable に戻したフレから transform 更新が再開する。
|
|
150
|
+
*
|
|
151
|
+
* disable で「container を素の状態に戻したい」場合は `destroy()` を呼ぶこと。
|
|
152
|
+
*/
|
|
153
|
+
set enabled(value: boolean);
|
|
154
|
+
get enabled(): boolean;
|
|
155
|
+
destroy(): void;
|
|
156
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const CAMERA_FOV = 60;
|
|
2
|
+
export declare const CAMERA_NEAR = 0.1;
|
|
3
|
+
/**
|
|
4
|
+
* Camera の far plane。座標系は「DOM 1px = WebGL 1unit」。
|
|
5
|
+
* カメラ距離は `viewport高 / 2 / tan(fov/2)` で ~519px (viewport=600px) 前後になるため
|
|
6
|
+
* far=10000 はかなり余裕がある(カメラ距離の ~20 倍)。
|
|
7
|
+
*
|
|
8
|
+
* **注意**: near=0.1 / far=10000 で精度レンジが 5 桁開いており、z 軸を奥行きに
|
|
9
|
+
* 積極的に使うと z-fighting / shadow map の精度劣化が出やすい。
|
|
10
|
+
* 奥行き表現を多用する場合は near/far をユースケースに合わせて絞ること。
|
|
11
|
+
*/
|
|
12
|
+
export declare const CAMERA_FAR = 10000;
|
|
13
|
+
export declare const DEFAULT_SCALE = 1;
|
|
14
|
+
export declare const DEFAULT_OFFSET: {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
z: number;
|
|
18
|
+
};
|
|
19
|
+
export declare const AMBIENT_LIGHT_COLOR = 16777215;
|
|
20
|
+
export declare const AMBIENT_LIGHT_INTENSITY = 0.5;
|
|
21
|
+
export declare const DIRECTIONAL_LIGHT_COLOR = 16777215;
|
|
22
|
+
export declare const DIRECTIONAL_LIGHT_INTENSITY = 1;
|
|
23
|
+
export declare const DIRECTIONAL_LIGHT_POSITION: {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
z: number;
|
|
27
|
+
};
|