我有一些组件(semantic-ui-react 中的分段组件),我正在尝试创建一个树状组件,它将由下图中给出的线组成。
我找到了一个名为 react-lineto 的库,但我无法获得我需要的东西。这是我试过的代码。
import React, { Component } from "react";
import { SteppedLineTo } from "react-lineto";
import { Segment, Grid, GridColumn, GridRow } from "semantic-ui-react";
const style = {
delay: true,
borderColor: "#ddd",
borderStyle: "solid",
borderWidth: 3
};
class App extends Component {
render() {
return (
<Grid>
<GridRow>
<GridColumn width={1} />
<GridColumn width={14}>
<Segment raised compact className="A">
Comapny A
</Segment>
<Segment raised compact className="B" style={{ margin: "20px" }}>
Comapny B
</Segment>
<Segment raised compact className="C" style={{ margin: "40px" }}>
Comapny C
</Segment>
<Segment raised compact className="D" style={{ margin: "20px" }}>
Comapny D
</Segment>
<Segment raised compact className="E" style={{ margin: "0px" }}>
Company E
</Segment>
<SteppedLineTo
from="A"
to="B"
fromAnchor="left"
toAnchor="0 50%"
orientation="h"
{...style}
/>
<SteppedLineTo
from="A"
to="C"
fromAnchor="left"
toAnchor="0 50%"
orientation="h"
{...style}
/>
</GridColumn>
<GridColumn width={1} />
</GridRow>
</Grid>
);
}
}
export default App;
这呈现了这样的东西
我怎样才能做到这一点?除了使用这个库还有其他选择吗?也许是一个普通的 CSS 技巧?
最佳答案
很酷的挑战!
这是使用纯 React 和 CSS 实现的方法。此解决方案仅适用于没有循环的树,这似乎是您的用例。
我们的想法是从一棵树开始,用渲染框和链接所需的所有信息丰富和展平节点,然后我们通过 CSS 使用 position: absolute;
放置这些信息。 .
希望对您有所帮助。
这是最终的输出,盒子高度为 20px,盒子之间的间距为 10px,链接偏移量为 5px(盒子侧面与链接附件之间的空间)。
<div style="position: relative;"><div style="position: absolute; left: 0px; top: 0px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; top: 20px; left: 4px; height: 105px; border-left: 1px solid grey;"></div>Parent</div><div style="position: absolute; left: 10px; top: 30px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div><div style="position: absolute; top: 20px; left: 4px; height: 15px; border-left: 1px solid grey;"></div>Child 1</div><div style="position: absolute; left: 20px; top: 60px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Grandchild 1</div><div style="position: absolute; left: 10px; top: 90px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Child 2</div><div style="position: absolute; left: 10px; top: 120px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div><div style="position: absolute; top: 20px; left: 4px; height: 45px; border-left: 1px solid grey;"></div>Child 3</div><div style="position: absolute; left: 20px; top: 150px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Grandchild 2</div><div style="position: absolute; left: 20px; top: 180px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Grandchild 3</div></div>
下面的代码👇
import React from 'react'
// Example:
//
// const topLevelNode = {
// text: 'Parent',
// children: [
// {
// text: 'Child 1',
// children: [
// {
// text: 'Grandchild 1',
// },
// ],
// },
// {
// text: 'Child 2',
// },
// ],
// }
//
// flatten(enrich(topLevelNode))
//
// [
// { text: 'Parent', depth: 0, descendentsCount: 3, heightDiffWithLastDirectChild: 3 },
// { text: 'Child 1', depth: 1, descendentsCount: 1, heightDiffWithLastDirectChild: 1 },
// { text: 'Grandchild 1', depth: 2, descendentsCount: 0, heightDiffWithLastDirectChild: 0 },
// { text: 'Child 2', depth: 1, descendentsCount: 0, heightDiffWithLastDirectChild: 0 },
// ]
// Enrich nodes with information needed for render
const enrich = (node, depthOffset = 0) => {
if (!node.children) {
return {
...node,
depth: depthOffset,
descendentsCount: 0,
heightDiffWithLastDirectChild: 0,
}
}
const enrichedChildren = node.children.map((child) => enrich(child, depthOffset + 1))
const descendentsCount = node.children.length + enrichedChildren.reduce(
(acc, enrichedChild) => acc + enrichedChild.descendentsCount,
0,
)
const heightDiffWithLastDirectChild = descendentsCount - enrichedChildren[node.children.length - 1].descendentsCount
return {
...node,
children: enrichedChildren,
depth: depthOffset,
descendentsCount,
heightDiffWithLastDirectChild,
}
}
// Flatten nodes with a depth first search
const flatten = (node) => {
const { children = [], ...nodeWithoutChildren } = node
return [
{ ...nodeWithoutChildren },
...children.map((childNode) => flatten(childNode)).flat(),
]
}
const boxHeight = 20
const boxGap = 10
const linkPositionOffset = 5
const LinkedBox = ({ node, order }) => (
<div
style={{
position: 'absolute',
left: `${node.depth * boxGap}px`,
top: `${order * (boxHeight + boxGap)}px`,
height: `${boxHeight}px`,
border: '1px solid grey',
borderRadius: '2px',
padding: '10px',
boxSizing: 'border-box',
display: 'flex',
alignItems: 'center',
}}
>
{node.depth > 0 && (
<div
style={{
position: 'absolute',
left: `-${boxGap - linkPositionOffset}px`,
top: `${linkPositionOffset - 1}px`,
width: `${boxGap - linkPositionOffset}px`,
borderTop: 'solid 1px grey',
}}
/>
)}
{node.heightDiffWithLastDirectChild > 0 && (
<div
style={{
position: 'absolute',
top: `${boxHeight}px`,
left: `${linkPositionOffset - 1}px`,
height: `${boxGap + (node.heightDiffWithLastDirectChild - 1) * (boxGap + boxHeight) + linkPositionOffset}px`,
borderLeft: 'solid 1px grey',
}}
/>
)}
{node.text}
</div>
)
const Diagram = ({ topLevelNode }) => (
<div style={{ position: 'relative' }}>
{flatten(enrich(topLevelNode)).map((enrichedNode, order) => (
<LinkedBox node={enrichedNode} order={order} key={JSON.stringify(enrichedNode)} />
))}
</div>
)
export default () => (
<Diagram
topLevelNode={{
text: 'Parent',
children: [
{
text: 'Child 1',
children: [
{ text: 'Grandchild 1' },
],
},
{ text: 'Child 2' },
{
text: 'Child 3',
children: [
{ text: 'Grandchild 2' },
{ text: 'Grandchild 3' },
],
},
],
}}
/>
)
关于html - 如何在两个组件之间画线,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57134184/