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
美人鱼 @MSMK0js deep dive @:}如何实际工作以及如何扩展它
Sunday, 09 November 2025
一. 导言导言 导言 导言 导言
请注意::}这是我在AI &/’的实验的一部分, 这是用来花费“$1000”Calude Code Web credits “.” I''ve fed this BUNCH of pages, 我的理解是‘,’
校对:Soup 如果您已退出 , 请检查 用 htmx 添加美人鱼@. @js, 为美人鱼切换主题,和 使用 Pan/#_ @ om & 导出加强美人鱼图@.}这种深沉的潜水解释执行背后的内部原因
Mermaid.js 是真正辉煌的@.写简单的文本}♪, 得到美丽的图表 . 不再因 Visio 或 draw\ . MIO}, 失去源文件 @, 或保持单独的图像文件 <. > 所有的东西都生活在标记下", 版本\ MPK9 控制在您的代码
但我想知道 如何 它实际上工作 在hung.}这如何? :
graph LR
A[Text] --> B[Magic?]
B --> C[Beautiful Diagram]
... @#Beasome a reactual SVG?}(更重要的是)你如何勾搭它来添加一些功能, 例如 Pan/Zoom@,}主题切换=,}我为这个网站所建的输出功能 @( 美人鱼 -增强音量)?
在通过美人鱼的 'Qs源代码进行大量挖掘之后... ...然后在此处建立真正的扩展...
美人鱼是什么?
美人鱼将文本定义转换为图表@ . @ 想象\ MS K1 Markdown 的图形@ MPK2}
旧的老样子
- 公开图表绘制工具
- 创建图表
- 作为巴布亚新几内亚出口
- 嵌入于 docs
- 需要更新@? @ 找到源文件@ ,} 编辑 @ MS K2 @ re-_ export @ MPK4} 替换图像@ I...{
美人鱼之道
- 文本中写入图表
- 完成完成完成
更新它@? 只要编辑文本“.版本控制” @? It's just text}{!} 工作在标记@MS K5Yep_!
图表类型
美人鱼支持数量荒谬的图表类型@: @
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
让'每一步分开 . *
步骤 @1: 文本定义
一切以文字@ .开头, 您在美人鱼中写入图表 : @ MS K1s DSL @ MPK2 @ domain @ I-}% k3 @ 特定语言=):{
// Flowchart
const diagram = `
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug time]
`;
步骤 @2:_ 词汇分析
名词典将文字拆分为符號@ . @ 例如 @ MS K1}% this line @ MPK2
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 { }
]
步骤 3: 剖析和 AST 一代
解析器会消耗标牌, 并构建一个抽象的语法树@ ( @ 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' }
]
}
美人鱼对每个图表类型使用不同的解析器.}这些通常是用语法文件生成的 与 Jison 代表 JavaScript ). 的 Yacc/Bison
步骤 @4:_图检测
美人鱼检测第一行的图表类型 @:
// 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
步骤“5:”类型@-}具体文件
每个图表类型都有自己的投影器 . 投影机取自 AST 并生成 SVG 元素 *.
美人鱼使用 达格 用于图形布局的库@. @% 对于其他{,} 它使用自定义算法或图书馆, 如 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 标记:
<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>
步骤 *7:DOM插入
美人鱼找到所有的东西 .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;
}
};
步骤“8:”邮政“MS K1”正在处理 “(”你从哪里来?
在美人鱼插入 SVG,后, 您可以增强它 . 这里是我在 @:}中所有提升钩的地方
- Pan/Zizom 功能
- 控制按钮
- 出口能力
- 主题切换
更多相关内容在下面@. @%
推广点
现在我们知道美人鱼是如何工作的了 ,,让我们来探索如何扩展它
@ 1._ 配置
最基本的扩展名是配置@: @%
import mermaid from 'mermaid';
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
securityLevel: 'loose',
flowchart: {
curve: 'basis',
padding: 15
}
});
2.主题自定义
我广泛报道了 为美人鱼切换主题@,,但这里#' 关键执行@:
主题转换问题
美人鱼需要初始化一个主题@,, 您可以在“ .” 之后修改它。 但是如果您想要用新主题重新%-render 图表,, 您需要一个原始的图源 @— 在 DOM 中存放的't.
解决方案
在将“ ,” 转换为“ I-” 之前存储原始内容, 然后在切换主题时恢复并重命名 “ MS K1” render @ :}
// 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;
}
}
};
多主题检测方法 @(_%sites 处理主题的方式不同@):}
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';
}
见见 整个主题切换码 详细描述@. @%
3.Post-Reander Support
这里才是真正的魔法发生的地方 在美人鱼完成后 ,\Q 您可以添加交互式功能@.}
我广泛报道了 使用 Pan/#_ @ om & 导出加强美人鱼图@,}所以IQ'#Lall在此强调关键技术_.
包装图
创建用于控件的包装容器@: @%
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;
}
正在添加 Pan/ @@ om
使用 {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}:
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);
}
事件代表团 @(_Performance@!)
Donó't 附加每个按钮的听众@ .使用事件代表团 @ MS K2
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 元素具有动态缩放Q,pan/zoom 变换@,和继承样式 @. 要正确导出,}您需要到
- 克隆 SVG
- 保全维度
- 删除变换
- 转换为 PNG 或 SVG
使用 @-_to% -}图像:
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);
}
关键细节@:
- 保存保护视图框 - 捕捉整张图表\ MS K1 不只是可见部分
- 关闭% - @% 屏幕翻譯 - 避免影响显示的图表
- 2 PNG出口快餐的-#High_-$DPI
见见 完整导出代码 详细信息@. @%
完整增强套件
我将所有这些增强功能包装为 美人鱼 -增强音量. 见 作为 npm 软件包的美人鱼增强 完整详细信息@. @%
{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}这
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();
你的美人鱼图现在有:了
- @✅_ 交互式的 Pan_ / zoom
- ✅全屏光箱
- ✅PNG/SVG出口
- ✅ 自动主题切换
- “✅”响应设计
与HTMX结合
正如我所覆盖的 用 htmx 添加美人鱼@. @jsHTMX 内容互换后, 您需要重新激活美人鱼@ , @ k2 @
// 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
});
最佳做法
建立这个工具并调试奇异的边缘情况后 "M,"在这里
1. 总是存储原始内容
美人鱼在翻譯@.}後, ' @t 保存原始图表源, 您必須自己儲存@ MS K2 @%
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.清理现场
内存泄漏是真实的@. 在创建新事件前销毁实例 @:
if (panZoomInstances.has(id)) {
try {
panZoomInstances.get(id).destroy();
} catch (e) {
console.warn('Failed to destroy:', e);
}
panZoomInstances.delete(id);
}
3.使用活动代表团
Donó't 附加单个按钮的收听器@ :
// ❌ 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火箭装载器
火箭加载器延迟 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();
});
}
将您的主要脚本从火箭加载器中排除@: @%
<script src="main.js" data-cfasync="false"></script>
时间是一切
使用 requestAnimationFrame B. 时间比任意选择更合适 setTimeout:
// After Mermaid renders
await mermaid.run();
// Wait for paint before enhancing
await new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
enhanceMermaidDiagrams();
resolve();
});
});
});
6.防守SVG处理
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}`;
}
调试提示
控制台输出
使用正确的初始化@ , 您应该看到 @ MS K1
Saving original data
Loading mermaid with theme: dark
Mermaid initialized
Enhanced 3 diagrams
测试核对清单
实施增强后 @:
- 页面载荷中提供的圖表
- Pan/Zzoom控制工作
- [ @_全屏打开@/}#(X,}点击外端 @MS K4}ESSC%){
- PNG 导出捕捉完整图表
- SVG出口保留区矢量
- 在 HTMX 内容互换后起作用
- [ @ ] @ 主题切换@ MS K1 @ renders 正确
- [ 移动响应
- [ @ ] 暗色模式外观
- 键盘无障碍
共同问题
图表未创建@: @%
- 检查浏览器控制台错误
- 校验美人鱼装入了 @(
window.mermaid) - 检查图表语法
Pan/Zoom 无法工作 @:
- 校验 svgM-pan}-zoom 已初始化
- 检查冲突的 CSS @ ( @% 1
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 });
跨部门观察员
仅在可见时显示 Render 图表@ : @ *
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);
});
Debcunse 重新- renders
调整大小或主题变化@: @%
let timeout;
window.addEventListener('resize', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
panZoomInstances.forEach(instance => {
instance.resize();
instance.fit();
});
}, 250);
});
在结论中的结论
美人鱼+.js在盒子里非常棒... ...,... ...但是理解它的内部运作方式...
- 输油管:Text @→Lexer@→Parser →AST→Render{→SVG
- 推广点:+Fintig@,}主题 @,Post-+Render加强功能
- 存储原始数据美人鱼是不会为你做的
- 清理资源: 内存泄漏是真的
- 代表团活动更好的业绩
- 多重主题来源: 站点处理主题的方式不同
- 时间安排事项: 使用
requestAnimationFrame
这里覆盖的所有技术 I'+@ve 都用于此站点的生产生产,并包装在 美人鱼 -增强音量. 完整的源代码可在 美人鱼.
相关员额
资源资源资源 资源和资源资源资源
- MermaidMSMK0js文档
- 美人鱼 GitHub
- {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}
- @-_to% -}图像
- 我的美人鱼增强包
尝试上方图表的控件@! @ 📊}