diff --git a/src/hooks/useDwgViewer.ts b/src/hooks/useDwgViewer.ts index cad2d86..d4bf336 100644 --- a/src/hooks/useDwgViewer.ts +++ b/src/hooks/useDwgViewer.ts @@ -78,8 +78,14 @@ export function useDwgViewer() { // mount Viewer2D once useEffect(() => { - if (!containerRef.current) return; - const viewer = new Viewer2D(containerRef.current); + const container = containerRef.current; + if (!container) return; + // Viewer2D has no dispose/teardown API (permanent window/canvas + // listeners, its own rAF loop) — defensively clear any leftover canvas + // from a prior construction (e.g. a dev Fast Refresh remount) so it + // never stacks two live WebGL contexts in the same container. + container.replaceChildren(); + const viewer = new Viewer2D(container); viewerRef.current = viewer; viewer.setTheme(true); viewer.onSelect((entity) => setSelectedEntity(entity)); diff --git a/src/main.tsx b/src/main.tsx index dfacde0..2bf1b15 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,10 @@ -import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App'; import './index.css'; -createRoot(document.getElementById('root')!).render( - - - , -); +// No : Viewer2D (ported unchanged from the vanilla app) has no +// dispose/teardown API and attaches permanent window/canvas listeners in its +// constructor. StrictMode's dev-only mount→unmount→remount double-invoke +// would construct it twice into the same container with no way to tear the +// first instance down, leaving two overlapping WebGL canvases. +createRoot(document.getElementById('root')!).render();