@mirage-engine/core 0.2.0 → 0.2.2
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 +16 -0
- package/dist/mirage-engine.js +1016 -418
- package/dist/mirage-engine.umd.js +136 -72
- package/dist/src/animation/Animator.d.ts +11 -0
- package/dist/src/core/Engine.d.ts +2 -0
- package/dist/src/core/Syncer.d.ts +8 -1
- package/dist/src/renderer/Renderer.d.ts +9 -4
- package/dist/src/store/MeshRegistry.d.ts +9 -0
- package/dist/src/store/TextureLifecycleManager.d.ts +16 -0
- package/dist/src/types/animate.d.ts +19 -0
- package/dist/src/types/common.d.ts +7 -1
- package/dist/src/types/config.d.ts +4 -0
- package/dist/src/types/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/animation/Animator.ts +163 -0
- package/src/core/Engine.ts +56 -4
- package/src/core/Syncer.ts +71 -16
- package/src/dom/Extractor.ts +176 -21
- package/src/env.d.ts +4 -0
- package/src/renderer/Renderer.ts +206 -80
- package/src/store/MeshRegistry.ts +32 -0
- package/src/store/TextureLifecycleManager.ts +111 -0
- package/src/types/animate.ts +26 -0
- package/src/types/common.ts +4 -1
- package/src/types/config.ts +4 -0
- package/src/types/index.ts +2 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { StyleData } from "../types";
|
|
2
|
+
import { MeshRegistry } from "../store/MeshRegistry";
|
|
3
|
+
import { Painter, parseColor } from "@mirage-engine/painter";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
|
|
6
|
+
export function animateMeshByData(
|
|
7
|
+
registry: MeshRegistry,
|
|
8
|
+
data: Map<HTMLElement, StyleData>,
|
|
9
|
+
) {
|
|
10
|
+
if (data.size === 0) return;
|
|
11
|
+
|
|
12
|
+
data.forEach((styleData, element) => {
|
|
13
|
+
const mesh = registry.get(element);
|
|
14
|
+
if (!mesh || !mesh.userData.basePosition) return;
|
|
15
|
+
|
|
16
|
+
let { x: baseX, y: baseY } = mesh.userData.basePosition;
|
|
17
|
+
const { width: baseW, height: baseH } = mesh.userData.baseSize;
|
|
18
|
+
|
|
19
|
+
const currentW = styleData.width ?? baseW;
|
|
20
|
+
const currentH = styleData.height ?? baseH;
|
|
21
|
+
const widthDiff = currentW - baseW;
|
|
22
|
+
const heightDiff = currentH - baseH;
|
|
23
|
+
|
|
24
|
+
// --- Capture and Lock Transform Origin (Performance & Accuracy) ---
|
|
25
|
+
// We capture the origin on the very first frame of change (> 0.1px)
|
|
26
|
+
// to ensure perfect sync from the start of the first animation.
|
|
27
|
+
if (
|
|
28
|
+
styleData.width !== undefined &&
|
|
29
|
+
mesh.userData.originRatioX === undefined &&
|
|
30
|
+
Math.abs(widthDiff) > 0.1
|
|
31
|
+
) {
|
|
32
|
+
const rect = element.getBoundingClientRect();
|
|
33
|
+
const tx = styleData.x ?? 0;
|
|
34
|
+
const baseTx = mesh.userData.baseTransform?.x ?? 0;
|
|
35
|
+
const scrollOffsetX = mesh.userData.isFixed ? 0 : window.scrollX;
|
|
36
|
+
// rect.left already includes GSAP transform (tx).
|
|
37
|
+
// We subtract tx to strip the animation, then add baseTx to match baseDOM's initial state.
|
|
38
|
+
const currentPageX = rect.left + scrollOffsetX - tx + baseTx;
|
|
39
|
+
const deltaX = currentPageX - mesh.userData.baseDOM.x;
|
|
40
|
+
|
|
41
|
+
let ratioX = -deltaX / widthDiff;
|
|
42
|
+
if (Math.abs(ratioX) < 0.05) ratioX = 0.0;
|
|
43
|
+
else if (Math.abs(ratioX - 1.0) < 0.05) ratioX = 1.0;
|
|
44
|
+
else if (Math.abs(ratioX - 0.5) < 0.05) ratioX = 0.5;
|
|
45
|
+
|
|
46
|
+
mesh.userData.originRatioX = ratioX;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
styleData.height !== undefined &&
|
|
51
|
+
mesh.userData.originRatioY === undefined &&
|
|
52
|
+
Math.abs(heightDiff) > 0.1
|
|
53
|
+
) {
|
|
54
|
+
const rect = element.getBoundingClientRect();
|
|
55
|
+
const ty = styleData.y ?? 0;
|
|
56
|
+
const baseTy = mesh.userData.baseTransform?.y ?? 0;
|
|
57
|
+
const scrollOffsetY = mesh.userData.isFixed ? 0 : window.scrollY;
|
|
58
|
+
const currentPageY = rect.top + scrollOffsetY - ty + baseTy;
|
|
59
|
+
const deltaY = currentPageY - mesh.userData.baseDOM.y;
|
|
60
|
+
|
|
61
|
+
let ratioY = -deltaY / heightDiff;
|
|
62
|
+
if (Math.abs(ratioY) < 0.05) ratioY = 0.0;
|
|
63
|
+
else if (Math.abs(ratioY - 1.0) < 0.05) ratioY = 1.0;
|
|
64
|
+
else if (Math.abs(ratioY - 0.5) < 0.05) ratioY = 0.5;
|
|
65
|
+
|
|
66
|
+
mesh.userData.originRatioY = ratioY;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// --- Calculate Mesh Position using Measured Origin ---
|
|
70
|
+
const ratioX = mesh.userData.originRatioX ?? 0.5;
|
|
71
|
+
const ratioY = mesh.userData.originRatioY ?? 0.5;
|
|
72
|
+
|
|
73
|
+
// Since Three.js meshes expand from center, we offset the center based on the locked origin ratio
|
|
74
|
+
const moveX = widthDiff * (0.5 - ratioX);
|
|
75
|
+
const moveY = heightDiff * (0.5 - ratioY);
|
|
76
|
+
|
|
77
|
+
// --- Calculate Delta Movement ---
|
|
78
|
+
// Extract baseTransform stored during syncScene
|
|
79
|
+
const baseTx = mesh.userData.baseTransform?.x ?? 0;
|
|
80
|
+
const baseTy = mesh.userData.baseTransform?.y ?? 0;
|
|
81
|
+
|
|
82
|
+
// We only want to apply the DIFFERENCE in transform since the base extraction.
|
|
83
|
+
const deltaTx = (styleData.x ?? baseTx) - baseTx;
|
|
84
|
+
const deltaTy = (styleData.y ?? baseTy) - baseTy;
|
|
85
|
+
|
|
86
|
+
mesh.position.setX(baseX + moveX + deltaTx);
|
|
87
|
+
mesh.position.setY(baseY - (moveY + deltaTy));
|
|
88
|
+
mesh.scale.set(currentW, currentH, 1);
|
|
89
|
+
|
|
90
|
+
Painter.forceUpdateUniforms(mesh.material as THREE.ShaderMaterial, {
|
|
91
|
+
backgroundColor: styleData.backgroundColor,
|
|
92
|
+
backgroundImage: styleData.backgroundImage,
|
|
93
|
+
opacity: styleData.opacity,
|
|
94
|
+
borderRadius: styleData.borderRadius ?? mesh.userData.baseStyles?.borderRadius,
|
|
95
|
+
width: currentW,
|
|
96
|
+
height: currentH,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export function animateMeshByAttribute(
|
|
101
|
+
_target: HTMLElement,
|
|
102
|
+
_options: { duration: number; easing?: string },
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
export function updateFixedMeshesScroll(
|
|
106
|
+
fixedMeshes: Set<THREE.Mesh>,
|
|
107
|
+
currentScrollX: number,
|
|
108
|
+
currentScrollY: number,
|
|
109
|
+
) {
|
|
110
|
+
fixedMeshes.forEach((mesh) => {
|
|
111
|
+
if (mesh.userData.isFixed && mesh.userData.initialScroll && mesh.userData.originalBasePosition) {
|
|
112
|
+
const offsetX = currentScrollX - mesh.userData.initialScroll.x;
|
|
113
|
+
const offsetY = currentScrollY - mesh.userData.initialScroll.y;
|
|
114
|
+
|
|
115
|
+
mesh.userData.basePosition.x = mesh.userData.originalBasePosition.x + offsetX;
|
|
116
|
+
mesh.userData.basePosition.y = mesh.userData.originalBasePosition.y - offsetY;
|
|
117
|
+
|
|
118
|
+
mesh.position.x = mesh.userData.basePosition.x;
|
|
119
|
+
mesh.position.y = mesh.userData.basePosition.y;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function extractFromStyle(style: CSSStyleDeclaration): StyleData {
|
|
125
|
+
const styleObject: StyleData = {};
|
|
126
|
+
|
|
127
|
+
if (style.opacity) {
|
|
128
|
+
styleObject.opacity = parseFloat(style.opacity);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (style.backgroundColor && style.backgroundColor !== "rgba(0, 0, 0, 0)") {
|
|
132
|
+
const parsed = parseColor(style.backgroundColor);
|
|
133
|
+
styleObject.backgroundColor = [parsed.color.r, parsed.color.g, parsed.color.b];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (style.backgroundImage) {
|
|
137
|
+
styleObject.backgroundImage = style.backgroundImage;
|
|
138
|
+
} else if (style.background) {
|
|
139
|
+
styleObject.backgroundImage = style.background;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (style.borderRadius) {
|
|
143
|
+
styleObject.borderRadius = parseFloat(style.borderRadius);
|
|
144
|
+
}
|
|
145
|
+
if (style.width) {
|
|
146
|
+
styleObject.width = parseFloat(style.width);
|
|
147
|
+
}
|
|
148
|
+
if (style.height) {
|
|
149
|
+
styleObject.height = parseFloat(style.height);
|
|
150
|
+
}
|
|
151
|
+
if (style.transform && style.transform !== "none") {
|
|
152
|
+
const matrix = new DOMMatrix(style.transform);
|
|
153
|
+
|
|
154
|
+
styleObject.x = matrix.m41;
|
|
155
|
+
styleObject.y = matrix.m42;
|
|
156
|
+
styleObject.z = matrix.m43;
|
|
157
|
+
|
|
158
|
+
// [TODO] direct matrix data pipeline to webGL
|
|
159
|
+
// styleObject.matrix = matrix.toFloat32Array();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return styleObject;
|
|
163
|
+
}
|
package/src/core/Engine.ts
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import { CoreConfig } from "../types";
|
|
2
2
|
import { Renderer } from "../renderer/Renderer";
|
|
3
3
|
import { Syncer } from "./Syncer";
|
|
4
|
+
import { MeshRegistry } from "../store/MeshRegistry";
|
|
4
5
|
|
|
5
6
|
export class Engine {
|
|
6
7
|
private renderer: Renderer;
|
|
7
8
|
private syncer: Syncer;
|
|
8
9
|
private target: HTMLElement;
|
|
10
|
+
private registry: MeshRegistry;
|
|
9
11
|
|
|
10
12
|
constructor(target: HTMLElement, config: CoreConfig) {
|
|
11
13
|
this.target = target;
|
|
14
|
+
this.registry = new MeshRegistry();
|
|
15
|
+
|
|
16
|
+
if (!document.getElementById("mirage-engine-styles")) {
|
|
17
|
+
const style = document.createElement("style");
|
|
18
|
+
style.id = "mirage-engine-styles";
|
|
19
|
+
style.textContent = `
|
|
20
|
+
[data-mirage-dom="hide"] {
|
|
21
|
+
opacity: 0 !important;
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
document.head.appendChild(style);
|
|
25
|
+
}
|
|
12
26
|
|
|
13
27
|
let mountContainer: HTMLElement | undefined;
|
|
14
28
|
|
|
@@ -23,11 +37,14 @@ export class Engine {
|
|
|
23
37
|
throw new Error("[Mirage] Cannot find a container (parent or option).");
|
|
24
38
|
}
|
|
25
39
|
|
|
26
|
-
this.renderer = new Renderer(
|
|
27
|
-
|
|
40
|
+
this.renderer = new Renderer(
|
|
41
|
+
this.target,
|
|
42
|
+
config,
|
|
43
|
+
mountContainer,
|
|
44
|
+
this.registry,
|
|
45
|
+
);
|
|
28
46
|
this.renderer.mount();
|
|
29
|
-
|
|
30
|
-
this.syncer = new Syncer(this.target, this.renderer, config);
|
|
47
|
+
this.syncer = new Syncer(this.target, this.renderer, this.registry, config);
|
|
31
48
|
}
|
|
32
49
|
|
|
33
50
|
public start() {
|
|
@@ -41,4 +58,39 @@ export class Engine {
|
|
|
41
58
|
this.syncer.stop();
|
|
42
59
|
this.renderer.dispose();
|
|
43
60
|
}
|
|
61
|
+
|
|
62
|
+
public test() {
|
|
63
|
+
const boxMesh = this.registry.get(
|
|
64
|
+
document.querySelector("#box2") as HTMLElement,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (!boxMesh) return;
|
|
68
|
+
|
|
69
|
+
const keys: { [key: string]: boolean } = {
|
|
70
|
+
ArrowRight: false,
|
|
71
|
+
ArrowLeft: false,
|
|
72
|
+
ArrowUp: false,
|
|
73
|
+
ArrowDown: false,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
window.addEventListener("keydown", (e) => {
|
|
77
|
+
if (keys[e.key] !== undefined) keys[e.key] = true;
|
|
78
|
+
});
|
|
79
|
+
window.addEventListener("keyup", (e) => {
|
|
80
|
+
if (keys[e.key] !== undefined) keys[e.key] = false;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const moveStep = 2;
|
|
84
|
+
|
|
85
|
+
const animate = () => {
|
|
86
|
+
requestAnimationFrame(animate);
|
|
87
|
+
|
|
88
|
+
if (keys.ArrowRight) boxMesh.position.setX(boxMesh.position.x + moveStep);
|
|
89
|
+
if (keys.ArrowLeft) boxMesh.position.setX(boxMesh.position.x - moveStep);
|
|
90
|
+
if (keys.ArrowUp) boxMesh.position.setY(boxMesh.position.y + moveStep);
|
|
91
|
+
if (keys.ArrowDown) boxMesh.position.setY(boxMesh.position.y - moveStep);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
animate();
|
|
95
|
+
}
|
|
44
96
|
}
|
package/src/core/Syncer.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { CoreConfig } from "../types/config";
|
|
2
2
|
import { Renderer } from "../renderer/Renderer";
|
|
3
|
+
import { MeshRegistry } from "../store/MeshRegistry";
|
|
3
4
|
import { extractSceneGraph } from "../dom/Extractor";
|
|
4
5
|
import {
|
|
5
6
|
DIRTY_NONE,
|
|
6
7
|
DIRTY_RECT,
|
|
7
8
|
DIRTY_STRUCTURE,
|
|
9
|
+
DIRTY_CONTENT
|
|
10
|
+
,
|
|
8
11
|
DIRTY_STYLE,
|
|
9
12
|
USER_LAYER,
|
|
10
13
|
SYSTEM_LAYER,
|
|
11
14
|
Visibility,
|
|
15
|
+
StyleData,
|
|
12
16
|
} from "../types";
|
|
17
|
+
import { extractFromStyle, animateMeshByData, updateFixedMeshesScroll } from "../animation/Animator";
|
|
13
18
|
|
|
14
19
|
interface InternalResizeConfig {
|
|
15
20
|
enabled: boolean;
|
|
@@ -21,9 +26,12 @@ interface InternalResizeConfig {
|
|
|
21
26
|
export class Syncer {
|
|
22
27
|
private target: HTMLElement;
|
|
23
28
|
private renderer: Renderer;
|
|
29
|
+
private registry: MeshRegistry;
|
|
24
30
|
private filter: CoreConfig["filter"];
|
|
25
31
|
|
|
26
32
|
private observer: MutationObserver;
|
|
33
|
+
private pendingDeletions: Set<HTMLElement> = new Set();
|
|
34
|
+
private pendingStyles: Map<HTMLElement, StyleData> = new Map();
|
|
27
35
|
|
|
28
36
|
private isDomDirty: boolean = false;
|
|
29
37
|
private isRunning: boolean = false;
|
|
@@ -38,9 +46,19 @@ export class Syncer {
|
|
|
38
46
|
private resizeTimer: number | null = null;
|
|
39
47
|
private isResizing: boolean = false;
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
private lastScrollX: number = 0;
|
|
50
|
+
private lastScrollY: number = 0;
|
|
51
|
+
private scrollTimer: number | null = null;
|
|
52
|
+
|
|
53
|
+
constructor(
|
|
54
|
+
target: HTMLElement,
|
|
55
|
+
renderer: Renderer,
|
|
56
|
+
registry: MeshRegistry,
|
|
57
|
+
config: CoreConfig,
|
|
58
|
+
) {
|
|
42
59
|
this.target = target;
|
|
43
60
|
this.renderer = renderer;
|
|
61
|
+
this.registry = registry;
|
|
44
62
|
this.filter = config.filter;
|
|
45
63
|
|
|
46
64
|
// Resize Debounce Configuration
|
|
@@ -64,13 +82,28 @@ export class Syncer {
|
|
|
64
82
|
for (const mutation of mutations) {
|
|
65
83
|
if (mutation.type === "childList") {
|
|
66
84
|
currentMask |= DIRTY_STRUCTURE;
|
|
85
|
+
if (mutation.removedNodes.length > 0) {
|
|
86
|
+
mutation.removedNodes.forEach((node) => {
|
|
87
|
+
if (node instanceof HTMLElement) {
|
|
88
|
+
this.pendingDeletions.add(node);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
67
92
|
} else if (mutation.type === "attributes") {
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
mutation.
|
|
71
|
-
|
|
93
|
+
if (mutation.attributeName === "style") {
|
|
94
|
+
currentMask |= DIRTY_RECT | DIRTY_STYLE;
|
|
95
|
+
const target = mutation.target as HTMLElement;
|
|
96
|
+
|
|
97
|
+
const extractedStyleData = extractFromStyle(target.style);
|
|
98
|
+
// console.log(extractedStyleData);
|
|
99
|
+
this.pendingStyles.set(target, extractedStyleData);
|
|
100
|
+
} else if (mutation.attributeName === "class") {
|
|
72
101
|
currentMask |= DIRTY_RECT | DIRTY_STYLE;
|
|
73
102
|
}
|
|
103
|
+
// else if (mutation.attributeName === "data-mirage") {
|
|
104
|
+
// }
|
|
105
|
+
} else if (mutation.type === "characterData") {
|
|
106
|
+
currentMask |= DIRTY_CONTENT | DIRTY_RECT;
|
|
74
107
|
}
|
|
75
108
|
}
|
|
76
109
|
|
|
@@ -80,7 +113,6 @@ export class Syncer {
|
|
|
80
113
|
// Structural Change detected
|
|
81
114
|
if (currentMask & DIRTY_STRUCTURE) {
|
|
82
115
|
this.clearTimers();
|
|
83
|
-
console.log("Structural Change detected");
|
|
84
116
|
this.isDomDirty = true;
|
|
85
117
|
return;
|
|
86
118
|
}
|
|
@@ -109,7 +141,6 @@ export class Syncer {
|
|
|
109
141
|
|
|
110
142
|
this.target.addEventListener("transitionend", this.onTransitionFinished);
|
|
111
143
|
this.target.addEventListener("animationend", this.onTransitionFinished);
|
|
112
|
-
|
|
113
144
|
window.addEventListener("resize", this.onWindowResize);
|
|
114
145
|
|
|
115
146
|
this.forceUpdateScene();
|
|
@@ -138,17 +169,18 @@ export class Syncer {
|
|
|
138
169
|
clearTimeout(this.cssTimer);
|
|
139
170
|
this.cssTimer = null;
|
|
140
171
|
}
|
|
172
|
+
if (this.scrollTimer) {
|
|
173
|
+
clearTimeout(this.scrollTimer);
|
|
174
|
+
this.scrollTimer = null;
|
|
175
|
+
}
|
|
141
176
|
}
|
|
142
177
|
|
|
143
178
|
private onTransitionFinished = (e: Event) => {
|
|
144
179
|
if (!this.target.contains(e.target as Node)) return;
|
|
145
|
-
|
|
146
180
|
if (this.mutationTimer !== null) return;
|
|
147
|
-
|
|
148
181
|
if (this.cssTimer) clearTimeout(this.cssTimer);
|
|
149
|
-
|
|
182
|
+
if (this.pendingStyles.size != 0) return;
|
|
150
183
|
this.pendingMask |= DIRTY_RECT | DIRTY_STYLE;
|
|
151
|
-
|
|
152
184
|
this.cssTimer = window.setTimeout(() => {
|
|
153
185
|
this.isDomDirty = true;
|
|
154
186
|
this.cssTimer = null;
|
|
@@ -160,17 +192,13 @@ export class Syncer {
|
|
|
160
192
|
this.isDomDirty = true;
|
|
161
193
|
return;
|
|
162
194
|
}
|
|
163
|
-
|
|
164
195
|
if (!this.isResizing) {
|
|
165
196
|
this.isResizing = true;
|
|
166
197
|
if (this.resizeConfig.onStart) this.resizeConfig.onStart();
|
|
167
198
|
}
|
|
168
|
-
|
|
169
199
|
if (this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
170
|
-
|
|
171
200
|
this.resizeTimer = window.setTimeout(() => {
|
|
172
201
|
this.isDomDirty = true;
|
|
173
|
-
|
|
174
202
|
if (this.resizeConfig.onEnd) this.resizeConfig.onEnd();
|
|
175
203
|
this.isResizing = false;
|
|
176
204
|
this.resizeTimer = null;
|
|
@@ -179,6 +207,9 @@ export class Syncer {
|
|
|
179
207
|
|
|
180
208
|
private forceUpdateScene() {
|
|
181
209
|
this.isDomDirty = false;
|
|
210
|
+
|
|
211
|
+
this.lastScrollX = window.scrollX;
|
|
212
|
+
this.lastScrollY = window.scrollY;
|
|
182
213
|
|
|
183
214
|
const discoveredTraveler =
|
|
184
215
|
document.querySelector("[data-mirage-travel='traveler']") !== null;
|
|
@@ -197,7 +228,8 @@ export class Syncer {
|
|
|
197
228
|
);
|
|
198
229
|
|
|
199
230
|
if (sceneGraph) {
|
|
200
|
-
this.renderer.syncScene(sceneGraph);
|
|
231
|
+
this.renderer.syncScene(sceneGraph, this.pendingDeletions);
|
|
232
|
+
this.pendingDeletions.clear();
|
|
201
233
|
}
|
|
202
234
|
|
|
203
235
|
this.pendingMask = DIRTY_NONE;
|
|
@@ -210,7 +242,30 @@ export class Syncer {
|
|
|
210
242
|
this.forceUpdateScene();
|
|
211
243
|
}
|
|
212
244
|
|
|
245
|
+
const currentScrollX = window.scrollX;
|
|
246
|
+
const currentScrollY = window.scrollY;
|
|
247
|
+
if (currentScrollX !== this.lastScrollX || currentScrollY !== this.lastScrollY) {
|
|
248
|
+
updateFixedMeshesScroll(this.renderer.fixedMeshes, currentScrollX, currentScrollY);
|
|
249
|
+
|
|
250
|
+
this.lastScrollX = currentScrollX;
|
|
251
|
+
this.lastScrollY = currentScrollY;
|
|
252
|
+
|
|
253
|
+
if (this.scrollTimer) clearTimeout(this.scrollTimer);
|
|
254
|
+
this.scrollTimer = window.setTimeout(() => {
|
|
255
|
+
this.pendingMask |= DIRTY_RECT;
|
|
256
|
+
this.isDomDirty = true;
|
|
257
|
+
this.scrollTimer = null;
|
|
258
|
+
}, 150);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (this.pendingStyles.size > 0) {
|
|
262
|
+
animateMeshByData(this.registry, this.pendingStyles);
|
|
263
|
+
this.pendingStyles.clear();
|
|
264
|
+
}
|
|
265
|
+
|
|
213
266
|
this.renderer.render();
|
|
214
267
|
requestAnimationFrame(this.renderLoop);
|
|
215
268
|
};
|
|
269
|
+
|
|
270
|
+
// [ThinkPoint] change call back pattern when after
|
|
216
271
|
}
|