如何让 flex 容器中的项目允许其嵌套的子项出现在其下方?

How to have items in a flex container allow their nested children to appear below it onClick?

提问人:Dog 提问时间:7/14/2022 最后编辑:Dog 更新时间:7/16/2022 访问量:163

问:

我有一个带有 flex 方向行的 flex 容器,其中包含一系列呈现到屏幕的项目,如下图所示:

Image 1

单击其中一个项目后,我需要一系列项目出现在它下面。例如,单击“顶级项目 1”后,以下项目应如下图所示:

Image 2

虽然我知道可以将两个容器(一个包含所有顶级项目,另一个包含所有最低级别项目)彼此叠放在一起,并简单地更新底部的容器 onClick,但这会导致对齐问题添加更多项目或将非项目添加到顶层。

相反,我正在尝试找到一种方法,将底层项目嵌套在其关联的顶级项目中。这种方法有效,但显然它会向下推边界,如下图所示:

Image 3如何实现图像 2 onClick 的布局,但仍将单个底层项目 div 嵌套在其关联的顶级项目 div 中?

例如,以类似于以下方式的方式:

               <div>
                    <div>Top Level Item 1</div>
                    <div>
                        <div>bottom Level Item 1</div>
                        <div>bottom Level Item 2</div>
                    </div>
                </div>

如果边框导航上有高度,并且项目居中,则下拉项目上的绝对位置将不起作用。因此,我也在尝试找到一种方法来解释该用例。例如,如果在容器上放置高度,并在下拉列表项上放置 position: absolute,就会发生这种情况。

enter image description here

感谢您的任何见解!

JavaScript HTML CSS FlexBox 嵌套

评论

2赞 Nimarel 7/14/2022
您应该使用 position: absolute。也许寻找现有的下拉 css/js。
0赞 Dog 7/14/2022
位置:绝对可能是一个很好的起点。它目前似乎确实达到了正确的效果。

答:

2赞 Cristian-Florin Calina 7/14/2022 #1

使用确实是解决方案(我开始研究它并看到一个更快的评论)。position:absolute

我仍然想做挑战,让我知道这是否是你想要的结果。

document.querySelectorAll('.level').forEach(el => {
  el.addEventListener('click', (ev) => {
    const toggleNode = [...el.childNodes].filter(child => child.classList && child.classList.contains('secondary'));
    
    if (toggleNode.length) {
        toggleNode[0].classList[toggleNode[0].classList.contains('hidden') ? 'remove' : 'add']('hidden');
    } 
  })
})
.flex {
  position: relative;
  display: flex;
  align-items: center;
  border: 1px solid black;
  height: 130px;
}

.level {
  flex-basis: 33%;
  flex-grow: 1;
}

.secondary {
  position: absolute;
  top: 130px;
}

.hidden {
  display: none;
}
<div class="flex">
  <div class="level">
    <div>Top Level Item 1</div>
    <div class="secondary hidden">
      <div>bottom Level Item 1</div>
      <div>bottom Level Item 2</div>
    </div>
  </div>
  <div class="level">
      <div>Top Level Item 2</div>
      <div class="secondary hidden">
        <div>bottom Level Item 1</div>
        <div>bottom Level Item 2</div>
    </div>
  </div>
  <div class="level">
      <div>Top Level Item 3</div>
      <div class="secondary hidden">
        <div>bottom Level Item 1</div>
        <div>bottom Level Item 2</div>
    </div>
  </div>
</div>

评论

0赞 Dog 7/14/2022
绝对位置的问题在于,如果在具有边框的容器上放置一个高度,然后将项目对齐到其中的中心,则下拉项目将不再显示在带边框的容器下方。对不起,我应该将该部分添加到描述中。
0赞 Cristian-Florin Calina 7/14/2022
是已知的高度吗?还是动态高度?因为如果你知道高度,你可以把属性放在 css 中。所以如果你有,你会把绝对位置的div.(我也在这个例子中改变了答案)topheight: 100pxtop: 100px;
0赞 Dog 7/14/2022
我认为这绝对会将其定位为视口而不是容器。
1赞 Cristian-Florin Calina 7/14/2022
它是相对于第一个相对/绝对父级的。因此,如果您制作带有边框相对的容器,它将起作用(查看我更新的示例)
1赞 Simp4Code 7/14/2022
@Dog如果您不想使用,而是希望下拉列表将其下方的内容推送到页面下方(防止重叠),请查看我发布的答案position: absolute
2赞 Simp4Code 7/14/2022 #2

网格手风琴 (NO 位置: 绝对位置)

