Skip to content

Commit f9812b9

Browse files
committed
fix(mermaid): use CSS zoom instead of transform for scroll-safe zooming
Replace transform: scale() with CSS zoom property for Mermaid diagram zoom controls. Transform only changes visual appearance while the layout box stays fixed, causing content that expands upward/leftward to go into unscrollable negative space. CSS zoom changes actual layout size so overflow scrolls normally in all directions. - Change align-items: flex-start to center for vertical centering - Add min-height: 400px to prevent diagram compression - Add initial zoom: 1.4 for better default readability - Remove unnecessary mermaid-inner wrapper - Update INITIAL_ZOOM constant in JavaScript - Update Scaling Small Diagrams section to use zoom - Remove dead code (transition on non-animatable property)
1 parent a9e3994 commit f9812b9

File tree

2 files changed

+53
-34
lines changed

2 files changed

+53
-34
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## [0.4.3] - 2026-03-01
4+
5+
### Mermaid Zoom and Positioning Fixes
6+
- **Fixed zoom clipping**: Replaced `transform: scale()` with CSS `zoom` property. Transform only changes visual appearance — content expanding upward/leftward goes into negative space which can't be scrolled to. Zoom changes actual layout size, so overflow scrolls normally in all directions.
7+
- **Fixed vertical centering**: Changed `align-items: flex-start` to `align-items: center` so diagrams are centered both horizontally and vertically in their container.
8+
- **Added initial zoom**: Complex diagrams can start at zoom > 1 (e.g., 1.4x) for better readability while keeping zoom controls functional.
9+
- **Added min-height**: Containers now have `min-height: 400px` to prevent vertical flowcharts from compressing into unreadable thumbnails.
10+
- Removed unnecessary `.mermaid-inner` wrapper — no longer needed with zoom-based approach.
11+
- Updated JavaScript to use `INITIAL_ZOOM` constant for consistent reset behavior.
12+
- Updated "Scaling Small Diagrams" section to use `zoom` instead of `transform: scale()` for consistency.
13+
314
## [0.4.2] - 2026-03-01
415

516
### Link Styling

references/css-patterns.md

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -438,11 +438,10 @@ mermaid.initialize({
438438
});
439439
```
440440

441-
**2. CSS scale transform** for diagrams that still render too small:
441+
**2. CSS zoom** for diagrams that still render too small:
442442
```css
443-
.mermaid-wrap--scaled .mermaid svg {
444-
transform: scale(1.3);
445-
transform-origin: center top;
443+
.mermaid-wrap--scaled .mermaid {
444+
zoom: 1.3;
446445
}
447446
```
448447

@@ -454,7 +453,7 @@ mermaid.initialize({
454453
}
455454
```
456455

457-
**Rule of thumb:** If the diagram has 10+ nodes or the text is smaller than 12px rendered, increase fontSize to 18-20px or apply a scale transform.
456+
**Rule of thumb:** If the diagram has 10+ nodes or the text is smaller than 12px rendered, increase fontSize to 18-20px or apply CSS zoom.
458457

459458
### Zoom Controls
460459

@@ -472,10 +471,12 @@ Add zoom controls to every `.mermaid-wrap` container for complex diagrams.
472471
border-radius: 12px;
473472
padding: 32px 24px;
474473
overflow: auto;
475-
/* CRITICAL: center the diagram */
474+
/* CRITICAL: center the diagram both horizontally and vertically */
476475
display: flex;
477476
justify-content: center;
478-
align-items: flex-start;
477+
align-items: center;
478+
/* Prevent vertical flowcharts from compressing into unreadable thumbnails */
479+
min-height: 400px;
479480
scrollbar-width: thin;
480481
scrollbar-color: var(--border) transparent;
481482
}
@@ -484,9 +485,22 @@ Add zoom controls to every `.mermaid-wrap` container for complex diagrams.
484485
.mermaid-wrap::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
485486
.mermaid-wrap::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
486487

488+
/* For shorter diagrams that don't need the full height */
489+
.mermaid-wrap--compact { min-height: 200px; }
490+
491+
/* For very tall vertical flowcharts */
492+
.mermaid-wrap--tall { min-height: 600px; }
493+
487494
.mermaid-wrap .mermaid {
488-
transition: transform 0.2s ease;
489-
transform-origin: center center;
495+
/* Use CSS zoom instead of transform: scale().
496+
Zoom changes actual layout size, so overflow scrolls normally in all directions.
497+
Transform only changes visual appearance — content expanding upward/leftward
498+
goes into negative space which can't be scrolled to.
499+
Supported in all browsers (Firefox added support in v126, June 2024).
500+
Note: zoom is not animatable, so no transition. */
501+
/* Optional: start at >1 for complex diagrams that render too small.
502+
The diagram stays centered, renders larger, and zoom controls still work. */
503+
zoom: 1.4;
490504
}
491505

