首页 > 解决方案 > 绝对定位于复杂结构

问题描述

我有一个非常复杂的 UI 控件需要开发。左半部分是一个 TreeTable 组件,由一个<table>元素组成。右半部分是一个SVG组件,用于显示左 treeTable 的每一行的数据。双方需要根据要求相对独立,但需要在同步滚动等方面表现得像一个人。

因此,整体结构看起来像这样

<div>
   <div id="leftComponent">
      <table>...</table
   </div>
   <div id="rightComponent">
      <svg>...</svg>
   </div>
</div>

我为超过 1m 行的大型数据集添加了性能优化,这样只有任一侧视口中的行才会被实际渲染并在 DOM 中。

这会导致以下问题:

  1. 当我在右侧滚动时,元素是绝对定位的,因此我可以使用原生平滑滚动。例如,一行的一半可能在视口顶部可见,然后是所有其他行,然后另一半行在视口底部可见。如何以某种方式偏移 leftComponent 表格内容,以便表格的行再次与右侧的行匹配?'s上的绝对定位<tr>不起作用。我还实现了光栅化滚动,以便用户只能滚动 rowHeight 的倍数,但这个想法很快就被否决了。<table>用类似的东西偏移整个元素<table style="margin-top:1337px">也不起作用,因为<table>标题不会在顶部粘住。有没有更好的方法?
  2. 由于两边都是相对独立的,所以每一边都有自己的滚动条。leftComponent 的 y 轴滚动条是不需要的,但它会干扰 UI 的整体感觉。简单地将其设置为overflow-y: hidden不起作用,因为现在我无法在左侧滚动。我对此的唯一想法是从中人工捕获并为 rightComponentWheelEvent创建一个。ScrollEvent我也在scrollbar-width: none;CSS 中玩过,但这会禁用两个滚动条。x 轴滚动条需要在视觉上和功能上保持不变,只是 y 轴滚动条需要在视觉上保持不变,但在功能上仍然保持不变。我还尝试用 隐藏 y 轴滚动条margin-right: -20px,这确实有效,但对我来说似乎非常 hacky。

总结一下:

  1. 双方相对独立,需要保持这种状态,但需要表现得“同步”
  2. 两个组件之间不应该有视觉上的 y 轴滚动条,但我仍然需要能够在左侧组件上滚动
  3. 我还需要能够将左侧<table>部分滚动/偏移任意数量的像素以匹配可以自由滚动的右侧。
  4. 如果可能的话,应该避免完全重写左侧并将<table>结构转换为<div>可以绝对定位的结构,因为这将是很多工作。
  5. 我仍然需要能够对 leftComponent 应用我的性能改进,以便仅渲染视口中的 X 行,但在正确的 y 位置与右侧保持同步。

关于这个有什么好主意吗?

编辑:根据要求,我添加了一些屏幕截图。 示例 1 示例 2

解释。在第一张图片中,您可以看到所有内容都滚动到顶部。每侧的两行都完美匹配。在第二张图片中,您会看到我所说的将行向下滚动一半的意思。最上面的行只有几个像素可见。自然地,我希望它看起来像第一张图片,两行都完美对齐。但是,由于左侧组件只有一个<table>元素,一旦我的性能优化处于活动状态,我无法将<tr>元素向下定位几个像素以使其与右侧匹配。此外,中间的滚动条需要移动,如上所述,但 x 轴滚动仍然需要在左右组件中都处于活动状态。

如果您需要更多信息,请询问我。

标签: htmlcsssvg

解决方案


这是一个纯 JS 模型,使用一种scrollTop方法,正如我之前建议的那样,结合行上的相对定位。

我认为这是合理的自我解释。如果您有任何问题,请告诉我。

const leftComponent = document.getElementById("leftComponent");
const rightComponent = document.getElementById("rightComponent");
const svg = rightComponent.querySelector("svg");

const theadHeight = leftComponent.querySelector("thead").offsetHeight;
const tbodyRowHeight = leftComponent.querySelector("tbody tr").offsetHeight;

const numRows = 100;    // Number of rows in th table
document.documentElement.style.setProperty('--table-height', (theadHeight + numRows * tbodyRowHeight) + 'px');


// Capture rightComponent scroll events
rightComponent.addEventListener('scroll', evt => {
  // Calculate top row index number based on scroll offset and row height
  const topRowIndex = Math.floor(rightComponent.scrollTop / tbodyRowHeight);
  // Work out how many pixels into the top row that the scroll position is
  const scrollFractionalAdjustment = - (rightComponent.scrollTop % tbodyRowHeight);
  // Set the position relative top value for the table rows.
  // This will adjust their vertical position on the page based on scroll fractional offset we just calculated.
  document.documentElement.style.setProperty('--scroll-fractional-adjustment', scrollFractionalAdjustment + 'px');
  // Update the table and graph based on out top row index
  populateTableRows(topRowIndex);
  populateGraph(topRowIndex);
});



function populateTableRows(topRowIndex)
{
  let n = topRowIndex;
  const rows = leftComponent.querySelectorAll("tbody tr");
  rows.forEach((row, i) => {
    const cols = row.querySelectorAll("td");
    cols[0].textContent = "row" + n + " with very long text";
    cols[1].textContent = "row" + n;
    n++;
  });
}


function populateGraph(topRowIndex)
{
}


// Initial setup
populateTableRows(0);
populateGraph(0);
:root {
  --scroll-fractional-adjustment: 0px;
  --table-height: 10000px;
}

html, body {
  margin: 0;
}

body {
  font-family: sans-serif;
  font-size: 20px;
}
th, td {
  height: 36px;
  line-height: 36px;
  padding-top: 0;
  padding-bottom: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
th {
  background-color: #aaa;
  color: white;
}

.system {
  display: flex;
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

#leftComponent.
#rightComponent {
  height: var(--table-height);
}

#leftComponent {
  width: 30%;
  height: 100%;
  overflow: scroll hidden;
}
#leftComponent tbody {
  display: block;
  overflow: hidden;
}
#leftComponent tbody td {
  position: relative;
  top: var(--scroll-fractional-adjustment);
}
#leftComponent table th,
#leftComponent table td
{
  max-width: 150px;
}

#rightComponent {
  width: 70%;
  overflow: scroll;
}
#rightComponent .rightContainer {
  width: 100%;
  height: var(--table-height);
}
#rightComponent svg {
  position: fixed;
}
<div class="system">
   <div id="leftComponent">
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>ID Blub</th>
            <th>Foo</th>
            <th>Sort string</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>row0 with very long text</td>
            <td>row0</td>
            <td>Foo something</td>
            <td>r2u58</td>
          <tr>
            <td>row0 with very long text</td>
            <td>row0</td>
            <td>Foo something</td>
            <td>r2u58</td>
          </tr>
          <tr>
            <td>row0 with very long text</td>
            <td>row0</td>
            <td>Foo something</td>
            <td>r2u58</td>
          </tr>
          <tr>
            <td>row0 with very long text</td>
            <td>row0</td>
            <td>Foo something</td>
            <td>r2u58</td>
          </tr>
          <tr>
            <td>row0 with very long text</td>
            <td>row0</td>
            <td>Foo something</td>
            <td>r2u58</td>
          </tr>
        </tbody>
      </table>
   </div>
   <div id="rightComponent">
      <div class="rightContainer">
        <svg>
          <circle cx="50" cy="50" r="50"/>
        </svg>
      </div>
   </div>
</div>


推荐阅读