下拉列表是没有的,因此在展开时,它们下面的内容会在页面中向下推。position: absolute

要获得切换开关周围的实心边框,请在每个切换开关周围添加顶部和底部边框,然后使用伪选择器将左边框添加到和将右边框添加到:first-of-type:last-of-type

您可以通过将以下内容添加到任何父元素来设置希望顶级项使用的行数:.itemsstyle: --topLevelLines: [line number];

切换开关中超过行限制的文本将被截断。行限制下的文本将垂直居中。不设置值将默认为单行。

JavaScript 将检查是否有用于添加点击事件侦听器的下拉预设。如果没有下拉列表,则将获得一个类,并相应地应用一些样式.item.nodrop

const lists = Array.from(document.getElementsByClassName('items'));

lists.forEach( list => {
  const items = Array.from(list.children);
  
  items.forEach( item => {
    const toggle = item.getElementsByClassName('toggle')[0];
    const dropdown = item.getElementsByClassName('dropdown')[0];
    
    dropdown ? toggle.addEventListener('click', function () { item.classList.toggle('active') }) : toggle.classList.add('nodrop');
  });
});
* { box-sizing: border-box } body { font: 16px sans-serif; margin: 0 }


.items {
  display: flex;
  margin: auto auto -1px;
  max-width: 960px;
}
.items:last-of-type { margin: auto }
.item {
  display: grid;
  grid-template-rows: auto 1fr;
  flex: 1 0 0%;
  
  transition: background-color .25s ease-in-out;
}

.toggle {
  border-top: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  cursor: pointer;
  display: grid;
  height: calc( var(--topLevelLines, 1) * 1.25rem + ( .5rem * 2 ) );
  line-height: 1.25rem;
  padding: .5rem;
  place-items: center center;
  transition: padding .25s ease-in-out;
}
.toggle.nodrop { cursor: not-allowed }
.toggle:not(.nodrop)::after {
  content: "⬇️";
  font: 1em 'Segoe UI Emoji';
  grid-area: 1/1/-1/-1;
  margin-left: auto;
  opacity: 0;
  transition: transform .25s ease-in-out;
}
.toggle:not(.nodrop):hover { background-color: rgba(0, 0, 0, 0.125) }
.toggle:not(.nodrop):hover::after { opacity: 1 }

.toggle > span {
  grid-area: 1/1/-1/-1;
  display: -webkit-box;
  -webkit-line-clamp: var(--topLevelLines, 1);
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-right: auto;
}
.toggle.nodrop > span { opacity: 0.5 }

.dropdown {
  overflow: hidden;
  transition: background-color .25s ease-in-out;
}
.dropdown > div {
  cursor: crosshair;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  padding: 0 .5rem;
  transform: translateY(-100%);
  transition: opacity .25s ease-in-out, padding .25s ease-in-out, transform .25s ease-in-out;
}
.dropdown > div:hover { background-color: rgba(0, 0, 0, 0.125) }

.active { box-shadow: inset 0 0 0 .5px rgba( 0, 0, 0, 0.25 ) }
.active .toggle { background-color: rgba(0, 0, 0, 0.25) }
.active .toggle::after { transform: scaleY(-1) }
.active .dropdown {
  background-color: rgba(0, 0, 0, 0.125);
  padding: .25rem 0
}
.active:hover { background-color: rgba(125,255,125,0.25) }

.active .dropdown > div {
  opacity: 1;
  padding: .25rem .5rem;
  max-height: none;
  transform: translateY(0);
}
.active .dropdown > div:last-of-type { margin-bottom: 0 }

.item:first-of-type .toggle { border-left: 1px solid currentColor }
.item:last-of-type .toggle { border-right: 1px solid currentColor }
<div class="items">
  <div class="item">
    <div class="toggle"><span>Toggle 1</span></div>
    <div class="dropdown">
      <div>bottom Level Item 1</div>
      <div>bottom Level Item 2</div>
    </div>
  </div>
  <div class="item">
    <div class="toggle"><span>Toggle 2 (no children)</span></div>
  </div>
  <div class="item">
    <div class="toggle"><span>Toggle 3</span></div>
    <div class="dropdown">
      <div>bottom Level Item 6</div>
      <div>bottom Level Item 7</div>
      <div>bottom Level Item 8</div>
      <div>bottom Level Item 9</div>
    </div>
  </div>
</div>

