Add scroll to each column in CSS Grid Layout

29,654

Solution 1

In the landscape orientation I want to use 2 columns. My whole content is displayed on the left side and my navigation moves to the right side. Now I want both parts to have a separate scroll. Is there a way to implement this? And the scroll should stop if the content of the current column ends.

In the left column you have three separate grid items: the header, main and footer elements.

In the right column you have one grid item: the nav element.

Adding a scrollbar – vertical or horizontal – to the left column is not feasible because there are three separate elements. You would need to wrap all elements in a container for a single scrollbar to work.

Adding a scrollbar – vertical or horizontal – to the right column is pretty easy because there is only one element.

Assuming that you're talking about a vertical scrollbar, here's one way to make it work:

body {
  margin: 0;
}

.grid-container {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  grid-gap: 15px 0;
  height: 100vh;
}

header {
  background-color: green;
  grid-column: 1;
  grid-row: 1
}

main {
  background-color: blue;
  grid-column: 1;
  grid-row: 2;
}

nav {
  background-color: pink;
  grid-column: 1;
  grid-row: 3;
  overflow: auto;
}

footer {
  background-color: teal;
  grid-column: 1;
  grid-row: 4;
}

@media only screen and (orientation: landscape) {
  .grid-container {
    grid-template-columns: 5fr 4fr;
    grid-template-rows: 1fr 1fr 1fr;
  }
  nav {
    grid-column: 2;
    grid-row: 1 / span 3;
  }
  footer {
    grid-row: 3;
  }
}
<div class="grid-container">
  <header>
    <h1>Logo</h1>
  </header>
  <main>
    <h1>content</h1>
  </main>
  <nav>
    <h1>Navigation<br><br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br>nav item<br></h1>
  </nav>
  <footer>
    <h1>Footer</h1>
  </footer>
</div>

revised codepen


Browser Support for CSS Grid

  • Chrome - full support as of March 8, 2017 (version 57)
  • Firefox - full support as of March 6, 2017 (version 52)
  • Safari - full support as of March 26, 2017 (version 10.1)
  • Edge - full support as of October 16, 2017 (version 16)
  • IE11 - no support for current spec; supports obsolete version

Here's the complete picture: http://caniuse.com/#search=grid

Solution 2

Here is a extended version from my answer on your earlier question, how to get scroll for both header/content/main and nav using flexbox.

Fiddle demo

Stack snippet

(function(w, d, timeout) {
  w.addEventListener("load", function() {
    resizer();
  }, false);

  w.addEventListener("resize", function() {
    if (!timeout) {
      timeout = setTimeout(function() {
        timeout = null;
        // do resize stuff
        resizer();
      }, 66);
    }
  }, false);

  function resizer() {
    if (w.innerHeight < w.innerWidth) {
      if (!(d.body.classList.contains('landscape'))) {
        d.body.classList.add('landscape');
        d.body.appendChild(d.querySelector('nav'));
      }
    } else {
      if (d.body.classList.contains('landscape')) {
        d.body.classList.remove('landscape')
        d.querySelector('section').appendChild(d.querySelector('nav'));
      }
    }
  }
}(window, document));
html, body {
  margin:0;
}
header, footer, main, nav {
  margin: 5px;
  padding: 5px;
  border-radius: 5px;
  min-height: 120px;
  border: 1px solid #eebb55;
  background: #ffeebb;
}
footer {
  order: 2;
}
nav {
  order: 1;
}
section {
  display: flex;
  flex-direction: column;
}

@media only screen and (orientation: landscape) {

  main div {
    height: 400px;
    border: 1px dashed red;
  }
  nav div {
    height: 800px;
    border: 1px dashed red;
  }

  body.landscape {
    display: flex;
  }
  section {
    display: block;
    width: calc(60% - 10px);         /*  10px is for the margin  */
    box-sizing: border-box;
    max-height: calc(100vh - 20px);
    overflow: auto;
  }
  nav {
    width: calc(40% - 10px);         /*  10px is for the margin  */
    box-sizing: border-box;
    max-height: calc(100vh - 20px);
    overflow: auto;
  }
}
<section>
  <header>header</header>
  <main>content
    <div>
      This div get a height when in landscape to show scroll in section
    </div>
  </main>
  <footer>footer</footer>
  <nav>navigation
    <div>
      This div get a height when in landscape to show scroll in nav
    </div>
  </nav>
</section>
Share:
29,654
ruslansteiger
Author by

ruslansteiger

Du bist ein 🎁 für die 🌍

Updated on July 09, 2022

