Flutter集成Mermaid API遇阻,无法预览Mermaid代码为图表
核心问题定位
- index.html中Mermaid脚本加载位置错误,DOM元素未就绪就执行渲染逻辑
- JS interop调用未处理异步特性,导致渲染时机偏差
- Markdown转换后的代码块未正确替换为Mermaid可识别的渲染容器
分步修复方案
1. 修正index.html配置
将Mermaid脚本放在<body>末尾,确保DOM元素先加载,同时提前全局配置Mermaid(与Dart端保持一致):
<!DOCTYPE html> <html> <head> <base href="$FLUTTER_BASE_HREF"> <meta charset="UTF-8"> <title>Flutter Mermaid Demo</title> </head> <body> <div id="mermaid-container"></div> <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script> <script> mermaid.initialize({ securityLevel: 'strict', logLevel: 'error', startOnLoad: false, arrowMarkerAbsolute: true, flowchart: { htmlLabels: true } }); </script> <script src="main.dart.js" type="application/javascript"></script> </body> </html>
2. 调整Dart代码逻辑
确保DOM完全加载后执行渲染,正确处理Mermaid异步渲染,并替换代码块容器:
import 'dart:html'; import 'package:flutter/material.dart'; import 'package:markdown/markdown.dart' as md; import 'package:js/js.dart'; import 'package:js/js_util.dart'; // JS Interop 定义 @JS() library mermaid; @JS('mermaid.render') external Promise<String> mermaidRender(String elementId, String mermaidCode); // 自定义Sanitizer(保持原实现) final nullSanitizer = NullTreeSanitizer(); class NullTreeSanitizer implements NodeTreeSanitizer { @override void sanitizeTree(Node node) {} } // 测试用Mermaid Markdown内容 const testContent = """ Sequence Diagram ------------------ ```mermaid sequenceDiagram actor Alice actor Bob Alice->>Bob: Hi Bob Bob->>Alice: Hi Alice
""";
class MermaidRenderer extends StatefulWidget {
@override
_MermaidRendererState createState() => _MermaidRendererState();
}
class MermaidRendererState extends State
@override
void initState() {
super.initState();
// 等待Flutter框架和Web DOM都就绪后渲染
WidgetsBinding.instance.addPostFrameCallback((
}
Future
final container = querySelector('#mermaid-container') as DivElement;
// 转换Markdown为HTML
final htmlContent = md.markdownToHtml(
testContent,
extensionSet: md.ExtensionSet.gitHubWeb,
);
container.setInnerHtml(htmlContent, treeSanitizer: nullSanitizer);
// 遍历所有Mermaid代码块,逐个渲染 final codeBlocks = container.querySelectorAll('code.language-mermaid'); for (var block in codeBlocks) { final parent = block.parent; if (parent is PreElement) { // 创建唯一ID的渲染容器 final renderId = 'mermaid-${DateTime.now().millisecondsSinceEpoch}'; final renderContainer = DivElement()..id = renderId; // 替换原pre/code容器 parent.replaceWith(renderContainer); // 调用Mermaid异步渲染 final svgResult = await promiseToFuture(mermaidRender(renderId, block.text)); renderContainer.setInnerHtml(svgResult, treeSanitizer: nullSanitizer); } }
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Mermaid Chart Demo')),
body: const HtmlElementView(viewType: 'mermaid-container'),
);
}
}
### 3. 关键注意事项 - 不要混用`HtmlWidget`:该组件针对Flutter非Web端设计,Web端直接使用`HtmlElementView`绑定DOM元素更可靠 - 异步处理:Mermaid的`render`方法返回Promise,必须通过`promiseToFuture`转换为Dart Future等待结果 - 容器替换:Markdown转换后的`<pre><code>`块需要替换为独立Div容器,避免渲染冲突 - 安全级别:本地测试可临时将`securityLevel`设为`'loose'`,避免SVG被拦截,正式环境改回`'strict'` --- ## 简化替代方案:WebView直接加载 如果JS interop流程过于复杂,可直接用`webview_flutter`加载本地HTML文件: 1. 在`web`目录下创建`mermaid-chart.html`: ```html <!DOCTYPE html> <html> <head> <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script> <script>mermaid.initialize({ startOnLoad: true });</script> </head> <body style="margin:0;"> <pre class="mermaid"> sequenceDiagram actor Alice actor Bob Alice->>Bob: Hi Bob Bob->>Alice: Hi Alice </pre> </body> </html>
- Flutter中加载该文件:
import 'package:webview_flutter/webview_flutter.dart'; class MermaidWebView extends StatelessWidget { @override Widget build(BuildContext context) { return WebView( initialUrl: 'mermaid-chart.html', javascriptMode: JavascriptMode.unrestricted, backgroundColor: Colors.transparent, ); } }
内容的提问来源于stack exchange,提问作者Omar Boshra

