Setting width/height as percentage minus pixels
Asked Answered
C

11

585

I'm trying to create some re-usable CSS classes for more consistency and less clutter on my site, and I'm stuck on trying to standardize one thing I use frequently.

I have a container <div> that I don't want to set the height for (because it will vary depending on where on the site it is), and inside it is a header <div>, and then an unordered list of items, all with CSS applied to them.

It looks a lot like this:

Widget

I want the unordered list to take up the remaining room in the container <div>, knowing that the header <div> is 18px tall. I just don't know how to specify the list's height as "the result of 100% minus 18px".

I've seen this question asked in a couple other contexts on SO, but I thought it would be worth asking again for my particular case. Does anyone have any advice in this situation?

Cholon answered 12/3, 2010 at 17:34 Comment(4)
Set a margin? __Organic
@KennyTM, I assume you're suggesting I put a margin-top on my unordered list of, say, 17px. But this just pushes the entire list down; it doesn't cause it to shrink to stay in the container. Essentially, it's current height is maintained, but it's just pushed down by 17px. This doesn't solve my issue, but I think it's a step in the right direction because I've seen other approaches online that used this technique.Cholon
I just solved this issue in my question I posted here. https://mcmap.net/q/65751/-css-fix-the-height-of-a-section-within-a-variable-height-elementLarrabee
possible duplicate of CSS How to set div height 100% minus nPxReverent
L
1100

You can use calc:

height: calc(100% - 18px);

Note that some old browsers don't support the CSS3 calc() function, so implementing the vendor-specific versions of the function may be required:

/* Firefox */
height: -moz-calc(100% - 18px);
/* WebKit */
height: -webkit-calc(100% - 18px);
/* Opera */
height: -o-calc(100% - 18px);
/* Standard */
height: calc(100% - 18px);
Liner answered 31/12, 2012 at 12:12 Comment(5)
Also if you use Less you'd better compile a file with lessc --strict-math=on in order less not to evaluate the expression inside the calc -- just a problem I faced and spent a lot of time (as of Less 2.0.0)Horatius
Note that the spaces around the minus sign are not optional: 100%-18px, 100%- 18px, and 100% -18px didn't work in the browsers I tested with.Wateriness
@DmitryWojciechowski Thanks for pointing out. This workaround also works for me with LESS: 4 ~'calc(50% - 20px)';Atlantis
If it isn't working use 100vh instead of 100%.Ambuscade
Note that in React the whole should be in quotes: height: 'calc(100% - 18px)'Remnant
F
74

For a bit of a different approach you could use something like this on the list:

position: absolute;
top: 18px;
bottom: 0px;
width: 100%;

This works as long as the parent container has position: relative;

