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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 izumitetsuya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# domSyncGL
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/dom-sync-gl)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
DOM 要素にロックした Three.js plane / 3D オブジェクト、スクロール同期、チェーン可能なポストエフェクト、touch 慣性付きの virtual scroll を、Three.js を peer dependency にした単一パッケージにまとめたもの。
|
|
7
|
+
|
|
8
|
+
> npm パッケージ名は **`dom-sync-gl`** (kebab-case)、display 名は **`domSyncGL`** (camelCase)。
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **DOM-locked rendering** — 任意の DOM 要素の bbox に追従する `DomPlane` / `Dom3DObject`。CSS で動かしてもピクセル単位で追従。
|
|
13
|
+
- **rAF↔paint scroll sync** — `ScrollSync` + `RafScroll` で paint 時にも desync しない。
|
|
14
|
+
- **Touch inertia** — `RafScroll` がモバイル touch に対して native と同等の慣性スクロールを自前で再現 (`touchFriction` で減衰率カスタマイズ可)。
|
|
15
|
+
- **Composable post effects** — `BaseEffect` を継承して `fragmentShader` を返すだけで ping-pong チェーンに自動配線。`enabled` で個別に on/off。
|
|
16
|
+
- **iOS Safari friendly** — `100lvh` / `visualViewport.resize` 対応で動的アドレスバーに canvas 高が引きずられない。
|
|
17
|
+
- **Mobile tier-ing pattern** — `maxPixelRatio` で renderer のピクセル密度を絞れる。fbm オクターブ等の shader 軽量化はアプリ側で template literal 分岐 (example 参照)。
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install dom-sync-gl three
|
|
23
|
+
# 任意 (GUI panel / FPS panel を使う時だけ)
|
|
24
|
+
npm install lil-gui stats.js
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`three` のみ必須 peer。`lil-gui` / `stats.js` は **`peerDependenciesMeta` で optional 宣言** + 内部で **dynamic import** なので、`showGUI: false` / `showStats: false`(または未指定)のときは追加 install 不要、bundle にも入りません。
|
|
28
|
+
|
|
29
|
+
`showGUI: true` で effect を addEffect すると、初回だけ lil-gui を async load してから `setupGUI()` を呼ぶ流れになります (load 中に effect を複数 addEffect しても load promise を共有して 1 インスタンスにまとまる)。
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
最低限のセットアップ:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { WebGLApp } from "dom-sync-gl";
|
|
37
|
+
|
|
38
|
+
const app = new WebGLApp("#canvas", {
|
|
39
|
+
scrollSync: true,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// DOM 要素にロックした plane
|
|
43
|
+
app.createPlane(".hero-card", {
|
|
44
|
+
fragmentShader: /* glsl */ `
|
|
45
|
+
precision highp float;
|
|
46
|
+
varying vec2 vUv;
|
|
47
|
+
uniform float uTime;
|
|
48
|
+
void main() {
|
|
49
|
+
gl_FragColor = vec4(vUv, 0.5 + 0.5 * sin(uTime), 1.0);
|
|
50
|
+
}
|
|
51
|
+
`,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// element=null で全画面 plane (背景レイヤ)
|
|
55
|
+
app.createPlane(null, {
|
|
56
|
+
fragmentShader: /* ... */,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
HTML 側に canvas slot を置く:
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<div id="canvas" style="position: absolute; top: 0; left: 0; width: 100vw; height: 100svh;"></div>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`ScrollSync` が container の元 CSS rect を snapshot して以降の resize でも比率を保つので、`height: 100svh` (アドレスバー込みの小さい viewport) のような指定がそのまま尊重される。
|
|
67
|
+
|
|
68
|
+
## Concepts
|
|
69
|
+
|
|
70
|
+
### 1. DOM-locked plane
|
|
71
|
+
|
|
72
|
+
`createPlane(selector)` で渡した DOM 要素の `getBoundingClientRect()` を毎フレーム読み、対応する Three.js mesh の world 位置とスケールを更新する。**transform で動く要素** (CSS animation, GSAP 等) は `updateRectEveryFrame: true` を指定すると毎フレ取り直す。
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const plane = app.createPlane(".card", {
|
|
76
|
+
fragmentShader,
|
|
77
|
+
updateRectEveryFrame: true,
|
|
78
|
+
uniforms: {
|
|
79
|
+
// 組み込み uniform (uTime / uResolution / uMouseUV / uIsHovered) は
|
|
80
|
+
// 宣言不要で shader 側に届く。ここに書くのはユーザー定義 uniform のみ。
|
|
81
|
+
uTexture: { value: texture },
|
|
82
|
+
uIntensity: { value: 0.5 },
|
|
83
|
+
},
|
|
84
|
+
onInView: (p) => p.material.uniforms.uIntensity.value = 1,
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
組み込み uniform (shader 側で `uniform xxx` と書くだけで使える、宣言・更新不要):
|
|
89
|
+
- `uTime` (`float`, 秒) — `clock.getElapsedTime()` を毎フレ書き込み
|
|
90
|
+
- `uResolution` (`vec2`) — plane の pixel 寸法 (resize / rect 更新時に追従)
|
|
91
|
+
- `uMouseUV` (`vec2`, 0..1) — hover 中の plane-local UV 座標
|
|
92
|
+
- `uIsHovered` (`bool`) — raycast hit 中か
|
|
93
|
+
|
|
94
|
+
### 2. Scroll sync
|
|
95
|
+
|
|
96
|
+
`scrollSync: true` を有効にすると、canvas container が `position: absolute; top:0; left:0` に置き換わり、毎 rAF で 1 回読んだ `window.scrollY` を:
|
|
97
|
+
- container 自身の `transform: translate3d(0, scrollY, 0)`
|
|
98
|
+
- 各 `DomPlane` の sceneY 計算
|
|
99
|
+
|
|
100
|
+
の両方に **同じ値で配る**。paint と JS rAF の間で scrollY が変わっても、container も plane も同じ「古い値」で揃ってズレるので、視覚上は DOM ↔ mesh が完璧に一致する。
|
|
101
|
+
|
|
102
|
+
`RafScroll` を併用すると `window.scrollY` の更新自体が rAF tick 上だけになり、native scroll の rAF↔paint Δ も消える:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { RafScroll } from "dom-sync-gl";
|
|
106
|
+
|
|
107
|
+
const rafScroll = new RafScroll({
|
|
108
|
+
touchFriction: 0.95, // touch リリース後の慣性減衰 (0 = 慣性なし)
|
|
109
|
+
});
|
|
110
|
+
// rafScroll は constructor で wheel/touch を listen 開始する
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 3. Post effects
|
|
114
|
+
|
|
115
|
+
`BaseEffect` を継承し `getConfig()` で fragment shader を返すだけ:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { BaseEffect, type BaseEffectConfig } from "dom-sync-gl";
|
|
119
|
+
|
|
120
|
+
class GrainEffect extends BaseEffect {
|
|
121
|
+
protected getConfig(): BaseEffectConfig {
|
|
122
|
+
return {
|
|
123
|
+
fragmentShader: /* glsl */ `
|
|
124
|
+
precision highp float;
|
|
125
|
+
uniform sampler2D tDiffuse;
|
|
126
|
+
uniform float uTime;
|
|
127
|
+
varying vec2 vUv;
|
|
128
|
+
void main() {
|
|
129
|
+
vec4 src = texture2D(tDiffuse, vUv);
|
|
130
|
+
float g = fract(sin(dot(vUv + uTime, vec2(12.9898, 78.233))) * 43758.5453);
|
|
131
|
+
gl_FragColor = vec4(src.rgb + (g - 0.5) * 0.06, src.a);
|
|
132
|
+
}
|
|
133
|
+
`,
|
|
134
|
+
uniforms: { uTime: { value: 0 } },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
update(time: number) { this.setUniform("uTime", time); }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
app.addEffect(new GrainEffect());
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`app.addEffect()` でフルスクリーンチェーンに追加、`plane.addEffect()` で per-plane に追加できる (同じインスタンスは片方だけ)。
|
|
144
|
+
|
|
145
|
+
`setupGUI(gui)` を実装すると `showGUI: true` のとき lil-gui パネルに自動で controls が出る。
|
|
146
|
+
|
|
147
|
+
## API reference
|
|
148
|
+
|
|
149
|
+
### `new WebGLApp(selector, options?)`
|
|
150
|
+
|
|
151
|
+
| option | type | default | 説明 |
|
|
152
|
+
|---|---|---|---|
|
|
153
|
+
| `scrollSync` | `boolean \| ScrollSyncOptions` | `false` | `true` ON、object でパラメータ指定 |
|
|
154
|
+
| `enableMouseTracking` | `boolean` | `true` | mousemove で uMouseUV / raycast を更新 |
|
|
155
|
+
| `maxPixelRatio` | `number` | `2` | `renderer.setPixelRatio` の上限 (モバイルは `1.5` 推奨) |
|
|
156
|
+
| `outputColorSpace` | `THREE.ColorSpace` | `SRGBColorSpace` | renderer の出力色空間 |
|
|
157
|
+
| `showStats` | `boolean` | `false` | stats.js FPS パネル表示 |
|
|
158
|
+
| `statsParent` | `HTMLElement` | `document.body` | パネル append 先 |
|
|
159
|
+
| `showGUI` | `boolean` | `true` | effect の `setupGUI()` を自動呼び出し |
|
|
160
|
+
| `guiTitle` | `string` | `'Effects'` | lil-gui ルートタイトル |
|
|
161
|
+
|
|
162
|
+
#### Main methods
|
|
163
|
+
|
|
164
|
+
- `createPlane(selector, options?)` → `DomPlane`。`selector` が `null` だと全画面背景 plane。
|
|
165
|
+
- `create3DObject(selector, options)` → `Dom3DObject` (GLTF を DOM bbox に fit)。
|
|
166
|
+
- `addEffect(effect)` / `removeEffect(effect)` — フルスクリーンチェーン
|
|
167
|
+
- `addObject(obj3d)` / `removeObject(obj3d)` — Three.js scene に直接追加
|
|
168
|
+
- `addUpdateCallback(fn)` → unsubscribe 関数。rAF tick ごとに呼ばれる。
|
|
169
|
+
- `addResizeCallback(fn)` → unsubscribe 関数
|
|
170
|
+
- `setMouseTrackingEnabled(bool)` — 動的に on/off
|
|
171
|
+
- `getScene() / getCamera() / getRenderer() / getMouse()`
|
|
172
|
+
- `destroy()` — 全 listener / RT / scene を解放
|
|
173
|
+
|
|
174
|
+
### `ScrollSyncOptions`
|
|
175
|
+
|
|
176
|
+
| option | type | default | 説明 |
|
|
177
|
+
|---|---|---|---|
|
|
178
|
+
| `padding` | `number` | `0` | 上下パディング比率 (canvas 高 = viewport × (1 + 2·padding))。`0` で CSS rect そのまま |
|
|
179
|
+
| `trackStrength` | `boolean` | `false` | スクロール強度 getter を有効化 |
|
|
180
|
+
| `strengthDecay` | `number` | `10` | strength の指数減衰係数 |
|
|
181
|
+
|
|
182
|
+
### `RafScrollOptions`
|
|
183
|
+
|
|
184
|
+
| option | type | default | 説明 |
|
|
185
|
+
|---|---|---|---|
|
|
186
|
+
| `lineHeight` | `number` | `16` | `WheelEvent.deltaMode=LINE` 時の 1 行 px |
|
|
187
|
+
| `touchFriction` | `number` | `0.95` | touch リリース後の慣性減衰率 (16.67ms 換算 1 フレあたりの velocity 乗算値、`0` で慣性無効) |
|
|
188
|
+
|
|
189
|
+
### `CreatePlaneOptions`
|
|
190
|
+
|
|
191
|
+
| option | type | default | 説明 |
|
|
192
|
+
|---|---|---|---|
|
|
193
|
+
| `vertexShader` / `fragmentShader` | `string` | (default passthrough) | shader ソース |
|
|
194
|
+
| `uniforms` | `{ [key]: IUniform }` | `{}` | 追加 uniform。組み込み (`uTime` 等) と衝突しないよう |
|
|
195
|
+
| `updateRectEveryFrame` | `boolean` | `false` | 毎フレ bbox 取り直し (CSS animation で動く要素用) |
|
|
196
|
+
| `segments` | `number` | `1` | PlaneGeometry セグメント数 (頂点 displacement する場合のみ増やす) |
|
|
197
|
+
| `onInView` / `onOutView` | `(plane) => void` | — | IntersectionObserver コールバック |
|
|
198
|
+
| `inViewRootMargin` | `string` | `'100%'` | IO の rootMargin |
|
|
199
|
+
| `inViewRepeat` | `boolean` | `false` | 出入りのたびに onInView を発火 |
|
|
200
|
+
| `crossOrigin` | `string` | `'anonymous'` | `data-texture` 読み込み時の CORS 属性 |
|
|
201
|
+
|
|
202
|
+
### Exports
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// Core
|
|
206
|
+
export { WebGLApp, Camera, Light, DomPlane, Dom3DObject };
|
|
207
|
+
|
|
208
|
+
// Scroll
|
|
209
|
+
export { ScrollSync, RafScroll };
|
|
210
|
+
|
|
211
|
+
// Post effects
|
|
212
|
+
export { EffectComposer, EffectPass, PlaneComposer, BaseEffect };
|
|
213
|
+
|
|
214
|
+
// Extension base
|
|
215
|
+
export { BaseScene };
|
|
216
|
+
|
|
217
|
+
// Utility
|
|
218
|
+
export { DomPositionCalculator };
|
|
219
|
+
|
|
220
|
+
// Types
|
|
221
|
+
export type {
|
|
222
|
+
WebGLAppOptions,
|
|
223
|
+
CreatePlaneOptions,
|
|
224
|
+
Create3DObjectOptions,
|
|
225
|
+
Dom3DObjectFitMode,
|
|
226
|
+
Offset3D,
|
|
227
|
+
DOMPositionInfo,
|
|
228
|
+
ScrollSyncOptions,
|
|
229
|
+
RafScrollOptions,
|
|
230
|
+
BaseEffectConfig,
|
|
231
|
+
EffectOptions,
|
|
232
|
+
EffectTarget,
|
|
233
|
+
EffectLike,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Re-export THREE (利用側で `import * as THREE from "three"` する代わりに使える)
|
|
237
|
+
export { THREE };
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Browser support
|
|
241
|
+
|
|
242
|
+
- Modern evergreen (Chrome / Edge / Firefox / Safari の各最新 2 バージョン)
|
|
243
|
+
- **iOS Safari 15.4+** (`100lvh` / `visualViewport` を使う)
|
|
244
|
+
- WebGL2 は不要 (WebGL1 で動く)
|
|
245
|
+
- IE11 等のレガシーブラウザは対象外
|
|
246
|
+
|
|
247
|
+
## Bundle
|
|
248
|
+
|
|
249
|
+
| 形式 | ファイル | minified | gzipped |
|
|
250
|
+
|---|---|---:|---:|
|
|
251
|
+
| ESM | `dist/index.js` | 73.1 kB | **20.2 kB** |
|
|
252
|
+
| CJS | `dist/index.cjs` | 39.4 kB | **10.1 kB** |
|
|
253
|
+
|
|
254
|
+
- 型定義: `dist/index.d.ts` + 各ファイル `*.d.ts` をそのまま投影 (`rollupTypes: false`)
|
|
255
|
+
- `sideEffects: false` で tree-shaking 可
|
|
256
|
+
- `three` / `lil-gui` / `stats.js` はバンドルに含まれない (peer + dynamic import)
|
|
257
|
+
- `three` 本体 (~600 kB) は利用側で持ち込み
|
|
258
|
+
|
|
259
|
+
## Develop
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npm install
|
|
263
|
+
npm run dev # examples/basic/ を vite で立ち上げる
|
|
264
|
+
npm run build # dist/ にビルド
|
|
265
|
+
npm run test # vitest 実行
|
|
266
|
+
npm run typecheck
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`examples/` は npm publish 物には含まれない (`files: ["dist"]`)。
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
MIT
|
package/dist/Camera.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
export declare class Camera {
|
|
3
|
+
rect: DOMRect;
|
|
4
|
+
/**
|
|
5
|
+
* 内部 THREE.PerspectiveCamera。外部から `lookAt` 等の操作は可能。
|
|
6
|
+
*
|
|
7
|
+
* **注意**: `position.z` は「DOM 1px = WebGL 1unit」を成立させるための値で、
|
|
8
|
+
* `resize()` のたびに `rect.height / 2 / tan(fov/2)` で**上書きされる**。
|
|
9
|
+
* 外から position.z を書き換えても resize で消えるので、ズームや視点調整を
|
|
10
|
+
* 永続化したい場合は別途 update ループで再設定するか、座標系が変わってもよい
|
|
11
|
+
* 設計にすること。
|
|
12
|
+
*/
|
|
13
|
+
instance: THREE.PerspectiveCamera;
|
|
14
|
+
constructor(rect: DOMRect);
|
|
15
|
+
resize(rect: DOMRect): void;
|
|
16
|
+
}
|
package/dist/Core.d.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
2
|
+
import { default as GUI } from 'lil-gui';
|
|
3
|
+
import { Camera } from './Camera';
|
|
4
|
+
import { Light } from './Light';
|
|
5
|
+
import { DomPlane } from './DomPlane';
|
|
6
|
+
import { Dom3DObject } from './Dom3DObject';
|
|
7
|
+
import { ScrollSync } from './ScrollSync';
|
|
8
|
+
import { EffectLike } from './EffectComposer';
|
|
9
|
+
import { BaseEffect } from './effects/BaseEffect';
|
|
10
|
+
import { CreatePlaneOptions, Create3DObjectOptions, WebGLAppOptions } from './types';
|
|
11
|
+
import * as THREE from 'three';
|
|
12
|
+
export declare class WebGLApp {
|
|
13
|
+
container: HTMLElement;
|
|
14
|
+
canvas: HTMLCanvasElement;
|
|
15
|
+
scene: THREE.Scene;
|
|
16
|
+
renderer: THREE.WebGLRenderer;
|
|
17
|
+
camera: Camera;
|
|
18
|
+
light: Light;
|
|
19
|
+
controls: OrbitControls | null;
|
|
20
|
+
updateCallbacks: (() => void)[];
|
|
21
|
+
resizeCallbacks: (() => void)[];
|
|
22
|
+
rect: DOMRect;
|
|
23
|
+
domPlanes: DomPlane[];
|
|
24
|
+
dom3DObjects: Dom3DObject[];
|
|
25
|
+
clock: THREE.Clock;
|
|
26
|
+
scrollSync: ScrollSync | null;
|
|
27
|
+
private options;
|
|
28
|
+
private rafId;
|
|
29
|
+
private resizeTimer;
|
|
30
|
+
private eventAbort;
|
|
31
|
+
/**
|
|
32
|
+
* mousemove listener 専用の AbortController。`setMouseTrackingEnabled()` で動的に
|
|
33
|
+
* detach 可能にするため `eventAbort` とは別に持つ。null の時は attach 済みでない。
|
|
34
|
+
*/
|
|
35
|
+
private _mouseAbort;
|
|
36
|
+
/**
|
|
37
|
+
* canvas の viewport 上の矩形をキャッシュ。mousemove ごとに `getBoundingClientRect`
|
|
38
|
+
* を呼ぶと layout 強制が走るため、resize 時 + (ScrollSync 無効時の) scroll 時に
|
|
39
|
+
* invalidate する形にしてフィールドアクセスで済むようにする。
|
|
40
|
+
* ScrollSync 有効時は canvas viewport rect が固定 (RafScroll が同 rAF tick で
|
|
41
|
+
* scrollTo するため paint 時 actual と JS rAF が一致 → 動かない) なので
|
|
42
|
+
* scroll 時の invalidate は不要。
|
|
43
|
+
*/
|
|
44
|
+
private _canvasRect;
|
|
45
|
+
private mouse;
|
|
46
|
+
private prevMouse;
|
|
47
|
+
/**
|
|
48
|
+
* マウスが canvas 矩形の内側に居るか。mousemove イベントごとに更新し、
|
|
49
|
+
* rAF tick 内の raycaster はこのフラグで実行を決める。
|
|
50
|
+
* (uMouseUV の更新を完全に rAF 駆動にすることで paint と同期させ、
|
|
51
|
+
* 「mousemove 非同期発火による uniform のちらつき」を消す)
|
|
52
|
+
*/
|
|
53
|
+
private _mouseInside;
|
|
54
|
+
/** raycaster で使う NDC バッファ (毎フレ allocate を避ける) */
|
|
55
|
+
private _ndcBuf;
|
|
56
|
+
/** getMouseDelta の戻り値スクラッチ (毎フレ clone を避ける) */
|
|
57
|
+
private _mouseDeltaBuf;
|
|
58
|
+
private raycaster;
|
|
59
|
+
private hoveredPlane;
|
|
60
|
+
private domPlaneMeshes;
|
|
61
|
+
private postEffect;
|
|
62
|
+
private internalComposer;
|
|
63
|
+
private effects;
|
|
64
|
+
private stats;
|
|
65
|
+
/**
|
|
66
|
+
* lil-gui の root インスタンス。
|
|
67
|
+
* showGUI が false なら null のまま。最初の `setupGUI` を持つ effect が
|
|
68
|
+
* `addEffect()` 経由で登録された瞬間に生成する(lazy + dynamic import)。
|
|
69
|
+
*/
|
|
70
|
+
private gui;
|
|
71
|
+
/**
|
|
72
|
+
* lil-gui の dynamic import promise。複数 effect の addEffect が同時に来た時、
|
|
73
|
+
* 二重 load を防ぐ。一度 resolve したら次回以降は `this.gui` を直接返す。
|
|
74
|
+
*/
|
|
75
|
+
private _guiLoadPromise;
|
|
76
|
+
/**
|
|
77
|
+
* destroy 済みフラグ。animate() 実行中に user callback から destroy() が
|
|
78
|
+
* 呼ばれると、renderer.dispose() 後の続きで renderer.render() を呼んで
|
|
79
|
+
* WebGL エラーになる。各段階の冒頭で見て早期 return する。
|
|
80
|
+
*/
|
|
81
|
+
private destroyed;
|
|
82
|
+
constructor(selector: string | HTMLElement, options?: WebGLAppOptions);
|
|
83
|
+
private init;
|
|
84
|
+
getScene(): THREE.Scene<THREE.Object3DEventMap>;
|
|
85
|
+
getCamera(): Camera;
|
|
86
|
+
getRenderer(): THREE.WebGLRenderer;
|
|
87
|
+
getLight(): Light;
|
|
88
|
+
getViewPort(): DOMRect;
|
|
89
|
+
getMouse(): THREE.Vector2;
|
|
90
|
+
getPrevMouse(): THREE.Vector2;
|
|
91
|
+
getMouseDelta(): THREE.Vector2;
|
|
92
|
+
getScrollSync(): ScrollSync | null;
|
|
93
|
+
addObject(object: THREE.Object3D): void;
|
|
94
|
+
removeObject(object: THREE.Object3D): void;
|
|
95
|
+
createPlane(selector: string | HTMLElement | null, options?: CreatePlaneOptions): DomPlane;
|
|
96
|
+
removePlane(domPlane: DomPlane): void;
|
|
97
|
+
create3DObject(selector: string | HTMLElement | null, options: Create3DObjectOptions): Dom3DObject;
|
|
98
|
+
remove3DObject(dom3DObject: Dom3DObject): void;
|
|
99
|
+
addUpdateCallback(callback: () => void): () => void;
|
|
100
|
+
addResizeCallback(callback: () => void): () => void;
|
|
101
|
+
enableOrbitControls(): OrbitControls;
|
|
102
|
+
getControls(): OrbitControls | null;
|
|
103
|
+
/**
|
|
104
|
+
* canvas 全体にエフェクトを追加する。
|
|
105
|
+
* 内部で EffectComposer を自動生成するため、別途 setPostEffect は不要。
|
|
106
|
+
*
|
|
107
|
+
* **注意**: `setPostEffect()` でカスタム postEffect を入れている状態でこれを呼ぶと、
|
|
108
|
+
* 自動生成された EffectComposer で上書きされる(カスタム postEffect の dispose は
|
|
109
|
+
* 呼ばれない=呼び出し側の責務)。両 API の併用は避けること。
|
|
110
|
+
*/
|
|
111
|
+
addEffect<T extends BaseEffect>(effect: T): T;
|
|
112
|
+
/**
|
|
113
|
+
* lil-gui を dynamic import で読み込み、root インスタンスを lazy 生成して返す。
|
|
114
|
+
* 同時に複数 effect から呼ばれても load promise を共有して 1 インスタンスにまとめる。
|
|
115
|
+
* @internal DomPlane.addEffect / WebGLApp.addEffect から呼ばれる。
|
|
116
|
+
*/
|
|
117
|
+
_ensureGUIAsync(): Promise<GUI>;
|
|
118
|
+
/**
|
|
119
|
+
* root の lil-gui インスタンスを取得 (sync)。
|
|
120
|
+
*
|
|
121
|
+
* **注意**: lil-gui は dynamic import で読み込むため、初回 effect 登録直後など
|
|
122
|
+
* load 中の段階では `null` を返す。確実にインスタンスを得たい場合は
|
|
123
|
+
* `getGUIAsync()` を使う。`showGUI: false` の場合は常に `null`。
|
|
124
|
+
*/
|
|
125
|
+
getGUI(): GUI | null;
|
|
126
|
+
/**
|
|
127
|
+
* lil-gui を必要に応じて load し、インスタンスを返す。
|
|
128
|
+
* `showGUI: false` の場合は `null` を resolve する。
|
|
129
|
+
*/
|
|
130
|
+
getGUIAsync(): Promise<GUI | null>;
|
|
131
|
+
/**
|
|
132
|
+
* ポストエフェクトを設定(低レベル API)。
|
|
133
|
+
* 自前で `EffectLike`(render/resize/dispose)を実装したオブジェクトを差し込みたい場合のみ使用。
|
|
134
|
+
* 通常は `addEffect()` を使うこと。
|
|
135
|
+
*
|
|
136
|
+
* **注意**: `addEffect()` で追加済みのエフェクトがある状態で呼ぶと、
|
|
137
|
+
* 自動 EffectComposer を捨てて引数の postEffect に差し替える。既存 effect の
|
|
138
|
+
* dispose は呼ばれない(必要なら先に `clearEffects()` を呼ぶこと)。
|
|
139
|
+
*/
|
|
140
|
+
setPostEffect(postEffect: EffectLike): void;
|
|
141
|
+
/**
|
|
142
|
+
* `addEffect()` で登録した effect を 1 つ取り除く。
|
|
143
|
+
*
|
|
144
|
+
* - 内部 EffectComposer から該当 pass を外し、material を dispose する
|
|
145
|
+
* - effect 自体の `dispose()` も呼ぶ(FluidEffect 等の RT も解放)
|
|
146
|
+
* - 全 effect が空になった場合、internalComposer はそのまま残す(次の addEffect で再利用)
|
|
147
|
+
*
|
|
148
|
+
* 登録されていない effect を渡した時は `false` を返して何もしない。
|
|
149
|
+
*/
|
|
150
|
+
removeEffect(effect: BaseEffect): boolean;
|
|
151
|
+
/**
|
|
152
|
+
* 登録されたエフェクトとポストエフェクトをすべて解除して破棄する。
|
|
153
|
+
* addEffect で追加した全 effect の dispose() を呼び、内部 EffectComposer も解放する。
|
|
154
|
+
*/
|
|
155
|
+
clearEffects(): void;
|
|
156
|
+
/**
|
|
157
|
+
* @deprecated `clearEffects()` を使ってください。挙動は同一です。
|
|
158
|
+
*/
|
|
159
|
+
removePostEffect(): void;
|
|
160
|
+
private setupEventListeners;
|
|
161
|
+
/**
|
|
162
|
+
* mousemove tracking の動的な ON/OFF。
|
|
163
|
+
* - true: 未 attach なら mousemove listener を追加する
|
|
164
|
+
* - false: attach 済みなら detach する(hover も解除)
|
|
165
|
+
*
|
|
166
|
+
* `destroy()` 時は eventAbort と一緒に自動 detach される(_mouseAbort 個別 abort も呼ぶ)。
|
|
167
|
+
*/
|
|
168
|
+
setMouseTrackingEnabled(enabled: boolean): void;
|
|
169
|
+
private invalidateCanvasRect;
|
|
170
|
+
/**
|
|
171
|
+
* ScrollSync の動的 padding clamp で canvas 寸法が変わった時に呼ばれる。
|
|
172
|
+
*
|
|
173
|
+
* window resize(onResize)と違い、DOM の位置や element 側の bbox は変わっていない。
|
|
174
|
+
* 必要なのは「canvas drawing buffer のサイズに依存するもの」だけ:
|
|
175
|
+
* - renderer.setSize(drawing buffer + canvas.style.{width,height})
|
|
176
|
+
* - camera の aspect / distance(rect.height に応じて変わる)
|
|
177
|
+
* - postEffect の RT サイズ
|
|
178
|
+
* - 各 effect の RT サイズ
|
|
179
|
+
*
|
|
180
|
+
* DomPlane の DOM 連動 plane は positionCalculator の bbox に依存していて canvas 高さに
|
|
181
|
+
* 依存しないので触らない(calculateWebGLPosition の数式が padding を相殺するため不変)。
|
|
182
|
+
* フルスクリーン plane(element=null)は canvasRect.width/height をスケールに使うので、
|
|
183
|
+
* その分だけ canvasRect 参照を更新する。
|
|
184
|
+
*/
|
|
185
|
+
private _onScrollSyncResize;
|
|
186
|
+
/** mousemove 等で頻繁に必要な canvas viewport rect を遅延 + キャッシュで返す。 */
|
|
187
|
+
private getCanvasRect;
|
|
188
|
+
private onResize;
|
|
189
|
+
/**
|
|
190
|
+
* mousemove は **mouse 座標と canvas 内外フラグだけ** 更新する。
|
|
191
|
+
* raycaster / setHoverInfo は呼ばない (= uMouseUV を直接書かない)。
|
|
192
|
+
* uniform 更新は `_updateHoverFromMouse` 経由で rAF tick 内に集約することで、
|
|
193
|
+
* paint と完全同期させ「mousemove 非同期発火による uMouseUV のちらつき」を防ぐ。
|
|
194
|
+
*/
|
|
195
|
+
private onMouseMove;
|
|
196
|
+
/**
|
|
197
|
+
* rAF tick 内の Phase A で呼ばれる。最新の `this.mouse` を使って raycaster を投げ、
|
|
198
|
+
* hover 中の plane を判定 + `setHoverInfo` (= uMouseUV / uIsHovered の更新) する。
|
|
199
|
+
* mousemove イベントから切り離すことで全 plane の uniform が 1 tick = 1 確定値で
|
|
200
|
+
* 揃い、paint と同期する。
|
|
201
|
+
*/
|
|
202
|
+
private _updateHoverFromMouse;
|
|
203
|
+
destroy(): void;
|
|
204
|
+
private animate;
|
|
205
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
2
|
+
import { Create3DObjectOptions } from './types';
|
|
3
|
+
import * as THREE from "three";
|
|
4
|
+
/**
|
|
5
|
+
* DOM 要素の位置・サイズに合わせて GLTF モデルを配置するクラス。
|
|
6
|
+
*
|
|
7
|
+
* **element の扱い**:
|
|
8
|
+
* - `HTMLElement` 指定: DOM 要素位置に追従。bbox 正規化と DOM サイズに対する scale
|
|
9
|
+
* フィットを行う。
|
|
10
|
+
* - `null` 指定: scene 原点に固定(モデル本来のサイズ × userScale)。
|
|
11
|
+
* ScrollSync 無効時はそのまま viewport 中心に固定される。
|
|
12
|
+
* ScrollSync 有効時でも Lenis 等で JS rAF と paint の scrollY が同一に揃っていれば
|
|
13
|
+
* container が viewport に厳密固定され、scene 原点固定 obj も viewport 上で
|
|
14
|
+
* ズレなく見える。
|
|
15
|
+
*
|
|
16
|
+
* **エフェクトについて**: `DomPlane` と異なり `addEffect()` API は持たない。
|
|
17
|
+
* 任意 3D モデル単体に局所ポストエフェクトを当てるのは Plane と違って
|
|
18
|
+
* 1) 任意視点からの再投影が必要、2) 透視テクスチャ再貼り付けでクオリティが落ちる
|
|
19
|
+
* といった問題があり、汎用化が難しいため意図的に提供していない。
|
|
20
|
+
*
|
|
21
|
+
* モデル全体に効くエフェクトを掛けたい場合は `WebGLApp.addEffect()` で
|
|
22
|
+
* 画面全体のポストエフェクトとして適用する。
|
|
23
|
+
* モデル個別にエフェクトが必要な場合は `getModel()` で `THREE.Group` を取り出し、
|
|
24
|
+
* カスタムマテリアル/シェーダーで対応すること。
|
|
25
|
+
*
|
|
26
|
+
* **IntersectionObserver について**: `DomPlane` と違い `onInView` / `onOutView` /
|
|
27
|
+
* `inViewRepeat` / `inViewRootMargin` は提供していない。Dom3DObject は GLTF を
|
|
28
|
+
* scene に置く性質上「画面外で非表示にして無駄な描画を避ける」用途に絞られ、
|
|
29
|
+
* 進行状況通知やリセット系のコールバックは Plane より使い所が薄いため。
|
|
30
|
+
* 必要なら利用側で `getModel()` を取り出して自前で IntersectionObserver を組む。
|
|
31
|
+
* element=null の場合は IntersectionObserver は作らない(常に表示)。
|
|
32
|
+
*/
|
|
33
|
+
export declare class Dom3DObject {
|
|
34
|
+
element: HTMLElement | null;
|
|
35
|
+
model: THREE.Group | null;
|
|
36
|
+
loader: GLTFLoader;
|
|
37
|
+
mainScene: THREE.Scene;
|
|
38
|
+
canvasRect: DOMRect;
|
|
39
|
+
options: Create3DObjectOptions;
|
|
40
|
+
isVisible: boolean;
|
|
41
|
+
private positionCalculator;
|
|
42
|
+
private updateRectEveryFrame;
|
|
43
|
+
private observer;
|
|
44
|
+
private destroyed;
|
|
45
|
+
constructor(element: HTMLElement | null, mainScene: THREE.Scene, canvasRect: DOMRect, options: Create3DObjectOptions);
|
|
46
|
+
/**
|
|
47
|
+
* Phase B-read (DOM read): getBoundingClientRect のみ。
|
|
48
|
+
* Core.animate で paint 直前にまとめて呼ばれる。
|
|
49
|
+
* element=null の場合は何もしない(scene 原点固定で DOM 追従不要)。
|
|
50
|
+
*
|
|
51
|
+
* **更新条件**: `updateRectEveryFrame: true` のときのみ毎フレ更新。
|
|
52
|
+
* 自身の CSS animation で動的に位置が変わるケースのみ true に。
|
|
53
|
+
* (旧仕様では isFixed で自動毎フレ更新していたが、多くの場合不要な layout 強制
|
|
54
|
+
* になっていたため明示フラグ駆動に変更)
|
|
55
|
+
* @internal Core.animate から呼ばれる。
|
|
56
|
+
*/
|
|
57
|
+
_tickRead(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Phase B-apply: model.position の書き込み。
|
|
60
|
+
* DOM あり/なしどちらでも `setPosition` を呼ぶ。setPosition 内部で positionCalculator の
|
|
61
|
+
* null を扱うため、DOM なしは offset のみ反映される(後から options.offset を書き換えても
|
|
62
|
+
* 毎フレ反映される一貫性を担保)。
|
|
63
|
+
* @internal Core.animate から呼ばれる。
|
|
64
|
+
*/
|
|
65
|
+
_tickApply(scrollX: number, scrollY: number): void;
|
|
66
|
+
private loadModel;
|
|
67
|
+
private setupModel;
|
|
68
|
+
private applyScale;
|
|
69
|
+
private setPosition;
|
|
70
|
+
setCanvasRect(canvasRect: DOMRect): void;
|
|
71
|
+
resize(): void;
|
|
72
|
+
getModel(): THREE.Group<THREE.Object3DEventMap> | null;
|
|
73
|
+
destroy(): void;
|
|
74
|
+
}
|