4.9 KiB
Canvas Overlay Pattern
Overview
Status: ✅ Proven Pattern (CommentLayer is production-ready)
Location: packages/noodl-editor/src/editor/src/views/commentlayer.ts
Created: Phase 4 PREREQ-003
This document describes the pattern for creating React overlays that float above the HTML5 Canvas in the Node Graph Editor. The pattern is proven and production-tested via CommentLayer.
What This Pattern Enables
React components that:
- Float over the HTML5 Canvas
- Stay synchronized with canvas pan/zoom
- Handle mouse events intelligently (overlay vs canvas)
- Integrate with the existing EventDispatcher system
- Use modern React 19 APIs
Why This Matters
Phase 4 visualization views need this pattern:
- VIEW-005: Data Lineage - Glowing path highlights
- VIEW-006: Impact Radar - Dependency visualization
- VIEW-007: Semantic Layers - Node visibility filtering
All of these require React UI floating over the canvas with proper coordinate transformation and event handling.
Documentation Structure
This pattern is documented across several focused files:
- Architecture Overview - How overlays integrate with NodeGraphEditor
- Coordinate Transforms - Canvas space ↔ Screen space conversion
- Mouse Event Handling - Intelligent event routing
- React Integration - React 19 patterns and lifecycle
- Code Examples - Practical implementation examples
Quick Start
Minimal Overlay Example
import React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { NodeGraphEditor } from './nodegrapheditor';
class SimpleOverlay {
private root: Root;
private container: HTMLDivElement;
constructor(private nodegraphEditor: NodeGraphEditor) {}
renderTo(container: HTMLDivElement) {
this.container = container;
this.root = createRoot(container);
this.render();
}
setPanAndScale(panAndScale: { x: number; y: number; scale: number }) {
const transform = `scale(${panAndScale.scale}) translate(${panAndScale.x}px, ${panAndScale.y}px)`;
this.container.style.transform = transform;
}
private render() {
this.root.render(<div>My Overlay Content</div>);
}
dispose() {
if (this.root) {
this.root.unmount();
}
}
}
Integration with NodeGraphEditor
// In nodegrapheditor.ts
this.myOverlay = new SimpleOverlay(this);
this.myOverlay.renderTo(overlayDiv);
// Update on pan/zoom
this.myOverlay.setPanAndScale(this.getPanAndScale());
Key Insights from CommentLayer
1. CSS Transform Strategy (Brilliant!)
The entire overlay stays in sync via a single CSS transform on the container:
const transform = `scale(${scale}) translate(${x}px, ${y}px)`;
container.style.transform = transform;
No complex calculations per element - the browser handles it all!
2. React Root Reuse
Create roots once, reuse for all re-renders:
if (!this.root) {
this.root = createRoot(this.container);
}
this.root.render(<MyComponent {...props} />);
3. Two-Layer System
CommentLayer uses two layers:
- Background layer - Behind canvas (e.g., colored comment boxes)
- Foreground layer - In front of canvas (e.g., comment controls, resize handles)
This allows visual layering: comments behind nodes, but controls in front.
4. Mouse Event Forwarding
Complex but powerful: overlay determines if clicks should go to canvas or stay in overlay. See Mouse Event Handling for details.
Common Gotchas
❌ Don't: Create new roots on every render
// BAD - memory leak!
render() {
this.root = createRoot(this.container);
this.root.render(<Component />);
}
✅ Do: Create once, reuse
// GOOD
constructor() {
this.root = createRoot(this.container);
}
render() {
this.root.render(<Component />);
}
❌ Don't: Manually calculate positions for every element
// BAD - complex and slow
elements.forEach((el) => {
el.style.left = (el.x + pan.x) * scale + 'px';
el.style.top = (el.y + pan.y) * scale + 'px';
});
✅ Do: Use container transform
// GOOD - browser handles it
container.style.transform = `scale(${scale}) translate(${pan.x}px, ${pan.y}px)`;
Next Steps
- Read Architecture Overview to understand integration
- Review CommentLayer source for full example
- Check Code Examples for specific patterns