html - 如何在两个组件之间画线

标签 html css reactjs treeview

我有一些组件(semantic-ui-react 中的分段组件),我正在尝试创建一个树状组件,它将由下图中给出的线组成。

enter image description here

我找到了一个名为 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;

这呈现了这样的东西

enter image description here

我怎样才能做到这一点?除了使用这个库还有其他选择吗?也许是一个普通的 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/

相关文章:

html - 如何在 CSS 中设置此按钮的样式以显示为按下?

Javascript 在不滚动的情况下更改 window.location.hash,但仍会触发 CSS :target pseudo-class

css - 没有导航栏元素的 Bootstrap 导航切换

javascript - "Process is not defined"与react-chartjs-2 CDN

javascript - 拖动源容器时,jsPlumb 源连接器不会移动

html - 将图标与简单的搜索栏对齐

javascript - 在左边移动一个 div,在一个 div 的右边移动另一个

javascript - 如何在 React 组件上设置 -webkit-overflow-scrolling 内联样式

function - react : Cant call a function inside child component

javascript - ReactJS 应用程序中的 CSS 不应用文本颜色