Skip to content

Fix broken diagrams in hidden containers (Reveal.js, tabs)#227

Open
drillan wants to merge 2 commits intomgaitan:masterfrom
drillan:fix/lazy-render-hidden-elements
Open

Fix broken diagrams in hidden containers (Reveal.js, tabs)#227
drillan wants to merge 2 commits intomgaitan:masterfrom
drillan:fix/lazy-render-hidden-elements

Conversation

@drillan
Copy link

@drillan drillan commented Feb 16, 2026

Summary

  • Use IntersectionObserver to defer rendering of hidden Mermaid elements until they become visible
  • Fixes broken SVGs caused by Mermaid's layout engine failing to measure text dimensions in display: none containers (e.g., Reveal.js non-active slides, sphinx-design unopened tabs)
  • Visible elements render immediately as before; no behavior change for standard pages

Problem

When mermaid_output_format = "raw", mermaid.run() processes all .mermaid elements at page load. For elements hidden by a parent (display: none), the browser returns zero for all dimension measurements. This causes Mermaid's layout engine (dagre/ELK) to produce broken SVGs with near-zero viewBox dimensions, or throw "Could not find a suitable point for the distance" errors that manifest as "Syntax error in text".

Affected contexts:

Approach

In the runMermaid() function within default.js.j2:

  1. Visibility check: Split .mermaid elements into visible (offsetParent !== null || getClientRects().length > 0) and hidden
  2. Immediate render: Call mermaid.run({ nodes: visible }) for visible elements only
  3. Deferred render: Set up IntersectionObserver for hidden elements, rendering each when it becomes visible
  4. Serialized execution: Use a promise chain to prevent concurrent mermaid.run() calls when multiple hidden elements become visible simultaneously
  5. Completion check: Exclude deferred/failed elements from the existing processing-completion polling loop to prevent infinite retries

Test plan

  • All 17 existing tests pass (16 original + 1 new)
  • New test_lazy_rendering_code_present verifies IntersectionObserver and deferred rendering code is emitted
  • Manual verification with Reveal.js slides containing Mermaid diagrams
  • Manual verification with sphinx-design tab-set containing Mermaid diagrams

Fixes #126

🤖 Generated with Claude Code

drillan and others added 2 commits February 16, 2026 22:23
Mermaid's layout engine measures text dimensions via the DOM, which
fails for elements hidden by a parent (e.g., Reveal.js non-active
slides, sphinx-design unopened tabs). The layout calculation produces
broken SVGs with near-zero viewBox dimensions.

Use IntersectionObserver to defer rendering of hidden elements until
they become visible. Visible elements render immediately as before.

Fixes mgaitan#126

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace forEach+async with for...of and promise chain to serialize
  concurrent mermaid.run() calls from IntersectionObserver
- Move data-mermaid-deferred removal to after successful render to
  prevent orphaned elements causing infinite polling
- Add data-mermaid-render-failed attribute for failed elements and
  exclude them from completion checks
- Add try/catch for visible element rendering
- Warn explicitly when IntersectionObserver is unavailable
- Add test for lazy rendering code presence

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Diagram not rendering if it's in an unopened tab from sphinx-design's tab-set

1 participant