Formulary answered 30/3, 2011 at 14:3 Comment(6)
For those who may wonder like me: position has to be absolute in the child container, it cannot be relative.Wideopen
Continued (sorry): However the parent just needs positioning (it can be absolute too), and it needs to be set to 100% height as well.Wideopen
Unfortunately, it does not work on Safari iOS 8. (Tested OK on Android 4.1 stock browser and standard FF 34) :-(Wideopen
This might work in some cases, but might also cause some troubles, as absolutely positioned elements are removed from the normal flow, see https://mcmap.net/q/65753/-clear-absolutely-positioned-elements-with-css-possible.Oakman
@ChristopheWeis this is true, but that is exactly the point in this case - we want the list to be sized by its parent rather than the opposite. The key is to understand how absolute position works. w3.org/wiki/CSS_absolute_and_fixed_positioningFormulary
I can confirm that position: absolute with setting bottom and top is by far the most elegant solution. Amazingly, despite searching for hours on Google, most CSS tutorial sites don't even mention this. Instead they suggest using calc or flex containers.Monandry
I
25

I use Jquery for this

function setSizes() {
   var containerHeight = $("#listContainer").height();
   $("#myList").height(containerHeight - 18);
}

then I bind the window resize to recalc it whenever the browser window is resized (if container's size changed with window resize)

$(window).resize(function() { setSizes(); });
Inglebert answered 12/3, 2010 at 18:56 Comment(6)
@puffpio, Certainly JS is a way to accomplish this on a case by case basis. But as I said, I'm trying to make the CSS generic, to be applied across multiple pages (that inherit from a master page, incidentally). So I'd prefer not to use JS. But thanks for the answer.Cholon
Don't put your style inside JavaScript.Wideopen
@greg, well it would help if CSS had some more useful features. Some of things you have to do hacks for is ridiculous.Adkisson
@Inglebert Nice solution indeed. But binding events on window is not a good solution for me. If my page has 3-4 or more such elements I have to update height and width of each element in window resize handler. This would be a little slower than CSS solution.Microgroove
@puffpio: Nice solution but consider changing your last line of code to $(window).resize(setSizes); No need to use an anonymous method with a single method callIllustration
"Don't put your xyz inside JavaScript" is like saying "don't support 80% of the browser market".Haygood
M
15

Don't define the height as a percent, just set the top=0 and bottom=0, like this:

#div {
   top: 0; bottom: 0;
   position: absolute;
   width: 100%;
}
Mainspring answered 1/8, 2012 at 17:16 Comment(3)
Could you explain why not set height to a percent?Stinker
@ShrikantSharat This is part of the visual formatting spec for absolutely positioned elements. This solution takes advantage of possibility 5., "'height' is 'auto', 'top' and 'bottom' are not 'auto', then 'auto' values for 'margin-top' and 'margin-bottom' are set to 0 and solve for 'height'". The browser can calculate the height of the element because it knows how far from the top and bottom of its parent it needs to be.Incredulity
This doesn't seem to work, at least in Firefox. Even with all parent elements set to height:100% and display:block. Otherwise it would be a very elegant solution. Interestingly, margin:auto also fails to center a fixed-width object vertically, even though it works fine horizontally.Haygood
L
8

Presuming 17px header height

List css:

height: 100%;
padding-top: 17px;

Header css:

height: 17px;
float: left;
width: 100%;
Ladon answered 12/3, 2010 at 19:3 Comment(2)
That didn't work quite as well as I had hoped. My unordered list is being started beneath that header div. The header div is taking up space in the container, so putting padding-top of 17px on the list is just going to push it down 17px from where it already is in the picture above, not 17px from the top of the container div. Here's a pic of what I get with the CSS given in the answer above: i41.tinypic.com/mcuk1x.jpg. I appreciate the effort, though, and if there's something you think I'm missing, please let me know. Btw, I have overflow: auto on the unordered list as well.Cholon
It would be a lot easier troubleshooting this if you posted actual css so we can see exactly what interactions are going on. Another thing you might try is negative top margin on your ul. ul { margin-top: -17px; }Ladon
M
7
  1. Use negative margins on the element you would like to minus pixels off. (desired element)
  2. Make overflow:hidden; on the containing element
  3. Switch to overflow:auto; on the desired element.

It worked for me!

Micco answered 30/8, 2011 at 15:3 Comment(1)
This is a very elegant and compatible solution. The main downside is that the element must be a fixed height. Otherwise you'd need to use JS or calc() to center it at runtime. But for many cases this shouldn't be an issue. Another thing to keep in mind is that using a lot of negative values for margins, padding, and offsets can cause confusion when debugging code later, so try to keep things simple.Haygood
D
5

Try box-sizing. For the list:

height: 100%;
/* Presuming 10px header height */
padding-top: 10px;
/* Firefox */
-moz-box-sizing: border-box;
/* WebKit */
-webkit-box-sizing: border-box;
/* Standard */
box-sizing: border-box;

For the header:

position: absolute;
left: 0;
top: 0;
height: 10px;

Of course, the parent container should has something like:

position: relative;
Downstairs answered 21/5, 2014 at 9:2 Comment(0)
V
3

Another way to achieve the same goal: flex boxes. Make the container a column flex box, and then you have all freedom to allow some elements to have fixed-size (default behavior) or to fill-up/shrink-down to the container space (with flex-grow:1 and flex-shrink:1).

#wrap {        
  display:flex;
  flex-direction:column;
}
.extendOrShrink {
  flex-shrink:1;
  flex-grow:1;
  overflow:auto;
}