492506
.zoom-controls {
@@ -523,14 +537,16 @@ Add zoom controls to every `.mermaid-wrap` container for complex diagrams.
523537
color: var(--text);
524538
}
525539

526-
.mermaid-wrap.is-zoomed { cursor: grab; }
540+
.mermaid-wrap { cursor: grab; }
527541
.mermaid-wrap.is-panning { cursor: grabbing; user-select: none; }
528-
529-
@media (prefers-reduced-motion: reduce) {
530-
.mermaid-wrap .mermaid { transition: none; }
531-
}
532542
```
533543

544+
**Why zoom instead of transform?**
545+
546+
CSS `transform: scale()` only changes visual appearance — the element's layout box stays the same size. When you scale from `center center`, content expands upward and leftward into negative coordinate space. Scroll containers can't scroll to negative positions, so the top and left of the zoomed content get clipped.
547+
548+
CSS `zoom` actually changes the element's layout size. The content grows downward and rightward like any other growing element, staying fully scrollable.
549+
534550
### HTML
535551

536552
```html
@@ -552,28 +568,23 @@ Add zoom controls to every `.mermaid-wrap` container for complex diagrams.
552568
Add once at the end of the page. Handles button clicks and scroll-to-zoom on all `.mermaid-wrap` containers:
553569

554570
```javascript
555-
function updateZoomState(wrap) {
556-
var target = wrap.querySelector('.mermaid');
557-
var zoom = parseFloat(target.dataset.zoom || '1');
558-
wrap.classList.toggle('is-zoomed', zoom > 1);
559-
}
571+
// Match this to the CSS zoom value (or 1 if not set)
572+
var INITIAL_ZOOM = 1.4;
560573

561574
function zoomDiagram(btn, factor) {
562575
var wrap = btn.closest('.mermaid-wrap');
563576
var target = wrap.querySelector('.mermaid');
564-
var current = parseFloat(target.dataset.zoom || '1');
565-
var next = Math.min(Math.max(current * factor, 0.3), 5);
577+
var current = parseFloat(target.dataset.zoom || INITIAL_ZOOM);
578+
var next = Math.min(Math.max(current * factor, 0.5), 5);
566579
target.dataset.zoom = next;
567-
target.style.transform = 'scale(' + next + ')';
568-
updateZoomState(wrap);
580+
target.style.zoom = next;
569581
}
570582

571583
function resetZoom(btn) {
572584
var wrap = btn.closest('.mermaid-wrap');
573585
var target = wrap.querySelector('.mermaid');
574-
target.dataset.zoom = '1';
575-
target.style.transform = 'scale(1)';
576-
updateZoomState(wrap);
586+
target.dataset.zoom = INITIAL_ZOOM;
587+
target.style.zoom = INITIAL_ZOOM;
577588
}
578589

579590
document.querySelectorAll('.mermaid-wrap').forEach(function(wrap) {
@@ -582,20 +593,17 @@ document.querySelectorAll('.mermaid-wrap').forEach(function(wrap) {
582593
if (!e.ctrlKey && !e.metaKey) return;
583594
e.preventDefault();
584595
var target = wrap.querySelector('.mermaid');
585-
var current = parseFloat(target.dataset.zoom || '1');
596+
var current = parseFloat(target.dataset.zoom || INITIAL_ZOOM);
586597
var factor = e.deltaY < 0 ? 1.1 : 0.9;
587-
var next = Math.min(Math.max(current * factor, 0.3), 5);
598+
var next = Math.min(Math.max(current * factor, 0.5), 5);
588599
target.dataset.zoom = next;
589-
target.style.transform = 'scale(' + next + ')';
590-
updateZoomState(wrap);
600+
target.style.zoom = next;
591601
}, { passive: false });
592602

593-
// Click-and-drag to pan when zoomed
603+
// Click-and-drag to pan
594604
var startX, startY, scrollL, scrollT;
595605
wrap.addEventListener('mousedown', function(e) {
596606
if (e.target.closest('.zoom-controls')) return;
597-
var target = wrap.querySelector('.mermaid');
598-
if (parseFloat(target.dataset.zoom || '1') <= 1) return;
599607
wrap.classList.add('is-panning');
600608
startX = e.clientX;
601609
startY = e.clientY;
@@ -613,7 +621,7 @@ document.querySelectorAll('.mermaid-wrap').forEach(function(wrap) {
613621
});
614622
```
615623

616-
Scroll-to-zoom requires Ctrl/Cmd+scroll to avoid hijacking normal page scroll. Click-and-drag panning activates only when zoomed in (zoom > 1). Cursor changes to `grab`/`grabbing` to signal the behavior. The zoom range is capped at 0.3x–5x.
624+
Scroll-to-zoom requires Ctrl/Cmd+scroll to avoid hijacking normal page scroll. Cursor changes to `grab`/`grabbing` to signal pan mode. The zoom range is capped at 0.5x–5x.
617625

618626
## Grid Layouts
619627

0 commit comments

Comments
 (0)