【问题标题】:Expanding div on hover in a flex row wrap container causes shifting of children elements在 flex 行包装容器中悬停时展开 div 会导致子元素的移动
【发布时间】:2020-08-28 05:00:21
【问题描述】:

我在编写一些 css/html 元素时遇到了这个问题。一直在网上搜索潜在的解决方案,但似乎没有一个能解决我的问题。

我有动态数量的子元素需要适合父元素(弹性容器)进行显示。子元素有点像徽章,您可以在其中显示信息。悬停时,子元素应展开宽度过渡,并显示一个实用程序图标供我单击。到目前为止一切都很好。问题是悬停时,因为孩子会展开,如果它是 flex 中行的“最后一个”孩子,它将移动到下一行并完全展开,我无法再到达图标。

有什么解决办法吗?我尝试对孩子施加负边距以欺骗它留在它所在的同一行,但孩子本身的整个身体在溢出时被父容器切断(它看起来也不是很好)。

我还尝试强制每行仅包含 n 个子元素(例如每行 2 个子元素),但这只会降低问题发生的机会,并不能有效地消除它。

我希望寻找的是保持宽度过渡,并允许动态数量的子元素填充到父容器元素中,并在每行填充尽可能多的子元素(因此是 flex 行换行),而所有子元素都是向左冲。

演示(下)中的“&”是图标;请注意,悬停时无法到达某些行末尾的某些子元素,因为它们会进入下一行。

.flex-container {
  height: 100px;
  width: 400px;
  overflow-y: auto;
  display: flex;
  flex-flow: row wrap;
}

.flex-badge {
  display: inline-flex;
  margin-right: 0.5vw;
  margin-bottom: 0.5vh;
  background-color: lightblue;
  border: 1px solid black;
  cursor: pointer;
}

.hiddenIconTray {
  display: flex;
  overflow: hidden;
  width: 0;
  transition: width 0.5s;
}

.flex-badge:hover > .hiddenIconTray {
  width: 20px;
}

.flex-badge:hover {
  background-color: yellow;
}
<div class="flex-container">

  <div class="flex-badge">Lorem ipsum dolor sit amet
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">ipsum dolor sit

    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem ipsum dolor sit amet

    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">ipsum dolor sit amet

    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem ipsum dolor sit amet
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem ipsum dolor sit amet

    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem ipsum dolor sit amet
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem

    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>
  
  <div class="flex-badge">ipsum dolor
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">ipsum dolor
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem ipsum
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem ipsum dolor sit amet
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">ipsum dolor
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

  <div class="flex-badge">Lorem ipsum dolor sit amet
    <div class="hiddenIconTray">
      &nbsp;
      <i class="icon">&</i>
    </div>
  </div>

</div>

谢谢!

【问题讨论】:

  • 如果有机会,请检查我在解决方案中提供的两个示例。

标签: html css flexbox row


【解决方案1】:

由于您已指定宽度:&lt;div class="flex-container"&gt; 的 40vw,因此此 div 的子元素将尝试适应指定的 40vw 区域。

我建议您设置min-width: 40vwmax-width: 50vw,这样当您将鼠标悬停在子元素上时,父 div 将拉伸到 50vw。

祝你好运!

