refactor(MermaidPreview): separate measurement from rendering

This commit is contained in:
one 2025-08-15 15:50:36 +08:00
parent 851b98bf5c
commit c3e61690d7
2 changed files with 35 additions and 13 deletions

View File

@ -14,8 +14,10 @@ const vizInitializer = new AsyncInitializer(async () => {
return await module.instance() return await module.instance()
}) })
/** Graphviz /**
* 使 usePreviewRenderer hook * Graphviz
* - 使 useDebouncedRender
* - 使 shadow dom SVG
*/ */
const GraphvizPreview = ({ const GraphvizPreview = ({
children, children,

View File

@ -8,9 +8,10 @@ import ImagePreviewLayout from './ImagePreviewLayout'
import { BasicPreviewHandles, BasicPreviewProps } from './types' import { BasicPreviewHandles, BasicPreviewProps } from './types'
import { renderSvgInShadowHost } from './utils' import { renderSvgInShadowHost } from './utils'
/** Mermaid /**
* 使 usePreviewRenderer hook * Mermaid
* FIXME: 等将来 mermaid-js * - 使 useDebouncedRender
* - 使 shadow dom SVG
*/ */
const MermaidPreview = ({ const MermaidPreview = ({
children, children,
@ -21,20 +22,39 @@ const MermaidPreview = ({
const diagramId = useRef<string>(`mermaid-${nanoid(6)}`).current const diagramId = useRef<string>(`mermaid-${nanoid(6)}`).current
const [isVisible, setIsVisible] = useState(true) const [isVisible, setIsVisible] = useState(true)
// 定义渲染函数 /**
* shadow dom
* 退 innerHTML
*/
const renderMermaid = useCallback( const renderMermaid = useCallback(
async (content: string, container: HTMLDivElement) => { async (content: string, container: HTMLDivElement) => {
// 验证语法,提前抛出异常 // 验证语法,提前抛出异常
await mermaid.parse(content) await mermaid.parse(content)
const { svg } = await mermaid.render(diagramId, content, container) // 获取容器宽度
const { width } = container.getBoundingClientRect()
if (width === 0) return
// 避免不可见时产生 undefined 和 NaN // 创建临时的 div 用于 mermaid 测量
const fixedSvg = svg.replace(/translate\(undefined,\s*NaN\)/g, 'translate(0, 0)') const measureEl = document.createElement('div')
measureEl.style.position = 'absolute'
measureEl.style.left = '-9999px'
measureEl.style.top = '-9999px'
measureEl.style.width = `${width}px`
document.body.appendChild(measureEl)
// 使用 shadow dom如果有问题可以回退到 innerHTML try {
renderSvgInShadowHost(fixedSvg, container) const { svg } = await mermaid.render(diagramId, content, measureEl)
// container.innerHTML = fixedSvg
// 避免不可见时产生 undefined 和 NaN
const fixedSvg = svg.replace(/translate\(undefined,\s*NaN\)/g, 'translate(0, 0)')
// 有问题可以回退到 innerHTML
renderSvgInShadowHost(fixedSvg, container)
// container.innerHTML = fixedSvg
} finally {
document.body.removeChild(measureEl)
}
}, },
[diagramId, mermaid] [diagramId, mermaid]
) )
@ -67,7 +87,7 @@ const MermaidPreview = ({
const element = containerRef.current const element = containerRef.current
if (!element) return if (!element) return
const currentlyVisible = element.offsetParent !== null const currentlyVisible = element.offsetParent !== null && element.offsetWidth > 0 && element.offsetHeight > 0
setIsVisible(currentlyVisible) setIsVisible(currentlyVisible)
} }