frontend/src/3dModels/CameraControls/CameraOverlay.tsx

90 lines
No EOL
2.9 KiB
TypeScript

import { useThree } from "@react-three/fiber";
import { useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom";
interface Props {
showResetButton?: boolean;
onReset?: () => void;
}
/**
* Renders camera control UI buttons as a DOM overlay on top of the R3F canvas.
* Must be placed inside a <Canvas> so it has access to useThree().
*
* Uses a portal into a div that is absolutely positioned over the canvas element,
* so the buttons sit in 2D screen space rather than 3D world space.
*/
export default function CameraOverlay({ showResetButton, onReset }: Props) {
const { gl } = useThree();
const [container, setContainer] = useState<HTMLDivElement | null>(null);
useEffect(() => {
const canvas = gl.domElement;
const parent = canvas.parentElement;
if (!parent) return;
// Ensure the parent is positioned so absolute children are relative to it
const parentPosition = getComputedStyle(parent).position;
if (parentPosition === "static") {
parent.style.position = "relative";
}
const div = document.createElement("div");
div.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
`;
parent.appendChild(div);
setContainer(div);
return () => {
parent.removeChild(div);
};
}, [gl]);
if (!container) return null;
return ReactDOM.createPortal(
<div style={{
position: "absolute",
bottom: 12,
right: 12,
display: "flex",
flexDirection: "column",
gap: 8,
pointerEvents: "auto",
}}>
{showResetButton && (
<button
onClick={onReset}
title="Reset camera"
style={{
width: 36,
height: 36,
borderRadius: 6,
border: "1px solid rgba(255,255,255,0.2)",
background: "rgba(0,0,0,0.45)",
color: "white",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 16,
backdropFilter: "blur(4px)",
transition: "background 0.15s",
}}
onMouseEnter={e => (e.currentTarget.style.background = "rgba(0,0,0,0.7)")}
onMouseLeave={e => (e.currentTarget.style.background = "rgba(0,0,0,0.45)")}
>
</button>
)}
</div>,
container
);
}