jsmind
jsmind copied to clipboard
设置了 custom_node_render 后 jmexpander 位置计算错误被遮蔽
版本 0.8.3,复现代码:
import React, { useEffect, useRef } from "react"
import ReactDOM from "react-dom"
import jsMind from "jsmind"
import "jsmind/style/jsmind.css"
const MindmapTest = () => {
const jmContainer = useRef(null)
const jm = useRef(null)
const nodeRenderTest = (jm, element, node) => {
if (node.isroot || node.id === "2") {
return false
}
ReactDOM.render(<span>{node.topic}</span>, element)
return true
}
useEffect(() => {
if (!jmContainer.current) {
return
}
jm.current = new jsMind({
container: jmContainer.current,
editable: true,
theme: "orange", //"greensea", //
shortcut: {
enable: true, // 是否启用快捷键
},
view: {
custom_node_render: nodeRenderTest, // 使用自定义渲染函数
},
})
jm.current.show({
meta: { name: "", author: "", version: "" },
format: "node_array",
data: [
{ id: "root", topic: "前端库", isroot: true },
{ id: "1", topic: "脑图(custom_node_render展开按钮被遮蔽了)", parentid: "root", expanded: false },
{ id: "11", topic: "JSMind", parentid: "1" },
{ id: "2", topic: "流程图", parentid: "root", expanded: false },
{ id: "21", topic: "PlantUML", parentid: "2" },
],
})
jm.current.resize()
})
return <div ref={jmContainer} style={{ width: "100%", height: "100%" }} />
}
export { MindmapTest }
产生的dom结构:
<div style="width: 100%; height: 100%;">
<div class="jsmind-inner jmnode-overflow-hidden" tabindex="1">
<canvas class="jsmind" width="1512" height="410"></canvas>
<jmnodes class="theme-orange" style="width: 1512px; height: 410px;">
<jmexpander nodeid="1" style="visibility: visible; left: 765px; top: 169px;">+</jmexpander>
<jmnode nodeid="1" class="" style="visibility: visible; left: 745px; top: 166px;">
<span>脑图(custom_node_render展开按钮被遮蔽了)</span>
</jmnode>
<jmexpander nodeid="2" style="visibility: visible; left: 813px; top: 218px;">-</jmexpander>
<jmnode nodeid="2" style="visibility: visible; left: 745px; top: 206px;">
流程图
</jmnode>
<jmexpander nodeid="11" style="visibility: hidden; display: none;">-</jmexpander>
<jmnode nodeid="11" style="visibility: hidden; display: none;">
<span>JSMind</span>
</jmnode>
<jmexpander nodeid="21" style="visibility: hidden; display: none;">-</jmexpander>
<jmnode nodeid="21" style="visibility: visible; left: 856px; top: 215px;">
<span>PlantUML</span>
</jmnode>
<jmnode class="root selected" nodeid="root" style="visibility: visible; left: 623px; top: 181.5px;">
前端库
</jmnode>
</jmnodes>
</div>
</div>
页面效果:

注意上面第一个子节点,看不到展开按钮。实际因 left top 设值错误,被节点本身遮住了。并且被 custom_node_render 渲染出的节点,上下的 margin 也消失了,挤在一起。
你在脑图…那个节点再加一个下级节点看看,看一下下一级节点的位置是不是正常的。
如果正常的话,这个节点可能是受页面上的其他样式的影响才这样的。
我稍后也试试看能不能复现这个现象。谢谢反馈!
哇,您回复好快。
看我例子的数据里面,是加了子节点的。
用开发者工具修改 css 让展开图标露出来,点击后的子节点位置也不对:

我想这个问题可能的原因是 ReactDOM.render(<span>{node.topic}</span>, element) 这一句,按我对 React 粗浅的理解,它并不是实时把 html element 给 render 出来的。jsMind 会在 custom_node_render 后尝试读取这个 node 的尺寸以进行排版,但是在 React 的环境下,读取到的 size 可能是个 0,由此产生了这个布局问题。
要验证这个原因,你可以把ReactDOM.render 这句直接改成 html dom 的写法,像 document.createElement 这种实验一下。
抱歉 @hqm19 , 我对 React 并不是很在行,在我本地配置了一个 react 18 的项目,但加上你上面帖的代码后一直跑不起来,所以也没能复现这个情况。你能否把你项目的 package.json 帖出来?或者在github/gitee上搭建一个小的项目演示这个问题,我也好可以运行起来试一下?
抱歉忘记说明 react 的版本了,我用的是 react 16 目前比较稳定。 react 18 + jsmind 还没调通。 我提交了一个工程把完整的复现代码放上去了: https://github.com/hqm19/jsmind-react
Hi @hqm19 我提交了一个 PR https://github.com/hqm19/jsmind-react/pull/1 你可以看看。
react 整体都是异步渲染的,把 react 改成同步没什么便捷的办法。 问了 ChatGPT ,综合后,下面的方案可以试试:
- 修改 jsmind 内部实现,允许将 view.custom_node_render 设置为一个返回 Promise 的函数。 jsmind 判断返回值,如果是 Promise 对象,则将后续动作放在, Promise 的 than 中执行。例如
const obj = custom_node_render_func(jm, element, node)
if (!!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'){
obj.then((result) => {
// 原来对返回 true 的处理 (label x)
}
}else if(obj){
// 原来对返回 true 的处理
}else{
// 原来对返回 false 的处理
}
- custom_node_render 设置为返回 一个 Promise 的函数:
view.custom_node_render = (jm, element, node) => {
// 函数返回一个Promise
return new Promise((resolve, reject) => {
ReactDOM.render(<span>{node.topic}</span>, element, () => {
// ReactDOM.render在异步渲染 element 完成后,会执行这个回调函数,
resolve(true) // 解决Promise后, 执行(label x)的地方
})
})
}
原因总结:
- jsMind 使用同步模式获取 html 元素尺寸并计算位置
- 本用例在 custom_node_render 里使用 React 框架异步填充 html 元素
- jsMind 在获取 html 元素尺寸时,该元素尚未 render 出来
- 导致位置的计算结果不符合预期
解决办法: 方法1: 在 custom_node_render 的实现里,使用同步模式填充 html 元素 (推荐) 方法2: 修改 jsMind 以支持异步方式生成节点。这种方法需要对 jsMind 进行较大规模重构,而且无法保证目前文档中 api 的兼容性,因此考虑暂不支持,后续或考虑以新项目的方式支持异步模型。
感谢 @hqm19 带来的建设性的讨论,以及非常有价值的尝试 https://github.com/hizzgdev/jsmind/pull/594 。 相关的 PR 将会被关闭,此 issue 将会移至 discussion 区。