Back to "Mermaid.js Deep Dive: Hur det faktiskt fungerar och hur man utökar det"

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

Diagrams JavaScript Mermaid SVG

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:

  1. Opena diagrammet
  2. Att skapa en diagram
  3. Exportera som PNG
  4. Embedra i dokument
  5. Bra att uppdatera? Hitta källdatet , redigera

The Mermaid way:

  1. Skryf diagram i text
  2. 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

  1. Klona SVG
  2. Bevara dimensioner
  3. Remover transformer
  4. 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-processed inte å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

  1. Rendering av Pipeline: Text | → | Lexer | → | Parser | МSK3 | AST | M→ | Renderer |M→ | SVG
  2. Längsvinkel: Bestämde , Themes , post
  3. Speichera ursprungliga data: Seejungfrun gör det inte för dig
  4. Skolla upp resurser: minnesleket är verkligt
  5. Eventdelegation: bättre resultat
  6. Flera temakällor: webbplatser hanterar tema på olika sätt
  7. 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.

resurser

Försök kontrollen på diagrammen ovan.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.