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
ЗАУВАЖЕННЯ: це частина моїх експериментів з комп' ютером ШІ / спосіб витратити на веб- кредит 100$ Code. Я надав вам папірець, моє розуміння, питання, які я повинен був створити для цієї статті. Це весело і заповнить прогалину, яку я не бачив у жодному іншому місці.
Цей допис будує над попередніми статтями: Якщо ви ще не встигли, подивіться на це. Додавання mudrow.js з htmx, Перемикання тем для Mer покоївки, і Покращення діаграм Merganow з Pan/Zoom і експортуванняЦе глибоке пірнання пояснює, що за цими реалізаціями стоїть внутрішній світ.
Mermald. js є дійсно блискучим. Напишіть простий текст, напишіть чудові діаграми. Більше ніяких помилок з показом Visio або drawing.io, втрат у файлах вихідних кодів або збереження окремих файлів зображень. Всі ці файли зберігаються у вигляді позначення, керування версіями поряд з вашим кодом.
Але я хотів знати. як і працює під капотом.
graph LR
A[Text] --> B[Magic?]
B --> C[Beautiful Diagram]
І що більш важливо, як можна втягнутися в неї, щоб додати такі можливості, як pan/zoom, перемикання тем та функціональні можливості експорту, які я побудував для цього сайта (тепер доступний як) @ mostulicid/mer покоївка- enthensments)?
После того, как я много проверял источник Мердёй и построил настоящие усилия, вот всё, что я узнала о том, как Мэриль работает внутри, и о том, как правильно его расширить.
Мерміль перетворює текстові визначення на діаграми. Думайте про "Помітки для діаграм."
Старий шлях:
Марланка в порядку:
Оновіть його? Просто змініть текст. Керування версіями? Це просто текст! Працює у вигляді позначки? Так!
Merowles підтримує безглузду кількість типів діаграм:
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 Merowle (спеціальна мова доменів):
// Flowchart
const diagram = `
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug time]
`;
Лічильник розбиває текст на поля. Наприклад, цей рядок:
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 { }
]
Обробник використовує позначки і будує абстрактне дерево синтаксису (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' }
]
}
Merowel використовує різні обробники для кожного типу діаграм. Їх часто створюють з файлів граматики за допомогою Джісон (на зразок Yaccc/Bison для JavaScript).
Merowows визначає тип діаграми з першого рядка:
// 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
Кожен з типів діаграм має свій власний показ. Інструмент відображення бере елемент AST і створює елементи SVG.
Для блок- схем, Mer покоївка використовується Dager бібліотека для компонування графів. Для інших програм використано нетипові алгоритми або бібліотеки на зразок 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:
<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>
Mer покоївка знаходить все .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;
}
};
Після того, як Mer покоївка вставляє SVG, ви можете покращити її. Ось тут всі мої покращення:
Більше про це.
Теперь, когда мы знаем, как работает Мер горничная, давайте проверим, как его расширить.
Основним суфіксом є налаштування:
import mermaid from 'mermaid';
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
securityLevel: 'loose',
flowchart: {
curve: 'basis',
padding: 15
}
});
Я все це продумав у Перемикання тем для Mer покоївки, але ось ключова реалізація:
Мембрану треба ініціалізувати з темою, і ви не можете змінити її після. Але якщо ви хочете зробити діаграму з новою темою, вам потрібна початкова діаграма з початковою діаграмою, де вона є не зберігається в 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';
}
Див. повний код перемикача теми у подробицях.
Тут відбувається справжня магія. ви можете додати інтерактивні характеристики.
Я все це продумав у Покращення діаграм Merganow з 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);
}
Критичні подробиці:
Див. повний код експорту щоб дізнатися більше.
Я запакував всі ці покращення як @ mostulicid/mer покоївка- enthensments. Див. Оприлюднення програмного забезпечення як пакунка 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();
У ваших діаграмах "Мернінгі" тепер є:
Як я вкрився Додавання mudrow.js з htmx, вам слід повторно ініціалізувати Mer покоївку після розмінів на контентах 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
});
Після побудови цієї штуки і усування дивних випадків на край, ось що працює:
Mer покоївка не зберігає початковий код діаграми після відтворення. Ви можете зберегти його самостійно:
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 затримує виконання 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:
<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();
});
});
});
SVG може бути дивним. Завжди перевіряйте:
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);
});
Mer покоївка.js чудово виходить з коробки, але розуміння того, як вона працює внутрішньо дає вам змогу побудувати деякі дійсно круті покращення. Основне розуміння:
requestAnimationFrameВсі прийоми, які я тут розповів, використовуються для виробництва на цьому сайті і запаковані в @ mostulicid/mer покоївка- enthensments. Повне джерело доступне за адресою three locidweb/ method-mer покоївка.
Спробуйте засоби керування на діаграмах, що вище! ♫
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.