<div class="items" style="--topLevelLines: 2">
  <div class="item">
    <div class="toggle"><span>Toggle 4 with lots of text in it that will eventually cause it to overflow the line restrictions rhoncus arcu cum ac vestibulum volutpat a luctus parturient nascetur condimentum dui penatibus habitant vestibulum vestibulum euismod id parturient porta ullamcorper viverra ultricies per integer a. A non sit adipiscing dis orci eget ac mi mauris nunc vestibulum gravida nascetur a nisl proin sociis adipiscing netus sit. Per id est posuere a a varius habitasse imperdiet laoreet consectetur vestibulum vestibulum nec a sit euismod. Consectetur vel vitae interdum mollis dis integer etiam non adipiscing vestibulum tempor ligula ultricies laoreet semper libero ligula consequat mollis a scelerisque elit ac sed viverra. Orci per suspendisse a eleifend mus a primis a orci nam augue condimentum leo ullamcorper sem volutpat sit condimentum vestibulum a velit eros nibh non mi. Sodales lorem malesuada bibendum sem parturient ligula dis a vulputate orci suspendisse curabitur varius porttitor vestibulum adipiscing parturient nam nam cursus sagittis.</span></div>
    <div class="dropdown">
      <div>bottom Level Item 10</div>
      <div>bottom Level Item 11</div>
    </div>
  </div>
  <div class="item">
    <div class="toggle"><span>Toggle 5</span></div>
    <div class="dropdown">
      <div>bottom Level Item 12</div>
      <div>bottom Level Item 13</div>
      <div>bottom Level Item 14</div>
    </div>
  </div>
  <div class="item">
    <div class="toggle"><span>Toggle 6</span></div>
    <div class="dropdown">
      <div>bottom Level Item 15</div>
      <div>bottom Level Item 16</div>
      <div>bottom Level Item 17</div>
      <div>bottom Level Item 18</div>
    </div>
  </div>
  <div class="item">
    <div class="toggle"><span>Toggle 7<br>(no children)</span></div>
  </div>
</div>

<div class="items" style="--topLevelLines: 3">
  <div class="item">
    <div class="toggle"><span>Toggle 8 with lots of text in it that will eventually cause it to overflow the line restrictions rhoncus arcu cum ac vestibulum volutpat a luctus parturient nascetur condimentum dui penatibus habitant vestibulum vestibulum euismod id parturient porta ullamcorper viverra ultricies per integer a. A non sit adipiscing dis orci eget ac mi mauris nunc vestibulum gravida nascetur a nisl proin sociis adipiscing netus sit. Per id est posuere a a varius habitasse imperdiet laoreet consectetur vestibulum vestibulum nec a sit euismod. Consectetur vel vitae interdum mollis dis integer etiam non adipiscing vestibulum tempor ligula ultricies laoreet semper libero ligula consequat mollis a scelerisque elit ac sed viverra. Orci per suspendisse a eleifend mus a primis a orci nam augue condimentum leo ullamcorper sem volutpat sit condimentum vestibulum a velit eros nibh non mi. Sodales lorem malesuada bibendum sem parturient ligula dis a vulputate orci suspendisse curabitur varius porttitor vestibulum adipiscing parturient nam nam cursus sagittis.</span></div>
    <div class="dropdown">
      <div>bottom Level Item 21</div>
      <div>bottom Level Item 22</div>
      <div>bottom Level Item 23</div>
    </div>
  </div>
  <div class="item">
    <div class="toggle"><span>Toggle 9</span></div>
    <div class="dropdown">
      <div>bottom Level Item 24</div>
      <div>bottom Level Item 25</div>
      <div>bottom Level Item 26</div>
      <div>bottom Level Item 27</div>
    </div>
  </div>
</div>

评论

1赞 Rene van der Lende 7/16/2022
非常好的广泛答案和很棒的演示!感谢您的辛勤工作...发布在 CodePen
0赞 Simp4Code 7/17/2022
@RenevanderLende哈哈谢谢!老实说,我对这个感到惊讶。它肯定可以使用一些移动工作,也许可以折叠成一个大的手风琴列表而不是网格。不过,我很荣幸您花时间制作了一支密码笔,非常感谢
1赞 Rene van der Lende 7/16/2022 #3

我在使用 (MDN: Details disclosure element) 时看到了这个问题,并想向您展示已接受答案的替代解决方案。<details><summary>

此示例使用一个元素作为由嵌套元素组成的三个主导航栏菜单的主父容器。由于这些元素已经包含打开/关闭机制,因此 Javascript 是可选的,并且仅在当前导航栏菜单应关闭时才需要,当用户选择另一个导航栏菜单时(“失去焦点”)。position: fixed<nav><details><summary>click.eventListener

