0

I'm developing an app with the interface that is supposed to fit the page (only some internal elements may have scrolling). The basic layout consists of a header and the main section:

<div class="page">
  <Navigation/> <!-- a Vue component -->
  <main class="page__main">
    ...
  </main>
</div>

currently, CSS has hardcoded height of the header (Navigation):

.page {
    height: 100vh;
}
.page__main {
    height: calc(100vh - 80px); /* 80px is the height of the header */
}

I'd like to get rid of this hardcoded bit but make sure .page__main's height gets no larger than 100vh - height of Navigation. Is there a way to do this without JS? I suspect that there are some options that can be used with

.page {
    height: 100vh;
    display: flex;
    flex-direction: column;
}

but just using that with

.page__main {
    flex-shrink: 1;
}

doesn't work: .page__main has children which use height in percents and once I set flex-shrink: 1; instead of height: calc(100vh - 80px); those grow and the interface is broken.

To illustrate the problem better, here's the current state:

body { padding: 0; margin: 0; }
.page {
  height: 100vh;
  background: blue;
}
.page__navigation {
  height: 80px;
  background: gray;
}
.page__main {
  height: calc(100vh - 80px);
}
.part1 {
  height: 50%;
  background: #eeeeee;
  overflow-y: scroll;
}
.part2 {
  height: 50%;
  background: #cccccc;
}
<div class="page">
  <div class="page__navigation">nav stuff</div>
  <main class="page__main">
    <div class="part1">
      this one usually has more elements than it could contain and those are shown with scrolling
      <br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line
    </div>
    <div class="part2">
      some
    </div>
  </main>
</div>

and here's what happen when I try to "set height" via flex:

body { padding: 0; margin: 0; }
.page {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: blue;
}
.page__navigation {
  height: 80px;
  background: gray;
}
.page__main {
  flex-shrink: 1;
}
.part1 {
  height: 50%;
  background: #eeeeee;
  overflow-y: scroll;
}
.part2 {
  height: 50%;
  background: #cccccc;
}
<div class="page">
  <div class="page__navigation">nav stuff</div>
  <main class="page__main">
    <div class="part1">
      this one usually has more elements than it could contain and those are shown with scrolling
      <br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line
    </div>
    <div class="part2">
      some
    </div>
  </main>
</div>
YakovL
  • 7,557
  • 12
  • 62
  • 102

3 Answers3

2

You can consider a nested flexbox container and don't forget the use of min-height:0; to allow the elements to shrink.

body { padding: 0; margin: 0; }
.page {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: blue;
}
.page__navigation {
  height: 80px;
  background: gray;
}
.page__main {
  flex-grow: 1; /* Fill the remaining space*/
  display:flex; /* Nested Container*/
  flex-direction:column;
  min-height:0;  /* Allow the element to shrink */
}
.part1 {
  flex-basis: 50%;
  background: #eeeeee;
  overflow-y: scroll; /* Allow the element to shrink */
}
.part2 {
  flex-basis: 50%;
  min-height:0; /* Allow the element to shrink */
  background: #cccccc;
}
<div class="page">
  <div class="page__navigation">nav stuff</div>
  <main class="page__main">
    <div class="part1">
      this one usually has more elements than it could contain and those are shown with scrolling
      <br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line
    </div>
    <div class="part2">
      some
    </div>
  </main>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • this is an interesting illustration of `flex-base` usage, but rather complicated. See my comment Ömürcan's answer; thanks anyway – YakovL Oct 01 '19 at 16:05
  • @YakovL there is nothing complicated. I simply replaced height with flex-basis, You can keep the use of height and you will get the same thing. The relevant part is the nested flexbox container combined with flex-grow:1 – Temani Afif Oct 01 '19 at 18:32
  • yeah, actually in the actual case using `min-height: 0;` was enough, it's just the 50% height children have multiple wrappers and I mapped the other bits of CSS to that DOM wrongly – YakovL Oct 03 '19 at 10:44
1

Use flex-grow. Keep everything as the second one (flex one) and change:

Edit

.page {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: blue;
}

.page__main {
   height: 100%;
   min-height: 0;
   flex: 1 1 auto;
}

Three value flex means flex: flex-grow | flex-shrink | flex-basis.

Flex-grow tells our element whether or not it can take up additional space.

Flex-shrink works very similarly to flex-grow, only instead of dealing with extra space, it deals with space not needed by an elements content.

Flex basis is best used when in conjunction with either flex-shrink or flex-grow.

You can check this article to understand better.

