mirage-engine 0.2.3 → 0.2.4
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/README.md +7 -1
- package/dist/mirage-engine.js +165 -93
- package/dist/mirage-engine.umd.js +1 -1
- package/dist/src/renderer/TextTextureGenerator.d.ts +3 -0
- package/dist/src/types/index.d.ts +18 -1
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> **An engine that mirrors HTML DOM elements to a WebGL scene in real-time.**
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/mirage-engine)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
|
|
5
9
|
MirageEngine directly mirrors HTML DOM elements to WebGL objects. It observes DOM mutations and synchronizes position, style, and content in real-time, allowing standard HTML elements to exist within a WebGL context.
|
|
6
10
|
|
|
7
11
|
## Installation
|
|
@@ -17,4 +21,6 @@ import { Mirage } from 'mirage-engine';
|
|
|
17
21
|
|
|
18
22
|
const engine = new Mirage("#target");
|
|
19
23
|
engine.start();
|
|
20
|
-
```
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**License | MIT © dltldn333**
|
package/dist/mirage-engine.js
CHANGED
|
@@ -1,26 +1,39 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
import * as
|
|
5
|
-
const
|
|
1
|
+
var b = Object.defineProperty;
|
|
2
|
+
var D = (i, e, t) => e in i ? b(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
|
|
3
|
+
var o = (i, e, t) => (D(i, typeof e != "symbol" ? e + "" : e, t), t);
|
|
4
|
+
import * as c from "three";
|
|
5
|
+
const g = 0, T = 1, y = 2, C = 4, w = 8, x = 16;
|
|
6
|
+
function N(i, e, t, s) {
|
|
7
|
+
const n = document.createElement("canvas"), r = n.getContext("2d");
|
|
8
|
+
if (!r)
|
|
9
|
+
throw new Error("[Mirage] Failed to create canvas context");
|
|
10
|
+
const a = window.devicePixelRatio || 2;
|
|
11
|
+
n.width = t * a, n.height = s * a, r.scale(a, a), r.font = e.font, r.fillStyle = e.color, r.textAlign = e.textAlign, r.direction = e.direction, r.textBaseline = "middle";
|
|
12
|
+
let l = 0;
|
|
13
|
+
e.textAlign === "center" && (l = t / 2), e.textAlign === "right" && (l = t);
|
|
14
|
+
const m = s / 2;
|
|
15
|
+
r.fillText(i, l, m);
|
|
16
|
+
const h = new c.CanvasTexture(n);
|
|
17
|
+
return h.colorSpace = c.SRGBColorSpace, h.minFilter = c.LinearFilter, h.needsUpdate = !0, h;
|
|
18
|
+
}
|
|
6
19
|
class R {
|
|
7
20
|
constructor() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
this.canvas = document.createElement("canvas"), this.scene = new
|
|
21
|
+
o(this, "canvas");
|
|
22
|
+
o(this, "scene");
|
|
23
|
+
o(this, "camera");
|
|
24
|
+
o(this, "renderer");
|
|
25
|
+
o(this, "renderOrder", 0);
|
|
26
|
+
o(this, "meshMap", /* @__PURE__ */ new Map());
|
|
27
|
+
this.canvas = document.createElement("canvas"), this.scene = new c.Scene();
|
|
15
28
|
const e = window.innerWidth, t = window.innerHeight;
|
|
16
|
-
this.camera = new
|
|
29
|
+
this.camera = new c.OrthographicCamera(
|
|
17
30
|
e / -2,
|
|
18
31
|
e / 2,
|
|
19
32
|
t / 2,
|
|
20
33
|
t / -2,
|
|
21
34
|
1,
|
|
22
35
|
1e3
|
|
23
|
-
), this.camera.position.z = 100, this.renderer = new
|
|
36
|
+
), this.camera.position.z = 100, this.renderer = new c.WebGLRenderer({
|
|
24
37
|
canvas: this.canvas,
|
|
25
38
|
alpha: !0
|
|
26
39
|
}), this.renderer.setSize(e, t);
|
|
@@ -42,108 +55,167 @@ class R {
|
|
|
42
55
|
this.renderOrder = 0;
|
|
43
56
|
const t = /* @__PURE__ */ new Set();
|
|
44
57
|
this.reconcileNode(e, t);
|
|
45
|
-
for (const [
|
|
46
|
-
t.has(
|
|
58
|
+
for (const [s, n] of this.meshMap.entries())
|
|
59
|
+
t.has(s) || (this.scene.remove(n), n.geometry.dispose(), n.material instanceof c.Material && n.material.dispose(), this.meshMap.delete(s));
|
|
47
60
|
}
|
|
48
61
|
reconcileNode(e, t) {
|
|
62
|
+
var s, n;
|
|
49
63
|
if (e.type === "BOX") {
|
|
50
64
|
t.add(e.element);
|
|
51
65
|
let r = this.meshMap.get(e.element);
|
|
52
|
-
if (r)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
console.log("[V2] 매쉬 신규 생성:", e.element);
|
|
56
|
-
const i = new a.PlaneGeometry(1, 1), o = new a.MeshBasicMaterial({ transparent: !0 });
|
|
57
|
-
r = new a.Mesh(i, o), this.scene.add(r), this.meshMap.set(e.element, r);
|
|
66
|
+
if (!r) {
|
|
67
|
+
const a = new c.PlaneGeometry(1, 1), l = new c.MeshBasicMaterial({ transparent: !0 });
|
|
68
|
+
r = new c.Mesh(a, l), this.scene.add(r), this.meshMap.set(e.element, r);
|
|
58
69
|
}
|
|
59
70
|
this.updateMeshProperties(r, e);
|
|
60
|
-
for (const
|
|
61
|
-
this.reconcileNode(
|
|
71
|
+
for (const a of e.children)
|
|
72
|
+
this.reconcileNode(a, t);
|
|
73
|
+
}
|
|
74
|
+
if (e.type === "TEXT") {
|
|
75
|
+
t.add(e.element);
|
|
76
|
+
let r = this.meshMap.get(e.element);
|
|
77
|
+
if (!r) {
|
|
78
|
+
const d = new c.PlaneGeometry(1, 1), u = new c.MeshBasicMaterial({ transparent: !0 });
|
|
79
|
+
r = new c.Mesh(d, u), r.name = "BG_MESH", this.scene.add(r), this.meshMap.set(e.element, r);
|
|
80
|
+
}
|
|
81
|
+
this.updateMeshProperties(r, e);
|
|
82
|
+
let a = r.children.find(
|
|
83
|
+
(d) => d.name === "TEXT_CHILD"
|
|
84
|
+
);
|
|
85
|
+
const l = JSON.stringify(e.textStyles), m = (s = a == null ? void 0 : a.userData) == null ? void 0 : s.styleHash, h = e.dirtyMask & x;
|
|
86
|
+
if (!a || h || l !== m) {
|
|
87
|
+
a && ((n = a.material.map) == null || n.dispose(), a.geometry.dispose(), r.remove(a));
|
|
88
|
+
const d = N(
|
|
89
|
+
e.textContent || "",
|
|
90
|
+
e.textStyles,
|
|
91
|
+
e.rect.width,
|
|
92
|
+
e.rect.height
|
|
93
|
+
), u = new c.PlaneGeometry(1, 1), f = new c.MeshBasicMaterial({
|
|
94
|
+
map: d,
|
|
95
|
+
transparent: !0,
|
|
96
|
+
side: c.FrontSide,
|
|
97
|
+
color: 16777215,
|
|
98
|
+
opacity: 1
|
|
99
|
+
});
|
|
100
|
+
a = new c.Mesh(u, f), a.name = "TEXT_CHILD", a.userData = { styleHash: l }, a.position.z = 5e-3, r.add(a);
|
|
101
|
+
}
|
|
62
102
|
}
|
|
63
103
|
}
|
|
64
104
|
updateMeshProperties(e, t) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const { rect: r, styles: i } = t, o = this.renderer.domElement.width, l = this.renderer.domElement.height;
|
|
69
|
-
e.scale.set(r.width, r.height, 1);
|
|
70
|
-
const g = 1e-3;
|
|
105
|
+
const { rect: s, styles: n } = t, r = this.renderer.domElement.width, a = this.renderer.domElement.height;
|
|
106
|
+
e.scale.set(s.width, s.height, 1);
|
|
107
|
+
const l = 1e-3;
|
|
71
108
|
this.renderOrder++, e.position.set(
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
|
|
109
|
+
s.x - r / 2 + s.width / 2,
|
|
110
|
+
-s.y + a / 2 - s.height / 2,
|
|
111
|
+
n.zIndex + this.renderOrder * l
|
|
75
112
|
);
|
|
76
|
-
const
|
|
77
|
-
let
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
else if (
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
const
|
|
84
|
-
|
|
113
|
+
const m = e.material, h = n.backgroundColor;
|
|
114
|
+
let p = h, d = 1;
|
|
115
|
+
if (h === "transparent" || h === "rgba(0, 0, 0, 0)")
|
|
116
|
+
p = "#ffffff", d = 0;
|
|
117
|
+
else if (h.startsWith("rgba")) {
|
|
118
|
+
const f = h.match(/[\d.]+/g);
|
|
119
|
+
if (f && f.length >= 4) {
|
|
120
|
+
const E = f[0], M = f[1], v = f[2];
|
|
121
|
+
d = parseFloat(f[3]), p = `rgb(${E}, ${M}, ${v})`;
|
|
85
122
|
}
|
|
86
123
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
124
|
+
const u = n.opacity * d;
|
|
125
|
+
m.color.set(p), m.opacity = u, m.transparent = u < 1;
|
|
89
126
|
}
|
|
90
127
|
render() {
|
|
91
128
|
this.renderer.render(this.scene, this.camera);
|
|
92
129
|
}
|
|
93
130
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
131
|
+
function F(i) {
|
|
132
|
+
var e;
|
|
133
|
+
return i.nodeType === Node.TEXT_NODE && (((e = i.textContent) == null ? void 0 : e.trim().length) || 0) > 0;
|
|
134
|
+
}
|
|
135
|
+
function z(i) {
|
|
136
|
+
const e = Array.from(i.childNodes);
|
|
137
|
+
return e.length === 0 || e.some(
|
|
138
|
+
(n) => n.nodeType === Node.ELEMENT_NODE
|
|
139
|
+
) ? !1 : e.some(F);
|
|
140
|
+
}
|
|
141
|
+
function L(i) {
|
|
142
|
+
const e = parseFloat(i.fontSize);
|
|
143
|
+
let t = parseFloat(i.lineHeight);
|
|
144
|
+
isNaN(t) && (t = e * 1.2);
|
|
145
|
+
let s = parseFloat(i.letterSpacing);
|
|
146
|
+
return isNaN(s) && (s = 0), {
|
|
147
|
+
font: `${i.fontStyle} ${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,
|
|
148
|
+
color: i.color,
|
|
149
|
+
textAlign: i.textAlign || "start",
|
|
150
|
+
textBaseline: "alphabetic",
|
|
151
|
+
direction: i.direction || "inherit",
|
|
152
|
+
lineHeight: t,
|
|
153
|
+
letterSpacing: s
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function S(i, e = T | y | C | x | w) {
|
|
157
|
+
const t = i.getBoundingClientRect(), s = window.getComputedStyle(i);
|
|
158
|
+
if (t.width === 0 || t.height === 0 || s.display === "none")
|
|
96
159
|
return null;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
160
|
+
let n = i.getAttribute("data-mid");
|
|
161
|
+
n || (n = Math.random().toString(36).substring(2, 11), i.setAttribute("data-mid", n));
|
|
162
|
+
const r = parseInt(s.zIndex), a = {
|
|
163
|
+
backgroundColor: s.backgroundColor,
|
|
164
|
+
opacity: parseFloat(s.opacity),
|
|
165
|
+
zIndex: isNaN(r) ? 0 : r,
|
|
166
|
+
borderRadius: s.borderRadius,
|
|
167
|
+
borderColor: s.borderColor,
|
|
168
|
+
borderWidth: s.borderWidth
|
|
169
|
+
};
|
|
170
|
+
let l = "BOX", m, h;
|
|
171
|
+
const p = [];
|
|
172
|
+
return z(i) ? (l = "TEXT", m = i.textContent || "", h = L(s)) : Array.from(i.children).forEach((d) => {
|
|
173
|
+
const u = S(d, e);
|
|
174
|
+
u && p.push(u);
|
|
175
|
+
}), {
|
|
176
|
+
id: n,
|
|
177
|
+
type: l,
|
|
178
|
+
element: i,
|
|
179
|
+
rect: {
|
|
180
|
+
x: t.left + window.scrollX,
|
|
181
|
+
y: t.top + window.scrollY,
|
|
182
|
+
width: t.width,
|
|
183
|
+
height: t.height
|
|
184
|
+
},
|
|
185
|
+
styles: a,
|
|
186
|
+
textContent: m,
|
|
187
|
+
textStyles: h,
|
|
116
188
|
dirtyMask: e,
|
|
117
|
-
children:
|
|
189
|
+
children: p
|
|
118
190
|
};
|
|
119
191
|
}
|
|
120
|
-
class
|
|
192
|
+
class O {
|
|
121
193
|
constructor(e, t) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.target.contains(e.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |=
|
|
194
|
+
o(this, "target");
|
|
195
|
+
o(this, "renderer");
|
|
196
|
+
o(this, "observer");
|
|
197
|
+
o(this, "isDomDirty", !1);
|
|
198
|
+
o(this, "isRunning", !1);
|
|
199
|
+
o(this, "pendingMask", g);
|
|
200
|
+
o(this, "mutationTimer", null);
|
|
201
|
+
o(this, "cssTimer", null);
|
|
202
|
+
o(this, "onTransitionFinished", (e) => {
|
|
203
|
+
this.target.contains(e.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= T | y, this.cssTimer = window.setTimeout(() => {
|
|
132
204
|
this.isDomDirty = !0, this.cssTimer = null;
|
|
133
205
|
}, 50));
|
|
134
206
|
});
|
|
135
|
-
|
|
207
|
+
o(this, "onWindowResize", () => {
|
|
136
208
|
this.renderer.setSize(window.innerWidth, window.innerHeight), this.isDomDirty = !0;
|
|
137
209
|
});
|
|
138
|
-
|
|
210
|
+
o(this, "renderLoop", () => {
|
|
139
211
|
this.isRunning && (this.isDomDirty && this.forceUpdateScene(), this.renderer.render(), requestAnimationFrame(this.renderLoop));
|
|
140
212
|
});
|
|
141
|
-
this.target = e, this.renderer = t, this.observer = new MutationObserver((
|
|
142
|
-
let
|
|
143
|
-
for (const
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
if (this.pendingMask |=
|
|
213
|
+
this.target = e, this.renderer = t, this.observer = new MutationObserver((s) => {
|
|
214
|
+
let n = g;
|
|
215
|
+
for (const r of s)
|
|
216
|
+
r.type === "childList" ? n |= w : r.type === "attributes" && (r.attributeName === "style" || r.attributeName === "class") && (n |= T | y);
|
|
217
|
+
if (n !== g) {
|
|
218
|
+
if (this.pendingMask |= n, n & w) {
|
|
147
219
|
this.clearTimers(), console.log("Structural Change detected"), this.isDomDirty = !0;
|
|
148
220
|
return;
|
|
149
221
|
}
|
|
@@ -169,19 +241,19 @@ class C {
|
|
|
169
241
|
}
|
|
170
242
|
forceUpdateScene() {
|
|
171
243
|
this.isDomDirty = !1;
|
|
172
|
-
const e =
|
|
173
|
-
e && this.renderer.syncScene(e), this.pendingMask =
|
|
244
|
+
const e = S(this.target, this.pendingMask);
|
|
245
|
+
e && this.renderer.syncScene(e), this.pendingMask = g;
|
|
174
246
|
}
|
|
175
247
|
}
|
|
176
|
-
class
|
|
248
|
+
class _ {
|
|
177
249
|
constructor(e) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
250
|
+
o(this, "renderer");
|
|
251
|
+
o(this, "syncer");
|
|
252
|
+
o(this, "target");
|
|
181
253
|
const t = document.querySelector(e);
|
|
182
254
|
if (!t)
|
|
183
255
|
throw new Error(`[Mirage] Element not found: ${e}`);
|
|
184
|
-
this.target = t, this.renderer = new R(), this.renderer.mount(document.body), this.syncer = new
|
|
256
|
+
this.target = t, this.renderer = new R(), this.renderer.mount(document.body), this.syncer = new O(this.target, this.renderer);
|
|
185
257
|
}
|
|
186
258
|
start() {
|
|
187
259
|
this.syncer.start();
|
|
@@ -191,5 +263,5 @@ class k {
|
|
|
191
263
|
}
|
|
192
264
|
}
|
|
193
265
|
export {
|
|
194
|
-
|
|
266
|
+
_ as Mirage
|
|
195
267
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(l,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],d):(l=typeof globalThis<"u"?globalThis:l||self,d(l.MirageEngine={},l.THREE))})(this,function(l,d){"use strict";var H=Object.defineProperty;var k=(l,d,T)=>d in l?H(l,d,{enumerable:!0,configurable:!0,writable:!0,value:T}):l[d]=T;var o=(l,d,T)=>(k(l,typeof d!="symbol"?d+"":d,T),T);function T(i){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const t in i)if(t!=="default"){const r=Object.getOwnPropertyDescriptor(i,t);Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>i[t]})}}return e.default=i,Object.freeze(e)}const c=T(d),w=0,S=1,x=2,v=4,E=8,M=16;function D(i,e,t,r){const s=document.createElement("canvas"),n=s.getContext("2d");if(!n)throw new Error("[Mirage] Failed to create canvas context");const a=window.devicePixelRatio||2;s.width=t*a,s.height=r*a,n.scale(a,a),n.font=e.font,n.fillStyle=e.color,n.textAlign=e.textAlign,n.direction=e.direction,n.textBaseline="middle";let m=0;e.textAlign==="center"&&(m=t/2),e.textAlign==="right"&&(m=t);const f=r/2;n.fillText(i,m,f);const h=new c.CanvasTexture(s);return h.colorSpace=c.SRGBColorSpace,h.minFilter=c.LinearFilter,h.needsUpdate=!0,h}class C{constructor(){o(this,"canvas");o(this,"scene");o(this,"camera");o(this,"renderer");o(this,"renderOrder",0);o(this,"meshMap",new Map);this.canvas=document.createElement("canvas"),this.scene=new c.Scene;const e=window.innerWidth,t=window.innerHeight;this.camera=new c.OrthographicCamera(e/-2,e/2,t/2,t/-2,1,1e3),this.camera.position.z=100,this.renderer=new c.WebGLRenderer({canvas:this.canvas,alpha:!0}),this.renderer.setSize(e,t)}mount(e){e.appendChild(this.canvas)}dispose(){try{this.renderer.dispose()}catch{}this.canvas.parentElement&&this.canvas.parentElement.removeChild(this.canvas)}setSize(e,t){this.renderer.setSize(e,t),this.camera.left=e/-2,this.camera.right=e/2,this.camera.top=t/2,this.camera.bottom=t/-2,this.camera.updateProjectionMatrix()}syncScene(e){this.renderOrder=0;const t=new Set;this.reconcileNode(e,t);for(const[r,s]of this.meshMap.entries())t.has(r)||(this.scene.remove(s),s.geometry.dispose(),s.material instanceof c.Material&&s.material.dispose(),this.meshMap.delete(r))}reconcileNode(e,t){var r,s;if(e.type==="BOX"){t.add(e.element);let n=this.meshMap.get(e.element);if(!n){const a=new c.PlaneGeometry(1,1),m=new c.MeshBasicMaterial({transparent:!0});n=new c.Mesh(a,m),this.scene.add(n),this.meshMap.set(e.element,n)}this.updateMeshProperties(n,e);for(const a of e.children)this.reconcileNode(a,t)}if(e.type==="TEXT"){t.add(e.element);let n=this.meshMap.get(e.element);if(!n){const u=new c.PlaneGeometry(1,1),p=new c.MeshBasicMaterial({transparent:!0});n=new c.Mesh(u,p),n.name="BG_MESH",this.scene.add(n),this.meshMap.set(e.element,n)}this.updateMeshProperties(n,e);let a=n.children.find(u=>u.name==="TEXT_CHILD");const m=JSON.stringify(e.textStyles),f=(r=a==null?void 0:a.userData)==null?void 0:r.styleHash,h=e.dirtyMask&M;if(!a||h||m!==f){a&&((s=a.material.map)==null||s.dispose(),a.geometry.dispose(),n.remove(a));const u=D(e.textContent||"",e.textStyles,e.rect.width,e.rect.height),p=new c.PlaneGeometry(1,1),g=new c.MeshBasicMaterial({map:u,transparent:!0,side:c.FrontSide,color:16777215,opacity:1});a=new c.Mesh(p,g),a.name="TEXT_CHILD",a.userData={styleHash:m},a.position.z=.005,n.add(a)}}}updateMeshProperties(e,t){const{rect:r,styles:s}=t,n=this.renderer.domElement.width,a=this.renderer.domElement.height;e.scale.set(r.width,r.height,1);const m=.001;this.renderOrder++,e.position.set(r.x-n/2+r.width/2,-r.y+a/2-r.height/2,s.zIndex+this.renderOrder*m);const f=e.material,h=s.backgroundColor;let y=h,u=1;if(h==="transparent"||h==="rgba(0, 0, 0, 0)")y="#ffffff",u=0;else if(h.startsWith("rgba")){const g=h.match(/[\d.]+/g);if(g&&g.length>=4){const L=g[0],_=g[1],I=g[2];u=parseFloat(g[3]),y=`rgb(${L}, ${_}, ${I})`}}const p=s.opacity*u;f.color.set(y),f.opacity=p,f.transparent=p<1}render(){this.renderer.render(this.scene,this.camera)}}function N(i){var e;return i.nodeType===Node.TEXT_NODE&&(((e=i.textContent)==null?void 0:e.trim().length)||0)>0}function R(i){const e=Array.from(i.childNodes);return e.length===0||e.some(s=>s.nodeType===Node.ELEMENT_NODE)?!1:e.some(N)}function O(i){const e=parseFloat(i.fontSize);let t=parseFloat(i.lineHeight);isNaN(t)&&(t=e*1.2);let r=parseFloat(i.letterSpacing);return isNaN(r)&&(r=0),{font:`${i.fontStyle} ${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,color:i.color,textAlign:i.textAlign||"start",textBaseline:"alphabetic",direction:i.direction||"inherit",lineHeight:t,letterSpacing:r}}function b(i,e=S|x|v|M|E){const t=i.getBoundingClientRect(),r=window.getComputedStyle(i);if(t.width===0||t.height===0||r.display==="none")return null;let s=i.getAttribute("data-mid");s||(s=Math.random().toString(36).substring(2,11),i.setAttribute("data-mid",s));const n=parseInt(r.zIndex),a={backgroundColor:r.backgroundColor,opacity:parseFloat(r.opacity),zIndex:isNaN(n)?0:n,borderRadius:r.borderRadius,borderColor:r.borderColor,borderWidth:r.borderWidth};let m="BOX",f,h;const y=[];return R(i)?(m="TEXT",f=i.textContent||"",h=O(r)):Array.from(i.children).forEach(u=>{const p=b(u,e);p&&y.push(p)}),{id:s,type:m,element:i,rect:{x:t.left+window.scrollX,y:t.top+window.scrollY,width:t.width,height:t.height},styles:a,textContent:f,textStyles:h,dirtyMask:e,children:y}}class z{constructor(e,t){o(this,"target");o(this,"renderer");o(this,"observer");o(this,"isDomDirty",!1);o(this,"isRunning",!1);o(this,"pendingMask",w);o(this,"mutationTimer",null);o(this,"cssTimer",null);o(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=S|x,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});o(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});o(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=e,this.renderer=t,this.observer=new MutationObserver(r=>{let s=w;for(const n of r)n.type==="childList"?s|=E:n.type==="attributes"&&(n.attributeName==="style"||n.attributeName==="class")&&(s|=S|x);if(s!==w){if(this.pendingMask|=s,s&E){this.clearTimers(),console.log("Structural Change detected"),this.isDomDirty=!0;return}this.mutationTimer&&clearTimeout(this.mutationTimer),this.mutationTimer=window.setTimeout(()=>{this.mutationTimer=null,this.isDomDirty=!0},200)}})}start(){this.isRunning||(this.isRunning=!0,this.observer.observe(this.target,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),this.target.addEventListener("transitionend",this.onTransitionFinished),this.target.addEventListener("animationend",this.onTransitionFinished),window.addEventListener("resize",this.onWindowResize),this.forceUpdateScene(),this.renderLoop())}stop(){this.isRunning=!1,this.observer.disconnect(),this.clearTimers(),this.target.removeEventListener("transitionend",this.onTransitionFinished),this.target.removeEventListener("animationend",this.onTransitionFinished),window.removeEventListener("resize",this.onWindowResize)}clearTimers(){this.mutationTimer&&(clearTimeout(this.mutationTimer),this.mutationTimer=null),this.cssTimer&&(clearTimeout(this.cssTimer),this.cssTimer=null)}forceUpdateScene(){this.isDomDirty=!1;const e=b(this.target,this.pendingMask);e&&this.renderer.syncScene(e),this.pendingMask=w}}class F{constructor(e){o(this,"renderer");o(this,"syncer");o(this,"target");const t=document.querySelector(e);if(!t)throw new Error(`[Mirage] Element not found: ${e}`);this.target=t,this.renderer=new C,this.renderer.mount(document.body),this.syncer=new z(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop(),this.renderer.dispose()}}l.Mirage=F,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type NodeType = "BOX" | "TEXT";
|
|
1
2
|
export interface NodeRect {
|
|
2
3
|
x: number;
|
|
3
4
|
y: number;
|
|
@@ -8,12 +9,27 @@ export interface BoxStyles {
|
|
|
8
9
|
backgroundColor: string;
|
|
9
10
|
opacity: number;
|
|
10
11
|
zIndex: number;
|
|
12
|
+
borderRadius: string;
|
|
13
|
+
borderColor: string;
|
|
14
|
+
borderWidth: string;
|
|
15
|
+
}
|
|
16
|
+
export interface TextStyles {
|
|
17
|
+
font: string;
|
|
18
|
+
color: string;
|
|
19
|
+
textAlign: CanvasTextAlign;
|
|
20
|
+
textBaseline: CanvasTextBaseline;
|
|
21
|
+
direction: CanvasDirection;
|
|
22
|
+
lineHeight: number;
|
|
23
|
+
letterSpacing: number;
|
|
11
24
|
}
|
|
12
25
|
export interface SceneNode {
|
|
13
|
-
|
|
26
|
+
id: string;
|
|
27
|
+
type: NodeType;
|
|
14
28
|
element: HTMLElement;
|
|
15
29
|
rect: NodeRect;
|
|
16
30
|
styles: BoxStyles;
|
|
31
|
+
textContent?: string;
|
|
32
|
+
textStyles?: TextStyles;
|
|
17
33
|
dirtyMask: number;
|
|
18
34
|
children: SceneNode[];
|
|
19
35
|
}
|
|
@@ -22,3 +38,4 @@ export declare const DIRTY_RECT: number;
|
|
|
22
38
|
export declare const DIRTY_STYLE: number;
|
|
23
39
|
export declare const DIRTY_ZINDEX: number;
|
|
24
40
|
export declare const DIRTY_STRUCTURE: number;
|
|
41
|
+
export declare const DIRTY_CONTENT: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mirage-engine",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"description": "An engine that mirrors HTML DOM elements to a WebGL scene in real-time.",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"types": "./dist/index.d.ts",
|
|
6
7
|
"main": "./dist/mirage-engine.umd.js",
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
],
|
|
22
23
|
"scripts": {
|
|
23
24
|
"dev": "vite",
|
|
24
|
-
"build": "tsc && vite build",
|
|
25
|
+
"build": "tsc && vite build",
|
|
25
26
|
"prepublishOnly": "npm run build"
|
|
26
27
|
},
|
|
27
28
|
"peerDependencies": {
|
|
@@ -34,7 +35,13 @@
|
|
|
34
35
|
"vite": "^4.0.0",
|
|
35
36
|
"vite-plugin-dts": "^4.5.4"
|
|
36
37
|
},
|
|
37
|
-
"keywords": [
|
|
38
|
+
"keywords": [
|
|
39
|
+
"webgl",
|
|
40
|
+
"dom",
|
|
41
|
+
"mirroring",
|
|
42
|
+
"threejs",
|
|
43
|
+
"typescript"
|
|
44
|
+
],
|
|
38
45
|
"author": "dltldn333@gmail.com",
|
|
39
46
|
"license": "MIT"
|
|
40
47
|
}
|