Scrolling inside nested flexboxes

2020-07-17 05:48发布

问题:

I'm working on a React app that has a modal with some Tabbed content inside that occasionally needs to scroll. However, the problem is that I can't manage to get the proper content to scroll.

The modal is broken into 3 main sections:

  1. Header
    • Should always be visible at the top of the modal
  2. Content
    • Should fill space between Header and Footer but never force either from view
  3. Footer
    • Should always be visible underneath the Content
    • This may either mean at the bottom of the modal (if Content fill remaining space) or underneath the Content (if Content doesn't fill space)

While this would be simple to implement (and I have already done so), the problem arises in the fact that the Content isn't supposed to scroll, but rather something inside it should. In the images below are two example of the intended behaviour, one with long content that should scroll and one without.

I am using a custom Tabbed component that renders something inside a container. This container (white in the images below) is what should scroll if necessary.

The furthest I have come can be found in the following CodePen and example code. I am currently able to scroll the element that contains intended scrollable content, but can't actually get that content to scroll. If you inspect the Pen you can see that it (for some reason) extends past the containing element.

Code Example

CodePen Example

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;
}

I am nearly at my wits end with this (have spent 2 hours messing around so far). Thanks in advance for any help!

P.S. I also need to worry about stopping the background scroll while the modal is open at some point...

回答1:

Generally speaking, for overflow: auto to render a vertical scrollbar, you need to define a height on the container.

In order for overflow to have an effect, the block-level container must have either a set height (height or max-height) or white-space set to nowrap.

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

However, this isn't always a practical solution, especially in dynamic environments.

In your layout, the simple solution is to make the container (.content) a flex container. That's enough to make the layout work in Chrome (demo).

However, for the layout to also work in Firefox and Edge, you need to override the default minimum height of flex items, which is min-height: auto. This prevents flex items from shrinking below the size of their content, which eliminates the possibility for an overflow to occur. (full explanation)

Make these adjustments to your code:

.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;
}

revised codepen

More details: Why doesn't flex item shrink past content size?



回答2:

Layout like this is tricky! I usually try and build simplified versions and work up.

One way you could do it would be with position absolute for the scrollable div.

https://codepen.io/sheriffderek/pen/zwQxLr/ - see this pen for long and short examples

<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 (this is stylus - but it's just syntax - and the concept should be clear)

.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

For your additional question, you can add/remove class from the body when opening the modal and stop scrolling.

Prevent body scrolling but allow overlay scrolling



回答3:

You have to set a height on .scrollable as well as overflow-y: auto; otherwise, the overflow is visible as there is no height for it to overflow from.