Comfy-Org / Comfy-Org/ComfyUI_frontend

perf: Add CSS `contain: paint` to Vue nodes via structural refactor

Open
#9,933 0 comments 0 reactions 1 assignee Claimed by @LittleSound View on GitHub
area:nodes area:vue-migration perf:speed
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.