<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>XR CSS Viewer</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, sans-serif;
background: #1a1a2e;
color: #fff;
overflow: hidden;
}
#container {
width: 100vw;
height: 100vh;
}
#controls {
position: fixed;
top: 10px;
left: 10px;
z-index: 100;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 8px;
max-width: 300px;
}
#controls select,
#controls button {
width: 100%;
padding: 8px;
margin: 5px 0;
border-radius: 4px;
border: none;
font-size: 14px;
}
#controls select {
background: #333;
color: #fff;
}
#controls button {
background: #6200ee;
color: #fff;
cursor: pointer;
}
#controls button:hover {
background: #7c4dff;
}
#info {
position: fixed;
bottom: 10px;
left: 10px;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 8px;
font-size: 12px;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="controls">
<h3 style="margin-bottom: 10px">XR CSS Viewer</h3>
<select id="demoSelect">
<option value="menu">VR Menu</option>
<option value="dashboard">Dashboard</option>
<option value="nested">Nested Flexbox</option>
<option value="justify">Justify Content</option>
</select>
<button id="loadBtn">Load Demo</button>
<div style="margin-top: 10px; font-size: 12px; color: #aaa">
ドラッグ: 回転<br />
スクロール: ズーム
</div>
</div>
<div id="info">Elements: <span id="elementCount">0</span></div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
const DEMOS = {
menu: [
{
id: 0,
x: 0,
y: 0,
w: 340,
h: 391,
bg: [0.078, 0.078, 0.157, 0.95],
color: [0, 0, 0, 0],
opacity: 1,
radius: 16,
fontSize: 16,
},
{
id: 1,
x: 20,
y: 20,
w: 97.2,
h: 80,
bg: [0.384, 0, 0.933, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 8,
fontSize: 16,
text: "VR Menu",
},
{
id: 3,
x: 40,
y: 120,
w: 120,
h: 69,
bg: [0.012, 0.855, 0.776, 1],
color: [0, 0, 0, 1],
opacity: 1,
radius: 8,
fontSize: 16,
text: "Start Game",
},
{
id: 4,
x: 40,
y: 201,
w: 100.8,
h: 69,
bg: [0.012, 0.855, 0.776, 1],
color: [0, 0, 0, 1],
opacity: 1,
radius: 8,
fontSize: 16,
text: "Settings",
},
{
id: 5,
x: 40,
y: 282,
w: 62.4,
h: 69,
bg: [0.012, 0.855, 0.776, 1],
color: [0, 0, 0, 1],
opacity: 1,
radius: 8,
fontSize: 16,
text: "Exit",
},
],
dashboard: [
{
id: 0,
x: 0,
y: 0,
w: 430,
h: 200.4,
bg: [0, 0, 0, 0.9],
color: [0, 0, 0, 0],
opacity: 1,
radius: 12,
fontSize: 16,
},
{
id: 1,
x: 15,
y: 15,
w: 154.4,
h: 60,
bg: [0.098, 0.463, 0.824, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 0,
fontSize: 16,
text: "System Monitor",
},
{
id: 3,
x: 30,
y: 90,
w: 130,
h: 80.4,
bg: [0.149, 0.196, 0.22, 1],
color: [0, 0, 0, 0],
opacity: 1,
radius: 8,
fontSize: 16,
},
{
id: 4,
x: 45,
y: 105,
w: 21.6,
h: 16.8,
bg: [0, 0, 0, 0],
color: [0.502, 0.502, 0.502, 1],
opacity: 1,
radius: 0,
fontSize: 12,
text: "CPU",
},
{
id: 5,
x: 45,
y: 121.8,
w: 43.2,
h: 33.6,
bg: [0, 0, 0, 0],
color: [0.298, 0.686, 0.314, 1],
opacity: 1,
radius: 0,
fontSize: 24,
text: "45%",
},
{
id: 6,
x: 170,
y: 90,
w: 130,
h: 80.4,
bg: [0.149, 0.196, 0.22, 1],
color: [0, 0, 0, 0],
opacity: 1,
radius: 8,
fontSize: 16,
},
{
id: 7,
x: 185,
y: 105,
w: 21.6,
h: 16.8,
bg: [0, 0, 0, 0],
color: [0.502, 0.502, 0.502, 1],
opacity: 1,
radius: 0,
fontSize: 12,
text: "RAM",
},
{
id: 8,
x: 185,
y: 121.8,
w: 72,
h: 33.6,
bg: [0, 0, 0, 0],
color: [0.298, 0.686, 0.314, 1],
opacity: 1,
radius: 0,
fontSize: 24,
text: "8.2GB",
},
{
id: 9,
x: 310,
y: 90,
w: 130,
h: 80.4,
bg: [0.149, 0.196, 0.22, 1],
color: [0, 0, 0, 0],
opacity: 1,
radius: 8,
fontSize: 16,
},
{
id: 10,
x: 325,
y: 105,
w: 21.6,
h: 16.8,
bg: [0, 0, 0, 0],
color: [0.502, 0.502, 0.502, 1],
opacity: 1,
radius: 0,
fontSize: 12,
text: "GPU",
},
{
id: 11,
x: 325,
y: 121.8,
w: 43.2,
h: 33.6,
bg: [0, 0, 0, 0],
color: [0.298, 0.686, 0.314, 1],
opacity: 1,
radius: 0,
fontSize: 24,
text: "72%",
},
],
nested: [
{
id: 0,
x: 0,
y: 0,
w: 390,
h: 250,
bg: [0.129, 0.129, 0.129, 1],
color: [0, 0, 0, 0],
opacity: 1,
radius: 0,
fontSize: 16,
},
{
id: 2,
x: 20,
y: 20,
w: 100,
h: 100,
bg: [0.957, 0.263, 0.212, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 8,
fontSize: 16,
text: "A",
},
{
id: 3,
x: 130,
y: 20,
w: 100,
h: 100,
bg: [0.298, 0.686, 0.314, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 8,
fontSize: 16,
text: "B",
},
{
id: 5,
x: 20,
y: 130,
w: 100,
h: 100,
bg: [0.129, 0.588, 0.953, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 8,
fontSize: 16,
text: "C",
},
{
id: 6,
x: 130,
y: 130,
w: 100,
h: 100,
bg: [1, 0.922, 0.231, 1],
color: [0, 0, 0, 1],
opacity: 1,
radius: 8,
fontSize: 16,
text: "D",
},
{
id: 7,
x: 240,
y: 130,
w: 100,
h: 100,
bg: [0.612, 0.153, 0.69, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 8,
fontSize: 16,
text: "E",
},
],
justify: [
{
id: 0,
x: 0,
y: 0,
w: 330,
h: 260,
bg: [0.102, 0.102, 0.18, 1],
color: [0, 0, 0, 0],
opacity: 1,
radius: 0,
fontSize: 16,
},
{
id: 1,
x: 15,
y: 15,
w: 280,
h: 50,
bg: [0.086, 0.129, 0.243, 1],
color: [0, 0, 0, 0],
opacity: 1,
radius: 0,
fontSize: 16,
},
{
id: 2,
x: 20,
y: 20,
w: 50,
h: 40,
bg: [0.914, 0.271, 0.376, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 0,
fontSize: 16,
text: "1",
},
{
id: 3,
x: 70,
y: 20,
w: 50,
h: 40,
bg: [0.914, 0.271, 0.376, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 0,
fontSize: 16,
text: "2",
},
{
id: 4,
x: 15,
y: 75,
w: 280,
h: 50,
bg: [0.086, 0.129, 0.243, 1],
color: [0, 0, 0, 0],
opacity: 1,
radius: 0,
fontSize: 16,
},
{
id: 5,
x: 105,
y: 80,
w: 50,
h: 40,
bg: [0.914, 0.271, 0.376, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 0,
fontSize: 16,
text: "1",
},
{
id: 6,
x: 155,
y: 80,
w: 50,
h: 40,
bg: [0.914, 0.271, 0.376, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 0,
fontSize: 16,
text: "2",
},
{
id: 7,
x: 15,
y: 135,
w: 280,
h: 50,
bg: [0.086, 0.129, 0.243, 1],
color: [0, 0, 0, 0],
opacity: 1,
radius: 0,
fontSize: 16,
},
{
id: 8,
x: 190,
y: 140,
w: 50,
h: 40,
bg: [0.914, 0.271, 0.376, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 0,
fontSize: 16,
text: "1",
},
{
id: 9,
x: 240,
y: 140,
w: 50,
h: 40,
bg: [0.914, 0.271, 0.376, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 0,
fontSize: 16,
text: "2",
},
{
id: 10,
x: 15,
y: 195,
w: 280,
h: 50,
bg: [0.086, 0.129, 0.243, 1],
color: [0, 0, 0, 0],
opacity: 1,
radius: 0,
fontSize: 16,
},
{
id: 11,
x: 20,
y: 200,
w: 50,
h: 40,
bg: [0.914, 0.271, 0.376, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 0,
fontSize: 16,
text: "1",
},
{
id: 12,
x: 240,
y: 200,
w: 50,
h: 40,
bg: [0.914, 0.271, 0.376, 1],
color: [1, 1, 1, 1],
opacity: 1,
radius: 0,
fontSize: 16,
text: "2",
},
],
};
const container = document.getElementById("container");
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
const camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 0, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
container.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
let panelGroup = new THREE.Group();
scene.add(panelGroup);
const SCALE = 0.01;
function createRoundedRectShape(w, h, r) {
const shape = new THREE.Shape();
const x = -w / 2;
const y = -h / 2;
r = Math.min(r, w / 2, h / 2);
shape.moveTo(x + r, y);
shape.lineTo(x + w - r, y);
shape.quadraticCurveTo(x + w, y, x + w, y + r);
shape.lineTo(x + w, y + h - r);
shape.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
shape.lineTo(x + r, y + h);
shape.quadraticCurveTo(x, y + h, x, y + h - r);
shape.lineTo(x, y + r);
shape.quadraticCurveTo(x, y, x + r, y);
return shape;
}
function createTextTexture(text, fontSize, color, w, h) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const scale = 4;
canvas.width = w * scale;
canvas.height = h * scale;
ctx.scale(scale, scale);
ctx.fillStyle = `rgba(${color[0] * 255}, ${color[1] * 255}, ${
color[2] * 255
}, ${color[3]})`;
ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, w / 2, h / 2);
const texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = true;
return texture;
}
function loadElements(elements) {
while (panelGroup.children.length > 0) {
const child = panelGroup.children[0];
if (child.geometry) child.geometry.dispose();
if (child.material) {
if (child.material.map) child.material.map.dispose();
child.material.dispose();
}
panelGroup.remove(child);
}
let maxX = 0,
maxY = 0;
elements.forEach((el) => {
maxX = Math.max(maxX, el.x + el.w);
maxY = Math.max(maxY, el.y + el.h);
});
const offsetX = maxX / 2;
const offsetY = maxY / 2;
elements.forEach((el, idx) => {
const w = el.w * SCALE;
const h = el.h * SCALE;
const x = (el.x + el.w / 2 - offsetX) * SCALE;
const y = -(el.y + el.h / 2 - offsetY) * SCALE; const z = idx * 0.01;
if (el.bg[3] > 0) {
const r = (el.radius || 0) * SCALE;
const shape = createRoundedRectShape(w, h, r);
const geometry = new THREE.ShapeGeometry(shape);
const material = new THREE.MeshStandardMaterial({
color: new THREE.Color(el.bg[0], el.bg[1], el.bg[2]),
transparent: el.bg[3] < 1 || el.opacity < 1,
opacity: el.bg[3] * el.opacity,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, z);
panelGroup.add(mesh);
}
if (el.text) {
const textW = el.w;
const textH = el.h;
const texture = createTextTexture(
el.text,
el.fontSize,
el.color,
textW,
textH
);
const textGeo = new THREE.PlaneGeometry(w, h);
const textMat = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
side: THREE.DoubleSide,
});
const textMesh = new THREE.Mesh(textGeo, textMat);
textMesh.position.set(x, y, z + 0.005);
panelGroup.add(textMesh);
}
});
document.getElementById("elementCount").textContent = elements.length;
}
document.getElementById("loadBtn").addEventListener("click", () => {
const demo = document.getElementById("demoSelect").value;
loadElements(DEMOS[demo]);
});
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
loadElements(DEMOS.menu);
animate();
</script>
</body>
</html>