This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Mermaid.js Deep Dive: Hur det faktiskt fungerar och hur man utökar det
Sunday, 09 November 2025
Inleiding
NOTET: Det här är en del av mina experiment med AI ♫ / ♫ ett sätt att spendera ♫$1000 ♫ Kaludkod webbkrediter ♫ . ♫ Jag ♫' ♫ har matat in det här med en massa papper ♫ , ♫ mitt förståelse ♫ SMK6 ♫ frågor jag var tvungen att skapa den här artikeln ♫ MVK7 ♫ Den ♫ SKK8 ♫ är roligt och fyller ett hål som jag inte sett ♫ SVK9 ♫ nån annanstans fyllt ♫
Denna artikel bygger på tidigare artiklar: Om du inte redan har checkt ut Att lägga till sjöjungfru.js med htmx, Att byta tema för sjöjungfrun, och Att förbättra sjöjungfrudiagram med Pan/Zoom och export. Den här djupgående dykning förklarar insidan bakom implementeringarna
sjöjungfru.js är verkligen lysande . skriva enkel text , få vackra diagram M SK3 Ingen mer fumbling med Visio eller rita MSC4io MST5 förlora källdatet MSV6 eller underhålla separata bilddatet~ MST7 Allt lever i markerdown~ MSV8 version ~ MST9 ~ kontrollerad tillsammans med din kod ~ MSV10
Men jag ville veta hur den fungerar faktiskt under kapseln.
graph LR
A[Text] --> B[Magic?]
B --> C[Beautiful Diagram]
... blir en verklig SVG ? Och viktigare än så , hur kan du koppla in i det för att lägga till egenskaper som pann @höjning)?
Efter att ha undersökt Mermaids källkod och konstruerat riktiga förlängningar, har jag lärt mig allt om hur Mermaid fungerar inuti mig och hur man kan göra det bra.
Vad är sjöjungfru ?
Mermaid omvandlar textdefinitioner till diagram.
Den gamla vägen:
- Opena diagrammet
- Att skapa en diagram
- Exportera som PNG
- Embedra i dokument
- Bra att uppdatera? Hitta källdatet , redigera
The Mermaid way:
- Skryf diagram i text
- Klapp
Datera upp den ? Bara redigera texten
Diagram-typer
Seejungfru supporterar ett löjligt antal diagramtyper:
graph LR
A[Flowcharts] --> B[Sequence Diagrams]
B --> C[Class Diagrams]
C --> D[State Diagrams]
D --> E[ER Diagrams]
E --> F[Gantt Charts]
F --> G[Pie Charts]
G --> H[Git Graphs]
H --> I[User Journeys]
I --> J[And many more...]
Se sjöjungfrudokumenten för hela listan.
Hur sjöjungfrun faktiskt fungerar
Här är vad som händer när Seejungfrun visar ett diagram.
graph TD
A[Text Definition] --> B[Lexer/Tokenizer]
B --> C[Parser]
C --> D[AST Built]
D --> E[Diagram Type Detected]
E --> F[Type-Specific Renderer]
F --> G[SVG Generated]
G --> H[Inserted into DOM]
H --> I[Your Enhancements Run]
style A stroke:#059669,stroke-width:3px,color:#10b981
style D stroke:#2563eb,stroke-width:3px,color:#3b82f6
style G stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
style I stroke:#d97706,stroke-width:3px,color:#f59e0b
Låt oss bryta ner varje steg
steg 1: Textdefinition
Allt börjar med text.
// Flowchart
const diagram = `
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug time]
`;
steg 2: Lexikaanalys
Lexaren bryter texten till tokens.
A[Start] --> B{Decision}
blir tokens som:
[
{ type: 'NODE_ID', value: 'A' },
{ type: 'NODE_TEXT', value: 'Start' },
{ type: 'ARROW', value: '-->' },
{ type: 'NODE_ID', value: 'B' },
{ type: 'NODE_TEXT', value: 'Decision' },
{ type: 'NODE_SHAPE', value: 'diamond' } // from { }
]
steg 3: Parsing och AST generering
Parsaren tar ut token och bygger en abstrakt syntaxträde (AST):
// Simplified AST structure
{
type: 'flowchart',
direction: 'TD',
nodes: [
{ id: 'A', text: 'Start', shape: 'rect' },
{ id: 'B', text: 'Decision', shape: 'diamond' }
],
edges: [
{ from: 'A', to: 'B', type: 'arrow' }
]
}
Mermaid använder olika parser för varje diagramtyp. De genereras ofta från grammatikdateer som använder Jison (lik Yacc/Bison för JavaScriptM SK2
steg 4: Detektor av diagram
Seejungfrun upptäcker diagramtyp från första linjen:
// Simplified detection logic
if (text.match(/^\s*graph/)) return 'flowchart';
if (text.match(/^\s*sequenceDiagram/)) return 'sequence';
if (text.match(/^\s*classDiagram/)) return 'class';
// ... etc
steg 5: Typ- Specifisk Rendering
Varje diagramtyp har sin egen renderer. Rendern tar AST och genererar SVG-element
För flowcharts, Mermaid använder Dagre bibliotek för grafbelagning. För andra , den använder anpassade algoritmer eller bibliotek som Cytoscape
// Simplified flowchart renderer
export const draw = function (text, id, version, diagObj) {
const graph = diagObj.db; // The AST
const svg = d3.select(`#${id}`);
// Render nodes
graph.getVertices().forEach(vertex => {
drawNode(svg, vertex);
});
// Render edges
graph.getEdges().forEach(edge => {
drawEdge(svg, edge);
});
// Apply layout algorithm
dagre.layout(graph);
};
steg 6: SVG generering
Renderern producerar SVG markup:
<svg xmlns="http://www.w3.org/2000/svg">
<g class="node">
<rect x="0" y="0" width="100" height="50"/>
<text x="50" y="25">Start</text>
</g>
<g class="edge">
<path d="M 100 25 L 200 25" stroke="#333"/>
</g>
</svg>
steg 7: DOM Insertion
sjöjungfru hittar allt .mermaid element och ersätter dem med utformat SVG:
// From mermaid.ts
export const init = async function (config, nodes) {
const nodesToProcess = nodes || document.querySelectorAll('.mermaid');
for (const node of nodesToProcess) {
const id = `mermaid-${Date.now()}-${Math.random()}`;
const txt = node.textContent;
const { svg } = await render(id, txt);
node.innerHTML = svg;
}
};
steg 8: Efteråt-Processering (Var du kommer in
Efter att sjöjungfrun sätter in SVG, kan man förbättra den.
- Pan/zoomfunktionalitet
- Kontrollknappar
- Exportförmågan
- Theme-switchning
Mer om detta härunder.
Extending Mermaid: De utvidgningspunkterna
Nu när vi vet hur Seejungfrun fungerar, låt oss undersöka hur vi kan utöka den.
1. Konfiguration
Den mest grundläggande utlängningen är konfiguration:
import mermaid from 'mermaid';
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
securityLevel: 'loose',
flowchart: {
curve: 'basis',
padding: 15
}
});
2. Theme Customization
Jag rapporterade om detta i Att byta tema för sjöjungfrun, men här, ' är nyckelimplementationen
Problemet med Theme Switching
Seejungfrun måste initialiseras med ett tema, och du kan' inte ändra det efterM SK2 men om du vill återskapa diagrammet med ett nytt tema , behöver du diagrammets ursprungliga källaMSC5 vilken Seejungfrau doesn' lagrar inte i DOM.
Lösningen
Speichera den ursprungliga innehållet innan man renderar
// From my theme-switcher implementation
const originalData = new Map();
// Save original content before first render
const saveOriginalData = async () => {
const elements = document.querySelectorAll('.mermaid');
elements.forEach(element => {
const id = element.id || `mermaid-${Date.now()}`;
element.id = id;
// Store the original diagram source
if (!originalData.has(id)) {
originalData.set(id, element.textContent?.trim());
}
});
};
// When theme changes, restore and re-render
const loadMermaid = async (theme) => {
mermaid.initialize({
startOnLoad: false,
theme: theme
});
const elements = document.querySelectorAll('.mermaid');
for (const element of elements) {
const source = originalData.get(element.id);
if (source) {
element.innerHTML = ''; // Clear
element.removeAttribute('data-processed');
const { svg } = await mermaid.render(
`mermaid-svg-${element.id}`,
source
);
element.innerHTML = svg;
}
}
};
Flera temadetektionsmetoder (sidor hanterar tema annorlunda):
function detectTheme() {
// Check various sources
if (typeof window.__themeState !== 'undefined') {
return window.__themeState;
}
if (localStorage.theme) {
return localStorage.theme;
}
if (document.documentElement.classList.contains('dark')) {
return 'dark';
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
Se fulla temamönsterkoden för detaljer.
3. Post-Render förhöjningar
Här händer den verkliga magin.
Jag rapporterade om detta i Att förbättra sjöjungfrudiagram med Pan/Zoom och export, så jag ' kommer att poängtera de viktigaste teknikerna här
Att skriva diagram
Att skapa en wrappercontainer för kontrollen:
function wrapDiagram(element) {
if (element.closest('.mermaid-wrapper')) {
return element.closest('.mermaid-wrapper');
}
const wrapper = document.createElement('div');
wrapper.className = 'mermaid-wrapper';
wrapper.id = `wrapper-${element.id}`;
element.parentNode.insertBefore(wrapper, element);
wrapper.appendChild(element);
return wrapper;
}
Att lägga till Pan/Zoom
Att använda svg-pan-zoom:
import svgPanZoom from 'svg-pan-zoom';
const panZoomInstances = new Map();
function initPanZoom(svgElement, diagramId) {
// Clean up existing instance
if (panZoomInstances.has(diagramId)) {
panZoomInstances.get(diagramId).destroy();
panZoomInstances.delete(diagramId);
}
const instance = svgPanZoom(svgElement, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: true,
center: true,
minZoom: 0.1,
maxZoom: 10
});
panZoomInstances.set(diagramId, instance);
return instance;
}
Kontrollknappar
Create floating control panel:
function createControlButtons(container, diagramId) {
const controlsDiv = document.createElement('div');
controlsDiv.className = 'mermaid-controls';
const buttons = [
{ icon: 'bx-fullscreen', title: 'Fullscreen', action: 'fullscreen' },
{ icon: 'bx-zoom-in', title: 'Zoom In', action: 'zoomIn' },
{ icon: 'bx-zoom-out', title: 'Zoom Out', action: 'zoomOut' },
{ icon: 'bx-reset', title: 'Reset', action: 'reset' },
{ icon: 'bx-move', title: 'Pan', action: 'pan' },
{ icon: 'bx-image', title: 'Export PNG', action: 'exportPng' },
{ icon: 'bx-code-alt', title: 'Export SVG', action: 'exportSvg' }
];
buttons.forEach(btn => {
const button = document.createElement('button');
button.className = `mermaid-control-btn bx ${btn.icon}`;
button.setAttribute('data-action', btn.action);
button.setAttribute('data-diagram-id', diagramId);
controlsDiv.appendChild(button);
});
container.appendChild(controlsDiv);
}
Eventdelegation (presterande!)
Att inte ansluta lyssnare till varje knapp
document.addEventListener('click', (e) => {
const target = e.target;
if (!target.classList.contains('mermaid-control-btn')) return;
const action = target.getAttribute('data-action');
const diagramId = target.getAttribute('data-diagram-id');
const panZoom = panZoomInstances.get(diagramId);
switch (action) {
case 'zoomIn': panZoom?.zoomIn(); break;
case 'zoomOut': panZoom?.zoomOut(); break;
case 'reset': panZoom?.reset(); break;
// ... etc
}
});
Exporta Funktionalitet
Utmaningen SVG-element har en dynamisk storleksmängd, platt / zoomtransformationer, och arvaktiga stiler
- Klona SVG
- Bevara dimensioner
- Remover transformer
- Konvertera till PNG eller SVG
Att använda html-till-bild:
import { toPng, toSvg } from 'html-to-image';
async function exportDiagram(container, format, diagramId) {
const svgElement = container.querySelector('svg');
if (!svgElement) return;
// Clone to avoid modifying original
const clonedSvg = svgElement.cloneNode(true);
// Get or calculate viewBox
let viewBox = clonedSvg.getAttribute('viewBox');
if (!viewBox) {
const bbox = svgElement.getBBox();
viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
clonedSvg.setAttribute('viewBox', viewBox);
}
// Set explicit dimensions
const [, , width, height] = viewBox.split(' ').map(Number);
clonedSvg.setAttribute('width', width);
clonedSvg.setAttribute('height', height);
// Remove pan-zoom transforms
clonedSvg.removeAttribute('style');
// Create off-screen container
const temp = document.createElement('div');
temp.style.position = 'absolute';
temp.style.left = '-9999px';
temp.appendChild(clonedSvg);
document.body.appendChild(temp);
// Export
const dataUrl = format === 'png'
? await toPng(clonedSvg, { pixelRatio: 2 })
: await toSvg(clonedSvg);
downloadFile(dataUrl, `diagram-${Date.now()}.${format}`);
// Cleanup
document.body.removeChild(temp);
}
Kritiska detaljer:
- Bevara synen - Ta upp hela diagrammet , inte bara den synliga delen
- Off-skärmdumpning - undvika att påverka diagrammet som visas
- pixelRatio: M SK1 - Högt-DPI för skarpa PNG-exporter
Se full exportkod för mer information.
Den kompletta förstärkningspaketet
Jag paketerade alla dessa förbättringar som @höjning. Se Publishing Mermaid Enhancements som ett npm-paket för mer information.
Här är hur allt passar ihop.
graph TB
A[User Initializes] --> B[init Function]
B --> C[initMermaid]
B --> D[enhanceMermaidDiagrams]
C --> E[Theme Detection]
C --> F[Event Listeners]
C --> G[Mermaid Rendering]
E --> E1[Global State]
E --> E2[LocalStorage]
E --> E3[DOM Class]
E --> E4[OS Preference]
F --> F1[Custom Events]
F --> F2[Media Query]
G --> H[Apply Enhancements]
D --> H
H --> I[Wrap Diagrams]
H --> J[Init Pan/Zoom]
H --> K[Add Controls]
I --> L[Interactive Diagram]
J --> L
K --> L
L --> M[User Interactions]
M --> M1[Zoom In/Out]
M --> M2[Pan]
M --> M3[Fullscreen]
M --> M4[Export PNG/SVG]
style A stroke:#059669,stroke-width:3px,color:#10b981
style L stroke:#2563eb,stroke-width:3px,color:#3b82f6
style M stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
Användning
npm install @mostlylucid/mermaid-enhancements
import { init } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';
await init();
Det är ', det är det , "!, dina sjöjungfru-diagram har nu:".
- ✅ Interaktiv panM SK1zoom
- ✅ Fullscreen-lampa
- ✅ PNG/SVG export
- ✅ Automatiskt Theme-Switching
- ✅ Reaktionsdesign
Integrering med HTMX
Som jag täckte i Att lägga till sjöjungfru.js med htmx, du behöver göra - att initialisera Mermaid efter HTMX innehållssvapning
// On page load
document.addEventListener('DOMContentLoaded', function () {
mermaid.initialize({ startOnLoad: true });
});
// After HTMX swaps content
document.body.addEventListener('htmx:afterSwap', function(evt) {
mermaid.run();
});
Med förbättringspaketet:
import { init, enhanceMermaidDiagrams } from '@mostlylucid/mermaid-enhancements';
// Initial load
await init();
// After HTMX swap
document.body.addEventListener('htmx:afterSwap', async function() {
await init(); // Re-init Mermaid with current theme
enhanceMermaidDiagrams(); // Re-apply enhancements
});
Beste metoder jag lärde mig
Efter att ha byggt den här saken och tagit bort konstiga randkoster
1. Säg alltid den ursprungliga innehållet
Seejungfrun bevarar inte originaldiagrammets källa efter att ha ställt ut det.
const originalData = new Map();
// Before first render
element.setAttribute('data-original-code', element.textContent);
originalData.set(element.id, element.textContent);
// When re-rendering
element.innerHTML = originalData.get(element.id);
2. rengör upp instanserna
Memory lecks are real. Destroy instances before creating new ones:
if (panZoomInstances.has(id)) {
try {
panZoomInstances.get(id).destroy();
} catch (e) {
console.warn('Failed to destroy:', e);
}
panZoomInstances.delete(id);
}
3. Använd händelsedelegation
Don'koppel inte lyssnare till individuella knappar
// ❌ Don't do this
buttons.forEach(btn => {
btn.addEventListener('click', handler);
});
// ✅ Do this
document.addEventListener('click', (e) => {
if (e.target.matches('.mermaid-control-btn')) {
handleClick(e.target);
}
});
4. Handle Cloudflare Rocket Loader
Rocket Loader skjuter upp JavaScripts Ausführung. Vänta till beroenden:
function waitForDependencies(maxAttempts = 50) {
return new Promise((resolve) => {
let attempts = 0;
const check = () => {
if (window.mermaid && window.htmx && window.Alpine) {
resolve();
} else if (attempts >= maxAttempts) {
resolve(); // Give up
} else {
attempts++;
setTimeout(check, Math.min(50 * Math.pow(1.2, attempts), 500));
}
};
check();
});
}
Och utelämna din huvudskript från Rocket Loader:
<script src="main.js" data-cfasync="false"></script>
5. Timing är allt
Använda requestAnimationFrame för bättre tid än godtyckligt setTimeout:
// After Mermaid renders
await mermaid.run();
// Wait for paint before enhancing
await new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
enhanceMermaidDiagrams();
resolve();
});
});
});
6. Bevakning av SVG
SVGs kan vara konstiga. Kontrollera alltid
const svgElement = container.querySelector('svg');
if (!svgElement) {
console.warn('No SVG found');
return;
}
// Clone before modifying
const cloned = svgElement.cloneNode(true);
// Ensure viewBox exists
let viewBox = cloned.getAttribute('viewBox');
if (!viewBox) {
const bbox = svgElement.getBBox();
viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
}
Debugstips
Konsolens utgång
Med korrekt initialisering, borde du se
Saving original data
Loading mermaid with theme: dark
Mermaid initialized
Enhanced 3 diagrams
Testing Checklist
Efter implementering av förbättringar:
- Diagram renderar vid sidans lading
- PanM SK1zoom-kontroller fungerar
- Hela skärmen öppnas / stängs (X, klicka utanför
- PNG-export fångar hela diagrammet
- SVG-export bevarar vektorer
- Fungerar efter HTMX innehållssvapning
- Theme-switching re- löser korrekt
- Mobilreagerande
- Dark mode styling
- Tappbord tillgänglighet
Samma frågor
Diagramm inte ger resultat:
- Kontrollera webbläsarens konsol för fel
- Verifiera sjöjungfrun ladd (
window.mermaid) - Se diagrammets syntax
Pan/zoom inte fungerar:
- Verifiera svg-pan-zoom initialiserad
- Kolla för motsägelsefulla CSS (
pointer-events: none) - Inspektera instanskartan
Export fångar bara hörn:
- saknade viewBox bevarande
- De omvandlade inte togs bort
- Kontrollera kloninglogiken
Theme inte ändras:
- Den ursprungliga datan är inte sparad
data-processedinte återställa- Event lyssnare är inte registrerade
Tillvägagångspunkter
Lazy Initialization
Don' inte förstärka last tills det behövs :
let enhancementsLoaded = false;
async function loadEnhancements() {
if (enhancementsLoaded) return;
const { enhanceMermaidDiagrams } = await import('./enhancements.js');
enhanceMermaidDiagrams();
enhancementsLoaded = true;
}
// Load on first interaction
document.addEventListener('click', (e) => {
if (e.target.closest('.mermaid')) {
loadEnhancements();
}
}, { once: true });
Intersektionsobservatör
Rendera diagram bara när de är synliga:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
renderDiagram(entry.target);
observer.unobserve(entry.target);
}
});
}, { rootMargin: '100px' });
document.querySelectorAll('.mermaid').forEach(el => {
observer.observe(el);
});
Debounce Re- långivare
På resizing eller temaförändring:
let timeout;
window.addEventListener('resize', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
panZoomInstances.forEach(instance => {
instance.resize();
instance.fit();
});
}, 250);
});
I slutsats
Mermaid.js är fantastisk ur hela lådan , men att förstå hur det fungerar internt låter dig bygga några riktigt häftiga förbättringar
- Rendering av Pipeline: Text | → | Lexer | → | Parser | МSK3 | AST | M→ | Renderer |M→ | SVG
- Längsvinkel: Bestämde , Themes , post
- Speichera ursprungliga data: Seejungfrun gör det inte för dig
- Skolla upp resurser: minnesleket är verkligt
- Eventdelegation: bättre resultat
- Flera temakällor: webbplatser hanterar tema på olika sätt
- Timing spelar roll: Använd
requestAnimationFrame
Alla tekniker som jag har omfattat här används i produktionen på den här platsen och är paketerade @höjning. Den kompletta källan är tillgänglig på mestlucidweb/mostlylucid-djungfru.
Related Posts
- Att lägga till sjöjungfru.js med htmx
- Att byta tema för sjöjungfrun
- Att förbättra sjöjungfrudiagram med Pan/Zoom och export
- Publishing Mermaid Enhancements som ett npm-paket
resurser
Försök kontrollen på diagrammen ovan.