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
Sunday, 09 November 2025
HUOMAUTUS: Tämä on osa kokeilujani tekoälyllä / tapa käyttää 1000 dollaria Calude Code Web -arvosanoja. Olen syöttänyt tälle BUNCH-lehden, ymmärrykseni, kysymyksiä, joita jouduin tekemään tämän artikkelin. Se on hauska ja täyttää aukon, jota en ole nähnyt paikattavan missään muualla.
Tämä viesti perustuu aiempiin artikkeleihin: Jos et ole jo ehtinyt, katso Lisää merenneito.js kanssa htmx, Teemojen vaihtaminen merenneidolle, ja Merenneitokaavioiden parantaminen Pan/Zoomin ja Exportin avullaTämä syväsukellus selittää toteutusten taustalla olevat sisätilat.
Mermaid.js on aidosti nerokas. Kirjoita yksinkertaista tekstiä, hanki kauniita kaavioita. Älä enää hapuile Vision tai Draw.io:n kanssa, menetä lähdetiedostoja tai säilytä erillisiä kuvatiedostoja. Kaikki elää markdownissa, versioiden hallinnassa koodisi rinnalla.
Mutta halusin tietää miten Se oikeasti toimii konepellin alla.
graph LR
A[Text] --> B[Magic?]
B --> C[Beautiful Diagram]
Ja mikä tärkeintä, miten siihen voi kytkeä lisäominaisuuksia, kuten pan/zoom, teemanvaihto ja vientitoiminto, jonka rakensin tälle sivustolle (nyt saatavilla nimellä @mostlylucid/mermaid-enhancements)?
Merenneidon lähdekoodin penkomisen ja todellisten laajennusten rakentamisen jälkeen tässä kaikki, mitä opin siitä, miten merenneito toimii sisäisesti ja miten sitä voidaan laajentaa kunnolla.
Merenneito muuttaa tekstin määritelmät kaavioiksi.
Vanha tapa:
Merenneidon tapa:
Se on vain teksti, joka toimii markdownissa.
Merenneito tukee naurettavaa määrää kaaviotyyppejä:
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...]
Katso Merenneidon dokumentit koko listaa varten.
Näin käy, kun merenneito tekee kaavion:
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
Murretaan jokainen askel.
Kaikki alkaa tekstistä. Merenneidon DSL:ään (aluekohtainen kieli) kirjoitetaan kaavioita:
// Flowchart
const diagram = `
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug time]
`;
Lexer jakaa tekstin poletteihin. Esimerkiksi tämä rivi:
A[Start] --> B{Decision}
Tulee kuponkeja, kuten:
[
{ 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 { }
]
Jäsentäjä kuluttaa kuponkeja ja rakentaa Abstract Syntax Treen (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' }
]
}
Merenneito käyttää eri jäsennyksiä jokaiselle kaaviotyypille. Ne syntyvät usein kielioppitiedostoista, joissa käytetään Jison (kuten Yacc/Bison for JavaScript).
Merenneito havaitsee kaaviotyypin ensimmäiseltä riviltä:
// 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
Jokaisessa kaaviotyypissä on oma renderaattorinsa. Renderaattori ottaa AST-järjestelmän ja tuottaa SVG-elementtejä.
Merenneito käyttää vuokaavioissa Dagre Muille se käyttää mukautettuja algoritmeja tai kirjastoja, kuten Cytoscapea.
// 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);
};
Renderaattori tuottaa SVG-leiman:
<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>
Merenneito löytää kaiken .mermaid elementit ja korvaa ne renderoidulla SVG:llä:
// 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;
}
};
Kun merenneito on laittanut SVG:n, voit parantaa sitä. Tässä kaikki lisälaitteeni koukuttavat:
Lisää aiheesta alla.
Nyt kun tiedämme, miten merenneito toimii, tutkitaan, miten sitä laajennetaan.
Peruslaajennus on konfiguraatio:
import mermaid from 'mermaid';
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
securityLevel: 'loose',
flowchart: {
curve: 'basis',
padding: 15
}
});
Käsittelin tätä laajasti Teemojen vaihtaminen merenneidolle, mutta tässä on tärkein toteutus:
Merenneito pitää alustaa teemalla, etkä voi muuttaa sitä sen jälkeen. Mutta jos haluat renderoida kaavioita uudella teemalla, tarvitset alkuperäisen kaavion lähteen, joka merenneito ei varastoidu DOM:iin.
Tallenna alkuperäinen sisältö ennen renderointia, palauta ja renderöi teemoja vaihdettaessa:
// 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;
}
}
};
Useita teemantunnistusmenetelmiä (kohteet käsittelevät teemoja eri tavalla):
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';
}
Katso koko teemavaihtajan koodi Tarkempia tietoja.
Täällä todellinen taikuus tapahtuu. Merenneidon renderoinnin jälkeen voit lisätä vuorovaikutteisia ominaisuuksia.
Käsittelin tätä laajasti Merenneitokaavioiden parantaminen Pan/Zoomin ja Exportin avullaKorostan tässä avaintekniikoita.
Luo kääresäiliö hallintalaitteille:
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;
}
Käyttäminen 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;
}
Luo kelluva ohjauspaneeli:
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);
}
Älä liitä kuuntelijoita jokaiseen painikkeeseen. Käytä tapahtuman delegointia:
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
}
});
Haaste: SVG-elementeissä on dynaamista kokoa, pan/zoom-muunnoksia ja perittyjä tyylejä. Viedäksesi oikein, sinun täytyy:
Käyttäminen html-to-image:
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);
}
Kriittiset tiedot:
Katso koko vientikoodi Tarkempia tietoja.
Pakkasin kaikki nämä parannukset @mostlylucid/mermaid-enhancements. Katso Merenneidon parannusten julkaiseminen npm-pakettina täydelliset tiedot.
Näin kaikki sopii yhteen:
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
npm install @mostlylucid/mermaid-enhancements
import { init } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';
await init();
Merenneitokaavioissasi on nyt:
Kuten peitin Lisää merenneito.js kanssa htmx, Merenneito pitää käynnistää uudelleen HTMX-sisältöjen swapien jälkeen:
// On page load
document.addEventListener('DOMContentLoaded', function () {
mermaid.initialize({ startOnLoad: true });
});
// After HTMX swaps content
document.body.addEventListener('htmx:afterSwap', function(evt) {
mermaid.run();
});
Parannuspaketti:
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
});
Rakennettuaan nämä jutut ja vianjäljiteltyään oudot laatikot, tämä toimii näin:
Merenneito ei säilytä alkuperäistä kaaviolähdettä renderoinnin jälkeen. Sinun on tallennettava se itse:
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);
Muistivuodot ovat todellisia. Tuhoa tapaukset ennen uusien luomista:
if (panZoomInstances.has(id)) {
try {
panZoomInstances.get(id).destroy();
} catch (e) {
console.warn('Failed to destroy:', e);
}
panZoomInstances.delete(id);
}
Älä liitä kuuntelijoita yksittäisiin painikkeisiin:
// ❌ 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);
}
});
Rocket Loader viivästyttää JavaScript-toteutusta. Odota riippuvuuksia:
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();
});
}
Ja jätä pääkirjoituksesi pois Rocket Loaderista:
<script src="main.js" data-cfasync="false"></script>
Käyttö requestAnimationFrame paremman ajoituksen kuin mielivaltaisen setTimeout:
// After Mermaid renders
await mermaid.run();
// Wait for paint before enhancing
await new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
enhanceMermaidDiagrams();
resolve();
});
});
});
SVG:t voivat olla outoja.
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}`;
}
Oikealla alustamisella näet:
Saving original data
Loading mermaid with theme: dark
Mermaid initialized
Enhanced 3 diagrams
Parannusten toteuttamisen jälkeen:
Kaavio ei renderöi:
window.mermaid)Pan/zoom ei toimi:
pointer-events: none)Vie vain nurkkaan:
Teema ei muutu:
data-processed ei nollaustaÄlä lataa parannuksia ennen kuin se on tarpeen:
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 });
Renderointikaaviot vain näkyvillä:
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);
});
Muutoksesta tai teemamuutoksesta:
let timeout;
window.addEventListener('resize', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
panZoomInstances.forEach(instance => {
instance.resize();
instance.fit();
});
}, 250);
});
Merenneito.js on fantastinen ulos laatikosta, mutta sen sisäisen toiminnan ymmärtäminen antaa mahdollisuuden rakentaa todella hienoja parannuksia. Tärkeimmät oivallukset:
requestAnimationFrameKaikki käyttämäni tekniikat käytetään tämän sivuston tuotannossa ja paketoidaan @mostlylucid/mermaid-enhancements. Täydellinen lähde on saatavilla osoitteessa Lähinnä lusid-verkkoa/lähinlylusid-merineitoa.
Kokeile yllä olevien kaavioiden ohjaimia!
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.