在嵌套的flexbox中滚动

8
我正在开发一个React应用程序,其中有一个带有选项卡内容的模态框,偶尔需要滚动。然而,问题在于我无法使正确的内容滚动。
模态框分为3个主要部分:
1. 头部 - 应始终可见于模态框顶部
2. 内容 - 应填充在“头部”和“页脚”之间的空间,但不应强制将任一部分隐藏
3. 页脚 - 应始终位于“内容”下方 - 这可能意味着位于模态框底部(如果“内容”填充剩余空间)或位于“内容”下方(如果“内容”未填充空间)
虽然这很容易实现(我已经这样做了),但问题在于“内容”不应该滚动,而是其内部的某些内容应该滚动。以下图片显示了预期行为的两个示例,其中一个具有应滚动的长内容,另一个没有。
我正在使用一个自定义的选项卡组件,在容器内呈现一些内容。这个容器(在下面的图片中是白色的)应该在需要时滚动。
我已经做的最好的可以在以下CodePen和示例代码中找到。我目前能够滚动包含所需可滚动内容的元素,但实际上无法使该内容滚动。如果您检查Pen,您会发现它(出于某种原因)超出了包含元素。

Modal Examples

代码示例

CodePen 示例

HTML

<div class="modal">
  <div class="background" />
  <div class="modal_content">
    <div class="header">Random header</div>
    <div class="tabbed_content">
      <div class="tabs">
        <div class="tab active">Tab 1</div>
        <div class="tab">Tab 2</div>
      </div>
      <div class="content">
        <div class="scrollable">
          Tabbed Content
          <br /><br /><br /><br /><br /><br />
          Another Couple Lines
          <br /><br /><br /><br /><br /><br />
          More Tabbed Content
          <br /><br /><br /><br /><br /><br />
          Even More Content!
          <br /><br /><br /><br /><br /><br />
          Why not more yet!
          <br /><br /><br /><br /><br /><br />
          Some Ending Content
          <br /><br /><br /><br /><br /><br />
          Final Content!
        </div>
      </div>
    </div>
    <div class='footer'>
      <button class='button'>
        Done
      </button>
    </div>
  </div>
</div>

CSS

body {
  font-family: 'Montserrat', sans-serif;
}

/* Modal wrapper */
.modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

/* Modal background styles */
.background {
  position: absolute;
  height: 100%;
  width: 100%;
  background-color: rgba(0, 0, 0, 0.5)
}

/* Modal content */
.modal_content {
  height: 100vh;
  display: flex;
  flex-direction: column;
  width: 85%;
  margin: 0 auto;
  background-color: white;
}

.header {
  margin-bottom: 1rem;
  padding: 1rem;
  font-size: 125%;
  text-align: center;
  font-weight: bold;
  background-color: #EEEEEE;
  border-bottom: 1px solid #CECECE;
}