Ömürcan Cengiz
  • 2,085
  • 3
  • 22
  • 28
  • You want to keep nav's height 80px and make `.part1` and `.part2`'s height same right? – Ömürcan Cengiz Oct 01 '19 at 14:35
  • I want `.page__main` to fill the rest of the page, being agnostic to what nav's height is (and keep percentage heights for `.part` and `.part2`) – YakovL Oct 01 '19 at 15:06
  • Did you try my editted code? It should fix your problem. – Ömürcan Cengiz Oct 01 '19 at 15:22
  • Oh, looks like I've missed the edit (page was closed, comment showed up, but answer didn't get refreshed). Cool, this works! Would you mind explaining how exactly this works? – YakovL Oct 01 '19 at 15:31
  • Hm, this is funny, actually the `flex` part doesn't have to do anything about this. It is just setting `height: 100%` inside the flex container which solves the issue. Hm.. – YakovL Oct 01 '19 at 15:58
  • @ÖmürcanCengiz I'm not sure it works properly.. the `height: 100%` sets the `.page_main` to be the same height as the `.page`/`viewport`. so it just overflows the bottom edge of the viewport. It doesn't fill the rest of the page... You can check it if you scroll the whole way down to the bottom.. @YakovL I think **Temani Afif**'s answer is what you are looking for. – maxim Oct 01 '19 at 18:02
  • @maxim perhaps you are right about Temani Afif's answer; but I don't get an overflow in a real-world case (nor in the simplified example). I'm not quite sure why this happens, but it works; perhaps I should dig a bit the implementation of `flex`: may be this solution is valid due to the real algorythm of height calculation inside flex.. – YakovL Oct 01 '19 at 19:21
  • @maxim Setting the `.page__main`'s `height: 100%` won't be same as `.page`. `.page` is a parent element. For example, if parent's height is 300px, setting the child element's `height: 100%` is simply means take all the height of that 300px. If you set `height: 80%`, it means child element's height will be 240px. – Ömürcan Cengiz Oct 01 '19 at 21:31
  • @ÖmürcanCengiz Exactly, but inside the `.page` there are two children: `.page__navigation` (which already occupies 80px) and `.page__main`. So by setting `.page__main` `height: 100%` you make the total height of two children = 100% + 80px which is definitely exceeds the height of the paren.. – maxim Oct 01 '19 at 22:39
  • @ÖmürcanCengiz Also, could you please provide a working code snippet with your solution? Even though YakovL said it works for his real-world case, for the snippet he provided inhere your solution doesn't really work.. Or do I miss something? – maxim Oct 01 '19 at 22:52
  • @maxim thanks for persistence, I've checked this in different browsers and found that while the `height:100%` approach works in Chrome (in both SO snippet and real-world case), it fails in FireFox (produces 80px overflow). Not sure which behavior should be considered a bug, but this is definitely not a reliable solution – YakovL Oct 02 '19 at 12:07
  • Add `overflow: hidden;` to `.page`. It should fix the problem in different browsers. – Ömürcan Cengiz Oct 02 '19 at 12:40
  • @ÖmürcanCengiz wow, it does! But this is something I don't understand. I would expect that adding `overflow: hidden;` would cut the overflown part of the content (overflown due to the reason explained by maxim), but this instead pushes it back into the boundaries of the page. How does this work? – YakovL Oct 02 '19 at 12:59
  • this is something unpredictable. In Edge this cuts the `page__navigation` instead! – YakovL Oct 02 '19 at 13:01
  • @ÖmürcanCengiz :) I'm sorry, but it won't solve the problem.. it will just hide the overflow of the `.page_main`.. the right way is what **Temani Afif** suggested.. and the important part there is to set `min-height: 0` ... I'm sorry for kipping commenting :) I will stop now. – maxim Oct 02 '19 at 13:03
  • @maxim Yes you are right. `min-height: 0` solves the problem. But we don't have to write `display: flex;` and `flex-direction: column` again. – Ömürcan Cengiz Oct 02 '19 at 13:11
  • @ÖmürcanCengiz Yes.. however it will work only if both `.part1` & `.part2` have set height in %% (as it is now).. but if one will be set in pixels then everything will be broken again.. so `flex` with `column` doesn't hurt here... – maxim Oct 02 '19 at 13:24
  • Yea that's true. I just wanted to make sure there wasn't a bunch of code. – Ömürcan Cengiz Oct 02 '19 at 14:16
-1

I would suggest css-grid approach : -

.page {
  background: gray;
  display: grid;
  grid-template-rows: 100px auto;
  height: 100vh;
  color: white;
}

.nav {
  grid-row: 1/2;
  background: brown;
}
.main {
  grid-row: 2/3;
  background: green;
  display: grid;
  grid-template-rows: 30% 70%;
}
.part1 {
  overflow: auto
}
.part2 {
  background: blue
}
<div class="page">
  <div class="nav">Nav</div>
    <div class="main">
      <div class="part1">
        this one usually has more elements than it could contain and those are shown with scrolling
      <br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line<br>line
      </div>
      <div class="part2">
        some
      </div>
    </div>
  </div>
</div>
YakovL
  • 7,557
  • 12
  • 62
  • 102
Naveen Jain
  • 1,042
  • 7
  • 26
  • I haven't tested this with the layout under consideration, but thanks for reminding about grid, worth using in some cases where I forget about it – YakovL Oct 01 '19 at 15:54
  • run the code snippet and please upvote . it will help me as well as community. @YakovL – Naveen Jain Oct 01 '19 at 15:58
  • This snippet doesn't have the essintial parts: you haven't put the overflown 50% height elements into the main. Edit it and then I'll run it – YakovL Oct 01 '19 at 16:03
  • @YakovL check this out. i have also crossed checked with create a html file in my pc and ran in chrome. vh always same. – Naveen Jain Oct 01 '19 at 16:12
  • well, currently it doesn't fit viewport height.. is it so for you or not? – YakovL Oct 01 '19 at 16:17
  • yes it is fit in viewport size. copy my code and put it in html file with style tag and run in browser you will see.@YakovL beacuse this stack snippet editor does not show how much viewport its taking in result output screen – Naveen Jain Oct 01 '19 at 16:19
  • nope, it should fit any viewport (well, if it more than 80px in height which is the case for SO snippet) – YakovL Oct 01 '19 at 16:28
  • it is fitting in any viewport . just check carefully @YakovL . bdw i will leave it to you how you are calculating viewport . but this answer is 100% correct. – Naveen Jain Oct 01 '19 at 16:31