【讨论】:

    【解决方案2】:

    这是正常的 flex 行为,如果元素太大,它会向下流动。你这样做的唯一方法是节省图标出现的空间。

    https://jsfiddle.net/rm9j8cb3/2/

    检查它是如何完成的。您在徽章元素上有 padding-right,通过 ::before 元素覆盖以“隐藏它”,使其看起来像空白。然后,当悬停徽章时,图标从左侧出现,在该 ::before 元素上方并显示自身。如果项目应该太大,它就会掉下来。

    我希望这对你有用。

    【讨论】:

      【解决方案3】:

      您可以在这种情况下使用的一个巧妙的解决方法是在您需要之前分配您想要使用的间距量。

      这里需要几个步骤……

      • 我们知道这些 flex-badge 元素在展开后可能会换行到下一行,因此我们可以预期可能会添加一个 margin-right 值,该值与我们知道宽度将增加的确切大小相同,您将其设置为 20px .因为您已经设置了一些边距来分隔这些 flex 元素,所以我将您的原始边距移动到父级上设置的 gap 属性,这是该属性的预期用途。所有现代浏览器都正式支持此功能 (caniuse.com)。
      • 为了使margin 发生这种变化,我们还必须考虑将margin-right: 20px 添加到所有flex-badge 元素会在每个元素之间添加不需要的间隙这一事实。我们还可以通过将margin-left: -20px 抢先添加到所有flex-badge 元素来抵消这种副作用。
      • 在悬停时,我们希望将悬停元素上的 margin-right 值转换为 0,以随着元素的 hiddenIconTray 子元素填充的空间增加以填充空白而流畅地减少预分配的空间。
      • 由于这也会导致其行中的第一个元素也从父容器的左侧流出,因此我们将其与应用于父元素的 padding-left: 20px 配对。

      就是这样!一个功能齐全的容器,其中元素提前换行到下一行,如果他们会在悬停时这样做的话。瞧✨

      .flex-container {
        height: 100px;
        width: 400px;
        overflow-y: auto;
        display: flex;
        gap: 0.5vw 0.5vh;
        flex-flow: row wrap;
        padding-left: 20px;
      }
      
      .flex-badge {
        display: inline-flex;
        background-color: lightblue;
        border: 1px solid black;
        cursor: pointer;
        transition: margin-left 0.5s, margin-right 0.5s;
      }
      
      .flex-badge {
        margin-left: -20px;
      }
      
      .flex-badge {
        margin-right: 20px;
      }
      
      .hiddenIconTray {
        display: flex;
        overflow: hidden;
        width: 0;
        transition: width 0.5s;
      }
      
      .flex-badge:hover {
        margin-right: 0;
        background-color: yellow;
        z-index: 1;
      }
      
      .flex-badge:hover > .hiddenIconTray {
        width: 20px;
      }
      <div class="flex-container">
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">ipsum dolor sit
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">ipsum dolor sit amet
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
        
        <div class="flex-badge">ipsum dolor
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">ipsum dolor
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">ipsum dolor
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
      </div>

      最后一点仍然需要更多关注。这些项目现在相互重叠,而不是随着它们的增长和收缩而相互推拉。不幸的是,仅使用 CSS 是无法避免的,因为这是我们执行的边距调整的结果。

      但是,如果您愿意使用一点 JavaScript,我们可以借助一个简单的循环来完美地工作基于弹性行而不是弹性项目。这是 CSS 中尚不支持的功能,但希望在接下来的行中,我们将获得用于 flex 和 grid 行和列的伪类。这与即将推出的 :has() 伪选择器搭配使用,仅使用 CSS 即可实现。

      我们还可以使用所有现代浏览器都支持的内置 ResizeObserver API (caniuse.com)。

      它在行动:

      const flexBadgesContainer = document.querySelector('.flex-container');
      const flexBadges = document.querySelectorAll('.flex-badge');
      const markFirstOfFlexRows = () => {
        let currentOffsetTop = null;
        for (const badge of flexBadges) {
          if (badge.offsetTop !== currentOffsetTop) {
            currentOffsetTop = badge.offsetTop;
            badge.classList.add('flex-row-first');
          }
        }
      }
      markFirstOfFlexRows();
      new ResizeObserver(() => {
        for (const badge of flexBadges) badge.classList.remove('flex-row-first');
        markFirstOfFlexRows();
      }).observe(flexBadgesContainer);
      .flex-container {
        height: 100px;
        width: 400px;
        overflow-y: auto;
        resize: both;
        border: 1px solid #000;
        display: flex;
        gap: 0.5vw 0.5vh;
        flex-flow: row wrap;
        padding-left: 20px;
      }
      
      .flex-badge {
        display: inline-flex;
        background-color: lightblue;
        border: 1px solid black;
        cursor: pointer;
        transition: margin-left 0.5s, margin-right 0.5s;
      }
      
      .flex-badge {
        margin-left: -20px;
      }
      
      .flex-badge {
        margin-right: 20px;
      }
      
      .hiddenIconTray {
        display: flex;
        overflow: hidden;
        width: 0;
        transition: width 0.5s;
      }
      
      .flex-badge:hover {
        margin-right: 0;
        background-color: yellow;
        z-index: 1;
      }
      
      .flex-badge:hover ~ .flex-badge:not(.flex-row-first) {
        margin-left: 0;
        margin-right: 0;
      }
      
      .flex-badge:hover ~ .flex-badge.flex-row-first + .flex-badge {
        margin-left: -20px;
      }
      
      .flex-badge:hover > .hiddenIconTray {
        width: 20px;
      }
      <div class="flex-container">
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">ipsum dolor sit
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">ipsum dolor sit amet
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
      
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
        
        <div class="flex-badge">ipsum dolor
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">ipsum dolor
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">ipsum dolor
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
        <div class="flex-badge">Lorem ipsum dolor sit amet
          <div class="hiddenIconTray">
            &nbsp;
            <i class="icon">&</i>
          </div>
        </div>
      
      </div>

      我在上面的示例中将resize: both 添加到.flex-container 元素中,因此您可以对此进行试验,并看到它在父元素.flex-container 大小发生变化时也能正常工作。

      【讨论】:

      • 很好,详细的答案 - 谢谢布兰登!正如我怀疑的那样,这个问题比最初看起来更复杂。我得出了与您相同的结论:关键是要确定哪些元素是其行中的最后一个(或第一个)元素,为此您需要的不仅仅是 CSS。我最终采用了类似的方法,尽管在我的例子中使用的是 Vue 框架而不是原生浏览器 JS。 +1 用于工作示例和检查浏览器支持。有一些赏金!
      • @user2993689 谢谢!是的,我也看过这个,乍一看认为这很简单,只使用 CSS。只是向我展示了更多关于 flex/grid 限制的信息。我希望在未来几年看到限制扩大。
      猜你喜欢
      • 2017-02-24
      • 2018-06-17
      • 1970-01-01
      • 2021-08-02
      • 1970-01-01
      • 2018-02-18
      • 2012-08-19
      • 2019-05-22
      • 2021-06-29
      相关资源
      最近更新 更多