javascript - 重新计算/回流绝对定位元素网格的有效方法

标签 javascript dom

问题陈述

我有一个网格,元素的 Angular 可能对齐也可能不对齐,但所有的边都接触所以没有间隙。也没有重叠。例如,可能有这样的东西:

+----+-------+
|    |   B   |
|    +---+---+
| A  | C |   |
|    |---| D |
|    | E |   |
+----+---+---+

网格是通过绝对定位的元素创建的。 (我意识到通过树创建这样的网格可能更容易,其中父节点是与相邻元素形成矩形的容器,但我认为这可能会限制我能够调整大小的方式元素 - 我稍后会解释)。

我希望能够调整单个元素的大小并让相邻元素重新计算它们的尺寸,以便它们在不留间隙的情况下捕捉到新的元素尺寸。例如,假设我们正在调整元素 C 的大小:

  • 如果我将 C 的左边缘向 A 调整,我希望 A 水平收缩。由于 A 收缩,B 和 E 都必须向 A 扩展以填充该空隙。
  • 如果我向下调整 C 的底部边缘,E 应该会缩小,不会影响其他元素。
  • 如果我将 C 的右边缘调整为 D,D 应该缩小,E 和 C 应该增长到那个空隙。
  • 如果我将 C 的上边缘调整为 B,B 应该垂直收缩,而 D 应该随着 C 扩展。

为什么树结构不起作用

现在,如前所述,我意识到将这些元素嵌套在容器元素(树状结构)中会更容易处理上述情况。我认为树结构对我不起作用的原因(除了我已经有太多依赖绝对位置的代码之外)是我不希望以下情况的大小调整依赖于底层树恰好在下面的结构:

+---+---+---+
|   |   |   |
+---+---+---+
|   |   |   |
+---+---+---+
|   |   |   |
+---+---+---+

对于一棵树,这个例子是行不通的,因为调整中间 block 的大小会调整碰巧共享同一父级/容器的元素的大小,即使它们不需要调整大小也是如此。

当前的想法/工作

我正在尝试弄清楚如何计算哪些附加元素需要以一种有效的方式为我的绝对元素调整大小。我正在考虑以下内容:

  • 在给定方向上调整元素的大小后,取相应的边并沿该边执行 document.elementsFromPoint() 从一个 Angular 到另一个 Angular 的二进制搜索模式,直到元素返回最小点与每个采样点的最大点相同(如果它们不相同,则在中点采样一个新点并继续递归进行)。这组元素将包含该元素由于调整大小而侵入的所有元素(因此它们需要被相反的边缘缩小)
  • 在缩小元素的调整大小后,沿原始边缘(调整大小之前)执行相同类型的二进制边缘遍历,但在与调整大小相反的方向上执行几个像素(这应该命中需要增长的元素填补空白)
  • 对于主要元素,它将是上面的一个或另一个项目符号(收缩或增长),但下一步是寻找“副作用”,如果相邻元素的边缘超出了元素的边缘原始元素,必须沿着此扩展执行相同类型的分析。如果我们有类似砖 block 的图案,这反过来可能会在同一边缘引起新的副作用。

第一个项目符号中解释的搜索是这样的,然后我会检查副作用:

function binarySearch(min, max, resizedElement, otherCoord, vertical=false) {

    function getElement(x, y) {
        if (vertical) {
            let tmp = x;
            x = y;
            y = tmp;
        }
        // we know there will always be an element touching, so this
        // should only throw an error if we pass bad otherCoord
        return document.elementsFromPoint(x, y).filter(e => e !== resizedElement)[0];
    }

    let elements    = new Set(),
        startIndex  = min,
        startElement= getElement(min, otherCoord),
        stopIndex   = max,
        stopElement = getElement(max, otherCoord);

    if (startElement === stopElement) {
        elements.add(startElement);
    } else {
        let middle = Math.floor((stopIndex + startIndex)/2),
            left   = binarySearch(min, middle, resizedElement, otherCoord, vertical),
            right  = binarySearch(middle, max, resizedElement, otherCoord, vertical);
        elements   = new Set([...elements, ...left, ...right]);
    }

    return elements;
}

我是不是太复杂了?有更好的方法吗?这可以通过树木实现吗,我只是没有看到它?

最佳答案

如果底层结构没有改变,那么您可能可以使用树结构来解决问题 css flexbox .

