Comfy-Org / Comfy-Org/ComfyUI_frontend
perf: Add CSS `contain: paint` to Vue nodes via structural refactor
- Dominant language
- TypeScript
- Stars
- 2k
- Forks
- 699
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 490
Description
## Summary
Add CSS `contain: paint` to Vue node containers to reduce Chrome Layer time from **65ms → 25ms** (~62% improvement) on 245-node workflows.
`contain: paint` cannot be applied directly to the current `.lg-node` container because slot connection dots (input/output ports) use `translate-x-1/2` / `-translate-x-1/2` to hang ~12px outside the node boundary. Paint containment clips these.
## Evidence
- **Source:** Chrome DevTools Performance panel, 245-node workflow (rizumu, Mar 2026)
- **Layer time:** 65ms → 25ms with `contain: paint`
- **Backlog item:** `docs/perf/BACKLOG.md` #21
## Root Cause of Clipping
Per [CSS Containment Level 2 spec](https://www.w3.org/TR/css-contain-2/#paint-containment), `contain: paint` clips all descendant painting at the element's border-box edge. Children cannot override this with `overflow: visible`. The following elements overflow:
| Element | Class | Overflow |
|---------|-------|----------|
| Input slot dot | `-translate-x-1/2` (InputSlot.vue L25) | 12px left |
| Output slot dot | `translate-x-1/2` (OutputSlot.vue L20) | 12px right |
| Collapsed input dot | `absolute left-0 -translate-x-1/2` (LGraphNode.vue L61) | 12px left |
| Collapsed output dot | `absolute right-0 translate-x-1/2` (LGraphNode.vue L66) | 12px right |
| Slot hit area pseudo | `after:w-5/2` (SlotConnectionDot.vue L64) | ~36px beyond dot |
| Tailwind ring | `hover:ring-7` (box-shadow) | ~7px all sides |
**Not affected:** CSS `outline` (paints outside border-box per spec, not clipped by containment). `getBoundingClientRect()` also unaffected — slot tracking continues to work.
## Recommended Approach: Structural Refactor
Split the node container into two layers:
```
┌─ Outer Shell (.lg-node) ─────────────────────────┐
│ No containment │
│ Handles: transform, z-index, opacity, │
│ pointer-events, ring, outline │
│ Slot dots attached here → can overflow naturally │
│ │
│ ┌─ Inner Content Div ──────────────────────────┐ │
│ │ contain: style layout paint │ │
│ │ Handles: border, background, border-radius, │ │
│ │ flex layout │ │
│ │ Contains: header, body, widgets, footer │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
```
### Key decisions
1. **Outer shell** has no containment — slot dots overflow naturally
2. **Inner content div** gets `contain: style layout paint` — browser skips painting offscreen node interiors
3. **Ring/outline on outer shell** — selection indicators remain visible
4. Slot labels stay inside inner content; only the **dot elements** need to be on the outer layer
### Alternatives evaluated and rejected
| Approach | Why rejected |
|----------|-------------|
| Padding to contain dots within bounds | Changes all node layouts, 24px wider, breaks design language |
| `contain: paint` only on node body (not header) | Partial improvement, collapsed dots still clipped |
| `content-visibility: auto` | Transform-based panning keeps nodes "in-viewport" at DOM level — optimization becomes no-op |
| `overflow: clip` | Clips visually but doesn't provide browser containment optimization |
## Files to Change
- `src/renderer/extensions/vueNodes/components/LGraphNode.vue` — restructure template into outer shell + inner contained div
- Potentially `InputSlot.vue` / `OutputSlot.vue` — may need prop to suppress dot rendering if dots move to outer layer
- `packages/design-system/src/css/style.css` — `.lg-node` styles may need adjustment
## Risks
- **Visual regression** — node layout/rendering is design-sensitive
- **Extension compatibility** — extensions adding custom node content may be affected
- **Collapsed state** — completely different layout, needs separate verification
- **Test coverage** — component restructuring may affect existing tests
## Measurement Plan
1. Build the refactored template
2. Load 245-node workflow in Chrome DevTools Performance panel
3. Record pan/zoom session
4. Compare Layer time (target: <30ms, baseline: 65ms)
5. Verify in Firefox Profiler that style recalc count doesn't increase
6. Run `@perf` CI tests to check for regressions
## References
- Full investigation: `temp/plans/contain-paint-investigation.md`
- Backlog: `docs/perf/BACKLOG.md` #21
- rizumu's compositor PR: #9649
- CSS Containment spec: https://www.w3.org/TR/css-contain-2/#paint-containment
┆Issue is synchronized with this [Notion page](https://www.notion.so/Issue-9933-perf-Add-CSS-contain-paint-to-Vue-nodes-via-structural-refactor-3246d73d3650811991ffee6fdbccf22d) by [Unito](https://www.unito.io)
Contributor guide
Assessment
This issue has not been assessed yet.