前言
最近项目中用到了组织架构图这个东西,产品的需求是不仅可以展示,还可以自由编辑,于是就突发奇想,使用canvas手搓一下,目前的功能只有展示和对节点的编辑等操作,后续可能会涉及到拖拽编辑等其他操作
运行效果和全部代码
此处的预览效果菜单有部分偏差,大家可以复制到本地预览
详细步骤
基础架构
- canvas
- 菜单
- 定义数据结构和节点基础配置
- 定义鼠标点击的节点
如何绘制节点
- 在canvas 上绘制一个矩形需要 起始(x,y)和长宽(在节点基础配置已定义)就行
- 把节点名字进行分割 循环绘制达到垂直的效果
绘制两个节点间的连线
- 两个(x,y)即可
- 可以使用直线 也可以使用曲线 这个可以自由选择
计算架构中每一层级的节点数量 同时保留父子关系
- 计算每层节点数量 我们可以合理的分配区域
开始绘制架构图 并确定绘制步骤
function drawOrgChart(root) {
const levelNodes = getLevelNodes(root)
const nodePositions = []
// 添加顶部边距常量
const TOP_MARGIN = 100 // 设置顶部边距为100px
// 第一步:计算每个父节点下子节点的总宽度
// 第二步:为每个节点分配位置
// 计算整个树的总宽度
const totalTreeWidth = calculateChildrenWidth(root)
const startX = (canvas.width - totalTreeWidth) / 2
// 分配位置并绘制
assignNodePositions(root, 0, startX)
// 绘制连接线和节点
}
第一步:计算每个父节点下子节点的总宽度
此处的思路是保证 父节点的位置在子节点的中间(即父节点的位置由子节点决定 因为需要递归)
没有子节点的元素不需要加上nodeConfig.horizontalGap
此处不加nodeConfig.horizontalGap
此处加nodeConfig.horizontalGap
第二步:为每个节点分配位置
此处秉持的思路是 把所有节点落到同一层,在次层级计算出该节点所对应的x坐标(y坐标根据层级来的,可以认为已经确定)
绘制连接线和节点
此时我们需要一个函数去寻找当前节点的父节点
对定义的nodePositions 进行循环绘制节点
此时绘制工作已经完成了 无需对节点编辑的话就到此结束了
添加右键菜单和点击菜单
绑定监听函数
canvas.addEventListener('contextmenu', (e) => {
e.preventDefault()
e.stopPropagation()
})
canvas.addEventListener('click', (e) => {
e.stopPropagation() // 阻止事件冒泡
})
document.addEventListener('click', (e) => {
// 如果点击的是 canvas 区域,不要在这里处理
if (e.target === canvas) {
return
}
// 如果点击的不是菜单内部,则隐藏菜单
if (!addMenu.contains(e.target) && !deleteMenu.contains(e.target)) {
hideMenus()
}
})
添加菜单
canvas.addEventListener('click', (e) => {
e.stopPropagation() // 阻止事件冒泡
const rect = canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
//找到点击的节点
selectedNode = findNode(x, y)
// 无论如何,先隐藏所有菜单
hideMenus()
if (selectedNode) {
// 显示添加菜单
addMenu.style.display = 'block'
// 计算菜单位置,避免超出窗口边界
let menuX = e.pageX
let menuY = e.pageY
const menuWidth = addMenu.offsetWidth
const menuHeight = addMenu.offsetHeight
const windowWidth = window.innerWidth
const windowHeight = window.innerHeight
if (menuX + menuWidth > windowWidth) {
menuX = windowWidth - menuWidth
}
if (menuY + menuHeight > windowHeight) {
menuY = windowHeight - menuHeight
}
addMenu.style.left = menuX + 'px'
addMenu.style.top = menuY + 'px'
}
})
找到点击的节点
绑定菜单的处理函数
添加子节点
添加同级节点
编辑节点
在操作后重新渲染
结语
谢谢你的观看 如果你觉得不错,点个赞就是对我最大的支持,如果你有什么问题欢迎在评论区留言 另外可拖拽的节点文章正在编写,敬请期待