/* Contains the tabs and content */
.tabbed_content {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* IGNORE */
.tabs {
  display: flex;
  /* NOTE: Added to stop them from hiding */
  flex-shrink: 0;
}

/* IGNORE */
.tab {
  flex-grow: 1;
  margin-top: 8px;
  padding: 0.5rem;
  text-align: center;
  background-color: #CECECE;
}

/* IGNORE */
.tab.active {
  margin-top: 0;
  font-weight: bold;
  background-color: #EEEEEE;
}

/* Contains the current tab's content */
/* NOTE: This shouldn't scroll */
.content {
  padding: 1rem;
  background-color: #EEEEEE;

  /* NOTE: Currently the closest I can come */
  /*overflow: auto;*/
}

/* IMPORTANT: This should scroll if necessary! */
.scrollable {
  padding: 0.5rem;
  background-color: white;
  /* NOTE: Can't get this to scroll */
  /*overflow: auto;*/
  border: 1px dashed #CECECE;
}

.footer {
  display: flex;
  flex-align: center;
  flex-shrink: 0;
  justify-content: center;
  margin-top: 1rem;
  padding: 1rem;
  border-top: 1px dashed #CECECE;
}

/* IGNORE */
.button {
  padding: 0.75rem 1rem;
  font-size: 90%;
  color: white;
  background-color: lightseagreen;
  border: none;
  border-radius: 2px;
  cursor: pointer;
}

我快要被这个问题搞疯了(已经浪费了两个小时)。非常感谢您提前的帮助!

另外,我还需要考虑在模态框打开时停止背景滚动...


看到一个写得好的问题真是太棒了。 - Merlevede
3个回答

10

通常情况下,为了使 overflow: auto 显示垂直滚动条,您需要在容器上定义高度。

  

为了使overflow生效,块级容器必须具有设置的高度(heightmax-height)或将white-space设置为nowrap

     

来源:https://developer.mozilla.org/en-US/docs/Web/CSS/overflow

然而,在动态环境中,这并不总是一个实用的解决方案。

在您的布局中,简单的解决方案是将容器(.content)设为flex容器。这足以使布局在Chrome中工作(演示)。

但是,为了使布局在Firefox和Edge中也能正常工作,您需要覆盖flex项目的默认最小高度(min-height:auto)。这可以防止flex项目缩小到其内容大小以下,从而消除了overflow可能出现的可能性。(完整解释

对您的代码进行以下调整:

.tabbed_content {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  min-height: 0;                  /* NEW, esp for FF & Edge (see link below) */
}

.content {
  padding: 1rem;
  background-color: #EEEEEE;
  overflow: auto;                /* KEEP THIS, for FF & Edge (see link below) */
  display: flex;                 /* NEW */
  flex-direction: column;        /* NEW */
}

.scrollable {
  padding: 0.5rem;
  background-color: white;
  overflow: auto;               /* RESTORE */
  border: 1px dashed #CECECE;
}

修订后的 CodePen

更多细节请参见:为什么 flex 项目不能缩小到内容大小以下?


1
哇,真的是太棒了。这肯定是我应该尝试的东西!非常感谢您提供的解决方案。正如我所希望的那样,它可以很好地处理长内容和短内容,所以再次感谢!我会在实际应用程序中试一试,并让您知道结果! - Kendall
我的原始答案只在Chrome中有效,但在FF和Edge中无效。我刚刚更新了我的答案,以提供更完整的跨浏览器解决方案。(我还没有在Safari中测试过。) - Michael Benjamin
1
Gotcha,这对我来说非常重要。由于它分布在两个React组件中(一个用于TabbedContent,另一个用于Modal),因此实现可能会很困难,但应该是可行的(可能作为TabbedContent的条件(默认)属性)。 - Kendall
另外,我认为你提到了flex-shrink: 0的一些内容。是的,如果您希望flex项目尊重您指定的宽度/高度,则这很重要。有关更完整的解释,请参见此答案中的***flex-shrink因子***部分:https://dev59.com/4FsX5IYBdhLWcg3wALba - Michael Benjamin
是的,在意识到我提供的笔存在同样的问题之前,我犯了错误。(我对Flex有一定的了解,但在没有指定的情况下,我会遇到关于flex[grow|shrink|basis]默认值的知识盲区。) - Kendall
1
我能够使用你的补充解决了这个问题 - 非常感谢!你的回答促使我寻找另一个问题,那就是我在一个父容器上缺少了 display: flex 属性 - 一旦我添加了它和其他几个样式,一切都顺利了!当然还需要跨浏览器测试,但是还是非常感谢! - Kendall

0

你必须在 .scrollable 上设置一个高度以及 overflow-y: auto;,否则,溢出内容会可见,因为没有高度可以让其溢出。


我已经尝试将 overflow: auto; 添加到 .scrollable div 中,但无济于事。此外,我还在所有父元素上添加了 height: 100%,直到具有 calc() 高度的元素。 - Kendall
你需要设置一个固定的高度,如果使用height: 100%是行不通的。试试例如height: 300px,你会看到它能正常工作。 - Tom Harris
考虑添加一个固定的高度,适合大多数情况,然后使用js添加父元素的高度。一个简单的jquery示例是:$(“.scrollable”).css(“height”,$(“.content”).height()+“px”); - Tom Harris
这是团队另一位成员采取的方法,但我不愿意猜测固定高度。Flexbox 的进步将许多曾经不可能实现的东西带入了可能性的领域(现在只需使用 Flex 即可解决)。还是谢谢你! - Kendall

0

这种布局很棘手!我通常会尝试构建简化版本并逐步完善。

你可以使用绝对定位来实现可滚动的 div。

https://codepen.io/sheriffderek/pen/zwQxLr/ - 查看此示例以获取长短示例

<aside class="window">

    <header>header</header>

    <main>
        <div class="scroll-area">
            <ul>
                <li>short conent</li>
                <li>2</li>
                <li>3</li>
                <li>4</li>
                <li>5</li>
            </ul>
        </div>
    </main>

    <footer>footer</footer>

</aside>

CSS(这是Stylus - 但只是语法 - 概念应该很清楚)

.window
    border: 10px solid $gray;
    max-width: 600px // arbitrary
    height: 80vh // arbitrary

    display: flex
    flex-direction: column

    header, main, footer
        padding: $pad

    header
        background: $color

    main
        flex-grow: 1
        background: $alternate
        position: relative

        .scroll-area
            position: absolute
            top: 0
            left: 0
            width: 100%
            height: 100%
            overflow: auto
            padding: $pad

    footer
        background: $highlight

针对您的附加问题,您可以在打开模态框时向 body 添加/删除类并停止滚动。

防止 body 滚动但允许覆盖层滚动


我会研究一下使用 position: absolute; - 我曾尝试过,但那是在我开始制作这个布局之前。就像你所提到的,我从简单的开始逐步加强 - 我遇到了"TabbedContent"组件的添加问题,因为这是中间的"高级"元素,并且必须在子元素内部滚动。此外,较短的内容不应该扩展以占用剩余的高度,而只应该占用必要的高度(可能也是导致我困扰的原因)。 - Kendall
有一些逻辑问题 - 你需要逐个解决。文本区域不能仅凭一些特定规则就“知道”自己的高度 - CSS 可能无法完成所有任务。我敢打赌,你需要一些 JavaScript 来检查一些东西并设置一些东西。 - sheriffderek
1
我实际上通过上面@Michael_B发布的解决方案解决了我的问题。确实,我在单个父容器上缺少display: flex和其他几个附加样式,一旦添加了它,问题就解决了。感谢您的帮助! - Kendall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接