美人鱼 @MSMK0js deep dive @:}如何实际工作以及如何扩展它 (中文 (Chinese Simplified))

美人鱼 @MSMK0js deep dive @:}如何实际工作以及如何扩展它

Sunday, 09 November 2025

//

12 minute read

一. 导言导言 导言 导言 导言

请注意::}这是我在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}

旧的老样子

  1. 公开图表绘制工具
  2. 创建图表
  3. 作为巴布亚新几内亚出口
  4. 嵌入于 docs
  5. 需要更新@? @ 找到源文件@ ,} 编辑 @ MS K2 @ re-_ export @ MPK4} 替换图像@ I...{

美人鱼之道

  1. 文本中写入图表
  2. 完成完成完成

更新它@? 只要编辑文本“.版本控制” @? 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 变换@,和继承样式 @. 要正确导出,}您需要到

  1. 克隆 SVG
  2. 保全维度
  3. 删除变换
  4. 转换为 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 @ ( @% 1pointer-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在盒子里非常棒... ...,... ...但是理解它的内部运作方式...

  1. 输油管:Text @→Lexer@→Parser →AST→Render{→SVG
  2. 推广点:+Fintig@,}主题 @,Post-+Render加强功能
  3. 存储原始数据美人鱼是不会为你做的
  4. 清理资源: 内存泄漏是真的
  5. 代表团活动更好的业绩
  6. 多重主题来源: 站点处理主题的方式不同
  7. 时间安排事项: 使用 requestAnimationFrame

这里覆盖的所有技术 I'+@ve 都用于此站点的生产生产,并包装在 美人鱼 -增强音量. 完整的源代码可在 美人鱼.

相关员额

资源资源资源 资源和资源资源资源

尝试上方图表的控件@! @ 📊}

Finding related posts...
logo

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