ΣΗΜΕΙΩΣΗ: Αυτό είναι μέρος των πειραμάτων μου με AI / ένας τρόπος για να περάσετε $ 1000 Calude Code Web πιστώσεις. Έχω ταΐσει αυτό ένα BUNCH των εγγράφων, την κατανόησή μου, ερωτήσεις που έπρεπε να δημιουργήσω αυτό το άρθρο. Είναι διασκεδαστικό και γεμίζει ένα κενό που δεν έχω δει πουθενά αλλού.
Αυτή η δημοσίευση βασίζεται σε προηγούμενα άρθρα: Αν δεν έχετε ήδη, ελέγξτε έξω Προσθήκη γοργόνας.js με htmx, Αλλαγή θεμάτων για τη Γοργόνα, και Ενίσχυση Διαγραμμάτων Γοργόνας με Pan/Zoom και ΕξαγωγήΑυτή η βαθιά κατάδυση εξηγεί τα εσωτερικά πίσω από αυτές τις εφαρμογές.
Mermaid.js είναι πραγματικά λαμπρό. Γράψτε απλό κείμενο, να πάρετε όμορφα διαγράμματα. Τέρμα οι αστοχίες με Visio ή κλήρωση.io, χάνοντας αρχεία πηγή, ή τη διατήρηση ξεχωριστών αρχείων εικόνας.
Αλλά ήθελα να μάθω. Πώς Δουλεύει κάτω από την κουκούλα.
graph LR
A[Text] --> B[Magic?]
B --> C[Beautiful Diagram]
...γίνετε ένα πραγματικό SVG; Και το πιο σημαντικό, πώς μπορείτε να συνδέσετε σε αυτό για να προσθέσετε χαρακτηριστικά όπως το pan / ζουμ, αλλαγή θέματος, και εξαγωγή λειτουργικότητα που έφτιαξα για αυτό το site (τώρα διαθέσιμο ως @mostly διαυγής/mermaid-enhancements)?
Μετά από πολύ σκάψιμο μέσω του πηγαίου κώδικα της Γοργόνας και την κατασκευή πραγματικών επεκτάσεων, εδώ είναι όλα όσα έμαθα σχετικά με το πώς η Γοργόνα λειτουργεί εσωτερικά και πώς να επεκταθεί σωστά.
Η Γοργόνα μετατρέπει τους ορισμούς κειμένου σε διαγράμματα.
Ο παλιός τρόπος:
Ο τρόπος της Γοργόνας:
Ενημέρωσέ το; Απλά επεξεργάζεσαι το κείμενο.
Η Γοργόνα υποστηρίζει έναν γελοίο αριθμό τύπων διαγραμμάτων:
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...]
Βλέπεις; Οι Γιατροί της Γοργόνας για την πλήρη λίστα.
Να τι συμβαίνει όταν η Γοργόνα κάνει ένα διάγραμμα:
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
Ας σπάσουμε κάθε βήμα.
Τα πάντα ξεκινούν με το κείμενο. Γράφετε διαγράμματα στο DSL της Γοργόνας (demain-special language):
// Flowchart
const diagram = `
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug time]
`;
Ο lexer σπάει το κείμενο σε μάρκες. Για παράδειγμα, αυτή η γραμμή:
A[Start] --> B{Decision}
Γίνεται μάρκες όπως:
[
{ 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 { }
]
Ο parser καταναλώνει μάρκες και δημιουργεί ένα αφηρημένο δέντρο σύνταξης (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' }
]
}
Η Γοργόνα χρησιμοποιεί διαφορετικούς πίνακες για κάθε τύπο διαγράμματος. Τζίσον (όπως Yacc/Bison για JavaScript).
Γοργόνα ανιχνεύει τον τύπο διαγράμματος από την πρώτη γραμμή:
// 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
Κάθε τύπος διαγράμματος έχει το δικό του depter. Ο depter παίρνει το AST και παράγει στοιχεία SVG.
Για charts ροής, η Γοργόνα χρησιμοποιεί το ΝτάγκρεCity name (optional, probably does not need a translation) βιβλιοθήκη για τη διάταξη γραφημάτων. Για άλλους, χρησιμοποιεί προσαρμοσμένους αλγόριθμους ή βιβλιοθήκες όπως το 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);
};
Ο εκτελεστής παράγει 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>
Η Γοργόνα τα βρίσκει όλα. .mermaid στοιχεία και τα αντικαθιστά με 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;
}
};
Αφού η Γοργόνα εισάγει το SVG, μπορείτε να το ενισχύσετε.
Περισσότερα γι' αυτό από κάτω.
Τώρα που ξέρουμε πώς λειτουργεί η Γοργόνα, ας εξερευνήσουμε πώς να την επεκτείνουμε.
Η πιο βασική επέκταση είναι η διαμόρφωση:
import mermaid from 'mermaid';
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
securityLevel: 'loose',
flowchart: {
curve: 'basis',
padding: 15
}
});
I covered this extensionly in Αλλαγή θεμάτων για τη Γοργόνα, αλλά εδώ είναι η βασική εφαρμογή:
Γοργόνα πρέπει να αρχικοποιηθεί με ένα θέμα, και δεν μπορείτε να το αλλάξετε μετά. δεν αποθηκεύει στο DOM.
Αποθηκεύστε το αρχικό περιεχόμενο πριν από την απόδοση, στη συνέχεια αποκαταστήστε και εκ νέου κατά την αλλαγή θεμάτων:
// 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;
}
}
};
Μέθοδοι ανίχνευσης πολλαπλών θεμάτων (τοποθεσίες χειρίζονται θέματα διαφορετικά):
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';
}
Βλέπεις; τον πλήρη κωδικό διακόπτη θέματος για λεπτομέρειες.
Αυτό είναι όπου η πραγματική μαγεία συμβαίνει. Αφού η Γοργόνα αποδίδει, μπορείτε να προσθέσετε διαδραστικά χαρακτηριστικά.
I covered this extensionly in Ενίσχυση Διαγραμμάτων Γοργόνας με Pan/Zoom και ΕξαγωγήΟπότε θα επισημάνω τις βασικές τεχνικές εδώ.
Δημιουργήστε ένα δοχείο περιτυλίγματος για τα χειριστήρια:
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;
}
Χρήση 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;
}
Δημιουργία πλωτού πίνακα ελέγχου:
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);
}
Μην επισυνάψετε ακροατές σε κάθε κουμπί.
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
}
});
Η πρόκληση: Τα στοιχεία SVG έχουν δυναμικό μέγεθος, το παν/ζωύφιο μεταμορφώνει, και κληρονομημένο στυλ. Για να εξαγάγετε σωστά, θα πρέπει να:
Χρήση 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);
}
Κρίσιμες λεπτομέρειες:
Βλέπεις; τον πλήρη κωδικό εξαγωγής για περισσότερες λεπτομέρειες.
Πακετάρισα όλες αυτές τις βελτιώσεις ως @mostly διαυγής/mermaid-enhancements- Βλέπεις; Publishing Mermaid Βελτιώσεις ως ένα πακέτο npm για πλήρεις λεπτομέρειες.
Εδώ είναι πώς όλα ταιριάζουν μεταξύ τους:
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();
Τα διαγράμματα της Γοργόνας σας έχουν τώρα:
Όπως καλύπτομαι Προσθήκη γοργόνας.js με htmx, θα πρέπει να επανεκκινήσετε την Γοργόνα μετά τις ανταλλαγές περιεχομένου HTMX:
// On page load
document.addEventListener('DOMContentLoaded', function () {
mermaid.initialize({ startOnLoad: true });
});
// After HTMX swaps content
document.body.addEventListener('htmx:afterSwap', function(evt) {
mermaid.run();
});
Με το πακέτο βελτιώσεων:
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
});
Μετά την κατασκευή αυτού του πράγματος και την αποσφαλμάτωση περίεργες ακραίες περιπτώσεις, εδώ είναι τι λειτουργεί:
Η Γοργόνα δεν διατηρεί την αρχική πηγή διαγράμματος μετά την απόδοση.
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);
Οι διαρροές μνήμης είναι πραγματικές. Καταστρέψτε περιπτώσεις πριν από τη δημιουργία νέων:
if (panZoomInstances.has(id)) {
try {
panZoomInstances.get(id).destroy();
} catch (e) {
console.warn('Failed to destroy:', e);
}
panZoomInstances.delete(id);
}
Μην επισυνάψετε ακροατές σε μεμονωμένα κουμπιά:
// ❌ 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 καθυστερεί JavaScript εκτέλεση. Περιμένετε για εξαρτήσεις:
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();
});
}
Και αποκλείστε το σενάριό σας από το Rocket Loader:
<script src="main.js" data-cfasync="false"></script>
Χρήση requestAnimationFrame για καλύτερο συγχρονισμό από τον αυθαίρετο setTimeout:
// After Mermaid renders
await mermaid.run();
// Wait for paint before enhancing
await new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
enhanceMermaidDiagrams();
resolve();
});
});
});
Το SVGs μπορεί να είναι παράξενο.
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}`;
}
Με την κατάλληλη αρχικοποίηση, θα πρέπει να δείτε:
Saving original data
Loading mermaid with theme: dark
Mermaid initialized
Enhanced 3 diagrams
Μετά την εφαρμογή των βελτιώσεων:
Διάγραμμα χωρίς απόδοση:
window.mermaid)Παν/ζωύφιο που δεν λειτουργεί:
pointer-events: none)Εξαγωγή συλλαμβάνει μόνο γωνία:
Θέμα που δεν αλλάζει:
data-processed Δεν αναστοιχειώνεταιΜην φορτώνετε τις βελτιώσεις μέχρι να χρειαστεί:
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 });
Αποδώστε διαγράμματα μόνο όταν είναι ορατά:
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);
});
Για αλλαγή μεγέθους ή θέματος:
let timeout;
window.addEventListener('resize', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
panZoomInstances.forEach(instance => {
instance.resize();
instance.fit();
});
}, 250);
});
Η Mermaid.js είναι φανταστική έξω από το κουτί, αλλά η κατανόηση πώς λειτουργεί εσωτερικά σας επιτρέπει να χτίσει μερικές πραγματικά δροσερό βελτιώσεις.
requestAnimationFrameΌλες οι τεχνικές που έχω καλύψει εδώ χρησιμοποιούνται στην παραγωγή σε αυτό το site και συσκευάζονται σε @mostly διαυγής/mermaid-enhancements. Η πλήρης πηγή είναι διαθέσιμη στο ως επί το πλείστον διαυγής ιστός/ ως επί το πλείστον διαυγής-χρήστης.
Δοκιμάστε τα χειριστήρια στα διαγράμματα παραπάνω!
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.