See https://jsfiddle.net/2Lmodwxk/ (try to extend or reduce the window to notice the effect)

Note: you may also use the shorthand property:

   flex:1 1 auto;
Visualize answered 24/6, 2018 at 22:12 Comment(1)
I believe this may the best answer today because it means not having to specify a set height for the header div (of 18px in my original question), and it means not having to subtract things like padding and margin. No set pixels, and everything takes care of itself.Cholon
D
2

I tried some of the other answers, and none of them worked quite how I wanted them to. Our situation was very similar where we had a window header and the window was resizable with images in the window body. We wanted to lock the aspect ratio of the resizing without needing to add in calculations to account for the fixed size of the header and still have the image fill the window body.

Below I created a very simple snippet that shows what we ended up doing that seems to work well for our situation and should be compatible across most browsers.

On our window element we added a 20px margin which contributes to positioning relative to other elements on the screen, but does not contribute to the "size" of the window. The window-header is then positioned absolutely (which removes it from the flow of other elements, so it won't cause other elements like the unordered list to be shifted) and its top is positioned -20px which places the header inside of the margin of the window. Finally our ul element is added to the window, and the height can be set to 100% which will cause it to fill the window's body (excluding the margin).

*,*:before,*:after
{
  box-sizing: border-box;
}

.window
{
  position: relative;
  top: 20px;
  left: 50px;
  margin-top: 20px;
  width: 150px;
  height: 150px;
}

.window-header
{
  position: absolute;
  top: -20px;
  height: 20px;
  border: 2px solid black;
  width: 100%;
}

ul
{
  border: 5px dashed gray;
  height: 100%;
}
<div class="window">
  <div class="window-header">Hey this is a header</div>
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
    <li>Item 5</li>
  </ul>
</div>
D answered 28/4, 2016 at 14:0 Comment(0)
F
1

Thanks, i solved mine with your help, tweaking it a little since i want a div 100% width 100% heigth (less height of a bottom bar) and no scroll on body (without hack / hiding scroll bars).

For CSS:

 html{
  width:100%;height:100%;margin:0px;border:0px;padding:0px;
 }
 body{
  position:relative;width:100%;height:100%;margin:0px;border:0px;padding:0px;
 }
 div.adjusted{
  position:absolute;width:auto;height:auto;left:0px;right:0px;top:0px;bottom:36px;margin:0px;border:0px;padding:0px;
 }
 div.the_bottom_bar{
  width:100%;height:31px;margin:0px;border:0px;padding:0px;
}

For HTML:

<body>
<div class="adjusted">
 // My elements that go on dynamic size area
 <div class="the_bottom_bar">
  // My elements that goes on bottom bar (fixed heigh of 31 pixels)
 </div>  
</div>  

That did the trick, oh yes i put a value little greatter on div.adjusted for bottom than for bottom bar height, else the vertical scrollbar appears, i adjusted to be the nearest value.

That difference is because one of the elements on dynamic area is adding an extra bottom hole that i do not know how to get rid of... it is a video tag (HTML5), please note i put that video tag with this css (so there is no reason for it to make a bottom hole, but it does):

 video{
  width:100%;height:100%;margin:0px;border:0px;padding:0px;
 }

The objetive: Have a video that takes the 100% of the brower (and resizes dynamically when browser is resized, but without altering the aspect ratio) less a bottom space that i use for a div with some texts, buttons, etc (and validators w3c & css of course).

EDIT: I found the reason, video tag is like text, not a block element, so i fixed it with this css:

 video{
  display:block;width:100%;height:100%;margin:0px;border:0px;padding:0px;
 }

Note the display:block; on video tag.

Frohman answered 13/5, 2016 at 11:56 Comment(0)
W
0

I'm not sure if this work in your particular situation, but I've found that padding on the inside div will push content around inside of a div if the containing div is a fixed size. You would have to either float or absolutely position your header element, but otherwise, I haven't tried this for variable size divs.

Wilkins answered 12/3, 2010 at 19:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.