Comments

  • ruslansteiger
    ruslansteiger almost 2 years

    I want separate scroll on each of my columns in my grid layout.

    Currently, I am developing a mobile only web application. I want to use a different grid layout for the portrait and landscape orientations.

    The portrait orientation is just 1 column and every element is after the other. No problem here.

    In the landscape orientation I want to use 2 columns. My whole content is displayed on the left side and my navigation moves to the right side. Now I want both parts to have a separate scroll. Is there a way to implement this? And the scroll should stop if the content of the current column ends.

    Code on CodePen: https://codepen.io/SuddenlyRust/pen/rmJOqV

    .grid-container {
      display: grid;
      grid-template-columns: 1fr;
      grid-template-rows: 1fr;
      grid-gap: 15px 0;
    }
    
    header {
      background-color: green;
      grid-column: 1;
      grid-row: 1
    }
    
    main {
      background-color: blue;
      grid-column: 1;
      grid-row: 2;
    }
    
    nav {
      background-color: pink;
      grid-column: 1;
      grid-row: 3;
    }
    
    footer {
      background-color: teal;
      grid-column: 1;
      grid-row: 4;
    }
    
    @media only screen and (orientation: landscape) {
      .grid-container {
        grid-template-columns: 5fr 4fr;
      }
      nav {
        grid-column: 2;
        grid-row: 1 / span 3;
      }
      footer {
        grid-row: 3;
      }
    }
    
    h1 {
      min-height: 200px;
    }
    <div class="grid-container">
      <header>
        <h1>Logo</h1>
      </header>
      <main>
        <h1>content</h1>
      </main>
      <nav>
        <h1>Navigation</h1>
      </nav>
      <footer>
        <h1>Footer</h1>
      </footer>
    </div>

    Thank you very much for your time!

  • ruslansteiger
    ruslansteiger about 7 years
    Thank you very much @Michael_B for your time. But this solution only works if the navigation is bigger then the left content. My Navigation just takes half the space of the left content. I am not sure if this really works well with grid layouts.
  • ruslansteiger
    ruslansteiger about 7 years
    Nearly works. But if I scroll on the left side my navigation disappears. Is there a way to wrap the left content together?
  • Michael Benjamin
    Michael Benjamin about 7 years
    If you can wrap the left elements in one container, then everything gets a lot easier. Just use flexbox. codepen.io/anon/pen/jmZgEo?editors=1100
  • ruslansteiger
    ruslansteiger about 7 years
    Yeah thank you. That was my next idea to use flex. Looks like grid is not suited for my solution! Thank you very much for your time @Michael_B you helped alot ;)
  • ruslansteiger
    ruslansteiger about 7 years
    Yeah nice job @LGSon. This works perfect. Could I ask you some basic question. I am not that skilled in javascript. What does the first and last part actually do? "(function(d, timeout) {" and "}(document));" You are using the variable d always to manipulate the dom but how is it defined? I don't understand the syntax. And I don't get the timeout variable. Looks like magic to me. The code does make sense how you move the nav part around the tree.
  • Asons
    Asons about 7 years
    @SuddenlyRust The (function(d, timeout) {...})(); calls a closure and wrap what's in it so it does not pollute the global scope with variables and stuff. It executes itself with the last (document) and where document (the DOM's document) is passed in as the first parameter d. I did this so instead of writing document.body etc, I can use the shorter d.body. The timeout is used in the resize event and since it is passed in like that, as a parameter, it is accessible through out the entire closure function, just like a global variable is. Hope this make sense.
  • ruslansteiger
    ruslansteiger about 7 years
    yeah wow thanks. so much to learn ;) really appreatice your time. have a great day mate. I am gonna rewrite it in jQuery haha. so I can understand it much easier. But I think I can use your solution. it works perfectly fine
  • Asons
    Asons about 7 years
    @SuddenlyRust I recommend to not rewrite the resize handler part and use jQuery resize event, because it does not have the throttler functionality mine has, unless you add the equivalent plug-in of course. What I provided with plain javascript has the less impact as possible on performance and there is no real reason to jQuery-fy it.
  • ruslansteiger
    ruslansteiger about 7 years
    probably I could just remove jQuery in my current project and try to rewrite everything in plain javascript. How would you rewrite following in plain JS? codepen.io/SuddenlyRust/pen/JNaygY?editors=0010# It would be a bit complicated or? Not sure how to add the 'one' handler with animationend
  • Asons
    Asons about 7 years
    @SuddenlyRust With something like this: codepen.io/anon/pen/PmdJGR ... though it is perfectly fine to use both jQuery and plain javascript. About animation events, here is a good article: sitepoint.com/css3-animation-javascript-event-handlers