Flexbox 是一个非常强大的布局工具,是现代浏览器引擎的原生布局工具。您使用 css 声明您的布局,使用 display: flex; 以及其他简单的 css。

CSS trick 的 flexbox 教程比我解释得更好所以请引用 this to understand what's going on .下面的代码更像是一个演示。

想法是改变元素的 flexbox 样式。要调整元素的大小,请使用 javascript 更改 flex-basis 下面我只有按钮来显示概念证明,但最终,您想要使用鼠标事件来调整元素的大小。您可以将 event.clientX 除以容器宽度 (container.clientWidth) 以获得鼠标相对于容器的位置百分比,并将该值用于 flexbasis .

在下面的演示中,我使用一个变量来跟踪元素 .a.a-complement 的 flexbasis。单击按钮时,每个元素的 flexbasis 都会更新。它们都从 50% 50% 开始,然后按每个按钮增加/缩小 10%。此示例可以扩展为包含使用相同技术调整所有元素的大小。他们都会尊重彼此的尺寸,并且都没有间隙等。

故事的寓意:让布局引擎为您完成工作!除非确实需要,否则不要使用绝对定位。

解决树结构问题:您可以重组树结构,在需要时将 div 移动到其他 div 中。如果这使事情过于复杂,那么不幸的是,浏览器可能没有对您的文档结构的 native 支持。

但它可能在未来...

如果 flexbox 不能解决您的问题,那么更具实验性的 CSS GRID可能,但请注意,CSS 网格仅在最新的浏览器中实现,没有移动浏览器,这可能适合您的目标受众。

let aBasis = 0.5;
const elementA = document.querySelector('.a');
const aComplement = document.querySelector('.a-complement');
document.querySelector('#left').addEventListener('click', () => {
  aBasis -= 0.1;
  elementA.style.flexBasis = (aBasis * 100) + '%';
  aComplement.style.flexBasis = ((1 - aBasis) * 100) + '%';
  console.log((aBasis * 100) + '%', ((1 - aBasis) * 100) + '%');
});

document.querySelector('#right').addEventListener('click', () => {
  aBasis += 0.1;
  elementA.style.flexBasis = (aBasis * 100) + '%';
  aComplement.style.flexBasis = ((1 - aBasis) * 100) + '%';
  console.log((aBasis * 100) + '%', ((1 - aBasis) * 100) + '%');
});
.a {
  display: flex;
  background-color: red;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.b {
  display: flex;
  background-color: blue;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.c {
  display: flex;
  background-color: green;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.d {
  display: flex;
  background-color: yellow;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.e {
  display: flex;
  background-color: orange;
  flex: 1;
  justify-content: center;
  align-items: center;
}

.h-container {
  display: flex;
  align-items: stretch;
  flex: 1;
}

.v-container {
  display: flex;
  flex: 1;
  flex-direction: column;
}

.container {
  display: flex;
  height: 200px;
  width: 200px;
  flex: 1
}

.example-container {
  display: flex;
  width: 100%;
}
<div class="example-container">
  <div class="container">
    <div class="h-container">
      <div class="a">
        <span>A</span>
      </div>
      <div class="a-complement v-container">
        <div class="b">
          <span>B</span>
        </div>
        <div class="h-container">
          <div class="v-container">
            <div class="c"><span>C</span></div>
            <div class="e"><span>E</span></div>
          </div>
          <div class="d"><span>D</span></div>
        </div>
      </div>
    </div>
  </div>
  <div>
    <button id="left">move a to the left</button>
    <button id="right">move a to the right</button>
  </div>
</div>

关于javascript - 重新计算/回流绝对定位元素网格的有效方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43061979/

相关文章:

javascript - 如果连接失败,我如何继续尝试从 Angular 服务订阅 signalR?

javascript - 每次用 .click() 触发?

javascript - jasmine/sinon 有没有办法 stub 嵌套函数调用?

php - 使用 DOM 在 PHP 中解析 RSS 提要

javascript - 替代 getElementsByTagName

javascript - php简单联系表单在浏览器刷新时重新提交

javascript - 在 Java 和 JavaScript 之间实现 AES

javascript - 突变观察者可以监听 "data-"属性的变化吗?

javascript - 等待 Angular 完成更新 DOM

javascript - 在 DOM 中可以使用 .notation 来获取/设置属性吗?