实现不相似树形图组件 - 节点位置初始化
如果每个节点都位于其正确的位置,那么整个树状图自然也就是正确的了,想要保证每个节点位于其正确的位置,其实就是对于上面提到的两个问题的解决
父节点与其所有子层级下的第一个子节点保持垂直居中对齐
如果是横向垂直居中对齐模式,那么这个对齐问题是很好解决的,只需要将父节点单独看成一个容器,将父节点下所有的子节点单独看成一个容器,那么对两个容器应用 vertical-align: middle
(或者其他的能够让两个平级元素垂直居中的方式)即可
至于第一元素对齐模式这种,也可以将父节点及其所有层级下的第一个子节点放到同一个DOM
层级下,然后使用 vertical-align: middle
(或者其他的能够让两个平级元素垂直居中的方式)也可以,但是前面说了,这种 DOM
布局方式会破坏层级结构,并且不利于后续的一些操作,而如果多个元素分布在多个容器内的话,是很难用 CSS
来垂直居中
css
不行,就只能借助 js
了,而利用 js
计算并操作 DOM
的能力,其实也比较简单
通过深度优先遍历,就可以获取到父节点及其所有子层级下的第一个子节点的DOM
了,然后从这些节点中找到高度最大的那个节点,其高度就作为整个容器的高度,其他节点以这个高度作为垂直居中对齐的基准即可
比如对于上面图4中的节点0、1、4、5四个节点,高度最高的是节点4,则节点4位置不需要变化,通过调整节点0、1、5的y
轴位置,保证与节点4垂直居中对齐(我这里采用了 margin-top
)
function computedParentElesPosition(parentEles: HTMLElement[]) {
const heights = parentEles.map(ele => ele.offsetHeight)
// 找出最大高度
const maxHeight = Math.max.apply(null, heights)
const halfMaxHeight = maxHeight / 2
parentEles.forEach((ele, index) => {
if (heights[index] < maxHeight) {
ele.style.marginTop = halfMaxHeight - heights[index] / 2 + 'px'
} else {
ele.style.marginTop = '0'
}
})
}
子节点连接线的高度
如果是横向垂直居中对齐模式,其实仅通过 css
照样可以解决
如上图,对于父节点0来说,其直接子节点1、2、3连成的竖向连接线的高度,其实就是节点1及其所有层级子节点共同所在容器的高度的一半,加上节点2所在容器的高度,加上节点3及其所有层级子节点共同所在容器的高度的一半
如上图,竖向连接线的高度,就是所有子节点中:第一个子节点高度一半 + 最后一个子节点高度一半 + 其他所有子节点的高度
这里说的子节点高度,包括的是子节点及其所有层级下所有子节点组成的一个大容器的高度
例如,对于节点 1来说,这个高度是指节点1及其节点4、5、6、7共同组成的大容器的高度,也就是图中的红色虚线框的高度,另外为了保持树状图的协调性,父节点下的子节点间上下会存在一定的间距,这部分间距也作为大容器的一部分
那么就可以利用这些子元素所在的大容器,来拼接竖向连接线,对于节点1、4、5、6、7所在的红色虚线大容器元素来说,其左边框的下半部分就可以作为竖向连接线的一部分,通常利用一个高度是红色虚线大容器元素高度一半的伪元素即可完成;
对于节点2,其所在大容器的整个左边框都是竖向连接线的一部分;
对于节点3、8、9所在的青色虚线大容器元素来说,其左边框的上半部分就可以作为竖向连接线的一部分,通常利用一个高度是青色虚线大容器元素高度一半的伪元素即可完成;
这样,就完成了竖向连接线的拼接,至于那些横向的连接线,就更简单了,一般也是通过伪元素完成
但是如果换成第一元素对齐模式,就没那么简单了
第一元素对齐模式,对齐的是所有层级下的第一个子节点,这些节点不在同一个层级下,并且其本身高度、有无子节点、子节点高度、有几层子节点等都是不确定的,子节点竖向连接线的起始点位置和高度是无法前置确定的,所以需要通过计算来完成
不过也不难,只需要找到父节点下所有子节点中的第一个子节点和最后一个子节点,通过计算二者之间的位置,即可得到竖向连接线的高度,因为DOM
结构维持了父子间的层级结构,所以还是很容易得到的
比如对于图4中的节点0来说,连接器子节点的竖向连接线的高度,就是:
Node3.bottom - Node1.top - Node1.height / 2 - Node3.height / 2
当然,除了高度之外,竖向连接线的位置也需要进行计算,就比较简单了
推荐阅读