Fixed/Absolute定位仅用于在视口顶部附加导航栏(可选,也可以)并绘制其边框。relative

除了一些令人眼花缭乱的东西外,导航栏菜单还使用常规/默认的CSS值。

代码片段(使用可选的 JS 切换活动菜单):

var currentDetail;

// top summary of menu only
document.querySelectorAll('.navmenu>summary').forEach(el => {
    // triggers before <details> 'toggle' event
    el.addEventListener("click", event => {
        // Parent of top summary
        const closest = event.currentTarget.closest('.navmenu');

        if (closest.open) {
            currentDetail = null; // all menus closed
        }
        else { // not null and different menu
            if ((currentDetail) && (currentDetail != closest)) {

                currentDetail.removeAttribute('open'); // close current open menu
             };
             currentDetail = closest; // save new (to be 'toggled') opened menu
        }
    });
});
*, ::before, ::after { -webkit-box-sizing: border-box; box-sizing: border-box }

html, body  { width: 100%; max-width: 100% }
body        { min-height: 100vh; margin: 0 }

:root {
    --doc-lh : 1.5;     /* document line-height */
    --nav-pad: .25rem;  /* navbar padding       */
    --nav-bd : 1px;     /* navbar border size   */

    --mnu-pad: 0.25rem; /* main menu padding */
    --mnu-br : 1em;     /* border radius     */
}

nav {
    top: 0; left: 0; z-index: 999;
    position: fixed; /* or 'relative' */
 
    width: 100%;  /* fill parent width */
    height: auto; /* just making sure */

    display: flex; justify-content: space-evenly;
    padding: var(--nav-pad);
 }
nav::after {
    position: absolute; inset: 0; content: ""; z-index: -1;

    /* parent padding + navmenu padding + border + line-height */
    height: calc(2 * var(--nav-pad) +
                 2 * var(--mnu-pad) +
                 2 * var(--nav-bd)  +
                 var(--doc-lh) * 1em);/**/

    border: var(--nav-bd) solid black;
}

nav details { align-self: flex-start }

.navmenu {
    padding: var(--mnu-pad) calc(3 * var(--mnu-pad));
}
.navmenu[open]>summary { /* to move it below the navbar */
    padding-bottom: calc( var(--nav-pad) + 2 * var(--mnu-pad) + var(--nav-bd) )
}

/******************/
/* Eye-candy only */
/******************/
details { width: min(14rem, 100%) }
summary { cursor: pointer }

details>details,
summary + * { padding-left: 1em }           /* saw-tooth layout */

.navmenu {
    background-color: hsl(0,0%,86.3%, .95); /* Gainsboro */
    border: 1px solid hsl(0,0%,76.3%, 1);   /* Gainsboro L-10 */
    border-radius: var(--mnu-br);
}
<nav>
      <details class="navmenu" id="det-01">
          <summary>menu 1</summary>
          <details>
              <summary>option 1.1</summary>
              <details>
                  <summary>option 1.1.1</summary>
                  <div>option text 1.1.1</div>
              </details>
              <details>
                  <summary>option 1.1.2</summary>
                  <div>option text 1.1.2</div>
              </details>
          </details>
          <details>
              <summary>option 1.2</summary>
              <div>option text 1.2</div>
          </details>
          <details>
              <summary>option 1.3</summary>
              <div>option text 1.3</div>
          </details>
      </details>

      <details class="navmenu" id="det-02">
          <summary>menu 2</summary>
          <details>
              <summary>option 2.1</summary>
              <details>
                  <summary>option 2.1.1</summary>
                  <div>option text 2.1.1</div>
              </details>
              <details>
                  <summary>option 2.1.2</summary>
                  <div>option text 2.1.2</div>
              </details>
          </details>
          <details>
              <summary>option 2.2</summary>
              <div>option text 2.2</div>
          </details>
          <details>
              <summary>option 2.3</summary>
              <div>option text 2.3</div>
          </details>
      </details>

      <details class="navmenu" id="det-03">
          <summary>menu 3</summary>
          <details>
              <summary>option 3.1</summary>
              <details>
                  <summary>option 3.1.1</summary>
                  <div>option text 3.1.1</div>
              </details>
              <details>
                  <summary>option 3.1.2</summary>
                  <div>option text 3.1.2</div>
              </details>
          </details>
          <details>
              <summary>option 3.2</summary>
              <div>option text 3.2</div>
          </details>
          <details>
              <summary>option 3.3</summary>
              <div>option text 3.3</div>
          </details>
      </details>
  </nav>