CSS - Calc inaccuracy - html

I have a ul element and 5 child <li>.
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
The <ul> has a display: flex property.
I tried using the calc property on the <li> to evenly size my list items:
calc:(100% / 5);
This gives the desired result and evenly sizes the 5 <li> blocks
Now I added borders to the right side of all, but the last child <li> element. So I reduced the total width of all the borders combined from the total width of the <ul>.
calc:((100% - 8px) / 5);
This also worked properly and evenly sized the <li> blocks with the borders.
ul {
width: 600px;
height: 300px;
margin: 0;
padding: 0;
display: flex;
background: red;
}
li {
list-style: none;
display: block;
width: calc((100% - 0.8px) / 5);
height: 100%;
border-right: 0.2px solid black;
background: blue;
}
li:last-child {
border-right: none;
}
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
Now I tried to set the border width in viewport unit vw instead of px but it gives a different result.
ul {
width: 600px;
height: 300px;
margin: 0;
padding: 0;
display: flex;
background: red;
}
li {
list-style: none;
display: block;
width: calc((100% - 0.8vw) / 5);
height: 100%;
border-right: 0.2vw solid black;
background: blue;
}
li:last-child {
border-right: none;
}
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
As you can see from the snippet above, there is a little bit of space to the right. This becomes bigger with a wider viewport (try viewing the snippet full page). So I think the problem lies with the vw units and flexbox here.
So what is the cause of this error?
EDIT:
From the provided answers and comments, I have seen there are other and more proper approches to achieve what I was trying to do. I appreciate those answers but those are not the answers to my question. Since calc is showing an error in this case, most likely it will cause more problems when I try to use calc and viewport units in other cases (not just borders). So I need to know the reason and "calc" fix.

You don't need to do calc to add inner content for your li. If you give the box-sizing: border-box; prop, border and padding will not make the container grows.
ul {
width: 600px;
height: 300px;
margin: 0;
padding: 0;
display: flex;
background: red;
}
li {
list-style: none;
display: block;
width: calc(100% / 5);
height: 100%;
border-right: 2px solid black;
background: blue;
box-sizing: border-box;
}
li:last-child {
border-right: none;
}
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>

It seems you are using flex to distribute your lis inside of your ul. In this case, you do not need to calculate the widths to make those equal. That is what flex is supposed to do.
See this example:
* { box-sizing: border-box; margin: 0; padding: 0; }
ul, li { list-style: none; }
ul { width: 600px; height: 300px; background: red; display: flex; }
li {
flex: 1 0 auto;
border-right: 0.8vw solid black;
background: blue;
}
li:last-child { border-right: none; }
<ul>
<li></li><li></li><li></li><li></li><li></li>
</ul>
When specifying flex dsiplay on containers, you just need to apply the flex property on the children to make them behave as flexible items. In your specific use-case, you need the following:
flex-grow: set to 1. This will allow your li to grow to fill the entire ul width.
flex-shrink: set to 0. You do not want your li to shrink.
flex-basis: set to auto. You want your li to grow to an equalized width automatically.
So, you see, you do not need calc at all.
You also, need not to specifically apply display: block to your lis. As per the specs here: https://www.w3.org/TR/css-flexbox-1/#flex-items
The display value of a flex item is blockified: if the specified
display of an in-flow child of an element generating a flex container
is an inline-level value, it computes to its block-level equivalent
As pointed out by #BoltClock in the comments, the list-item is already blockified.
Borders
Now, coming to borders. In your first example you have specified the width in 0.2px unit. This makes no sense. Although the specs allow fractional pixels, it will round it off to the nearest pixel available on display which is usually 1.
In you second example, you want the borders to scale with the viewport. That's good. No problem with the 0.8vw.
Just see the example I posted, in this fiddle. Resize the result window and you will see the borders changing relative to the viewport size.
Lastly, you may or may not want to set the box-sizing. It depends on your layout. Whatever you use, you use it consistently.

Pixels (px): Absolute pixels. So for example, 20px will be literally 20 pixels on any screen. If a monitor is of 1980x1200, and you set an element's height to 200px, the element will take 200 pixels out of that.
Viewport height/width (vw/vh): Size relative to the viewport (browser windoe, basically).
1px = (100 / document.documentElement.clientWidth)vw
1px = (100 / 500) = 0.2vw

Related

Sizing a positioned element based on one child only

I have an absolutely-positioned container with two children. I would like the width of the container to be based on the width of the first child, while the second child dynamically adapts to the width of its parent.
const passenger = document.getElementById("passenger");
function toggle(){
passenger.classList.toggle("squashed");
}
#top {
padding: 4px;
border: thin solid red;
position: absolute;
}
ul {
padding: 4px;
border: thin solid green;
}
li {
padding: 4px;
border: thin solid purple;
}
ul#passenger li {
width: auto; /* ??? No good -- what goes here ??? */
}
.squashed {
max-width: 10em;
}
<button onclick="toggle()">Toggle</button>
<hr/>
<div id="top">
<ul id="driver">
<li>This is the desired width</li>
</ul>
<ul id="passenger">
<li>This should be forced to wrap because its width doesn't contribute to the container width calculation.</li>
</ul>
</div>
Click the "Toggle" button in the example to see an approximation of the effect I'm going for. When I don't artificially constrain the width of passenger, it grows horizontally to fill the page, which pushes the width of top to match it.
I have tried setting #passenger li {width:auto;} as well as width:100%;, and also various combinations of word-wrap, etc, but I can't figure out how to force the li to take on the width of its parent. It seems that #passenger li {width:100%;} only works when the width of top is explicitly set, rather than based on the width of driver. It also "works" the way I want if I take passenger completely out of the flow by making it position: absolute but I want top to completely contain both elements, so this is not an option.
Is the desired effect possible using CSS only (no JS)?
You could do this with display: table on parent element and some small width, for example 1%, and then you also set white-space: nowrap on first ul element.
In this case all of the other child elements will adjust its width to the largest one.
#top {
padding: 4px;
border: thin solid red;
position: absolute;
display: table;
width: 1%;
}
#driver {
white-space: nowrap;
}
ul {
padding: 4px;
border: thin solid green;
}
li {
padding: 4px;
border: thin solid purple;
}
<div id="top">
<ul id="driver">
<li>This is the desired width</li>
</ul>
<ul id="passenger">
<li>This should be forced to wrap because its width doesn't contribute to the container width calculation.</li>
</ul>
</div>

Understanding CSS table-cell and percentage width

I was checking how Github display the below menu:
If you notice, each menu item is given an equal width. In CSS, we should give it any percentage value, what's the reason behind that? notice that the parent div is not given display: table property.
div {
border: 1px solid #000;
border-radius: 4px;
}
div ul {
padding: 0;
margin: 0;
}
div ul li {
display: table-cell;
width: 1%;
text-align: center;
border-right: 1px solid #000;
padding: 10px 0;
}
div ul li:last-child {
border-right: 0;
}
<div>
<ul>
<li>Commits
</li>
<li>Branch
</li>
<li>Contribution
</li>
<li>anything
</li>
</ul>
</div>
What's the reason behind the percentage width?
This has to do with how automatic table layout works. In particular:
A percentage value for a column width is relative to the table width. If the table has 'width: auto', a percentage represents a constraint on the column's width, which a UA should try to satisfy. (Obviously, this is not always possible: if the column's width is '110%', the constraint cannot be satisfied.)
In your case, you're setting a minuscule percentage width on every table-cell element. But the browser needs to ensure that the table-cells fill up the width of the table (which itself is as wide as its containing block), so it has to expand the cells to occupy as much space within the table as possible.
The reason why this results in approximately equal-width cells is because the percentage value is equal for all of them. If, for example, you set a slightly larger width for one of the cells, you'll see that it grows wider and the other cells become narrower to accommodate:
div {
border: 1px solid #000;
border-radius: 4px;
}
div ul {
padding: 0;
margin: 0;
}
div ul li {
display: table-cell;
width: 1%;
text-align: center;
border-right: 1px solid #000;
padding: 10px 0;
}
div ul li:first-child {
width: 3%;
}
div ul li:last-child {
border-right: 0;
}
<div>
<ul>
<li>Commits
</li>
<li>Branch
</li>
<li>Contribution
</li>
<li>anything
</li>
</ul>
</div>
However, note that there is a slight difference because some cells have longer content than others, and the length of this content is accounted for when calculating the cell widths.
The reason why a value as small as 1% is used is so that you can continue to add cells and the browser will still try to distribute the available width as evenly as possible. You're essentially telling the cells that they don't need to be a certain width at minimum so you can add away (although technically, 1% is still something).
The fact that no elements are assigned display: table is inconsequential, because an anonymous table wrapper will be generated around the table-cells so that they can render correctly. This anonymous table wrapper functions exactly the same as an unstyled element with display: table, which means the table width is auto for the purposes of calculating percentage cell widths.
This is something called the 1% width table hack.
This is because the table-cell inherits it's width from the parent div, allowing you to specify a related percentage of the cell width.
Here is a working demo on CodePen that you can play with and examine further.
http://codepen.io/ld0rman/pen/FCiLh
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<li>Five</li>
</ul>
CSS:
ul {
list-style-type: none;
margin: 100px auto;
width: 80%;
}
li {
display: table-cell;
width: 1%;
text-align: center;
}
a {
display: block;
border: 1px solid black;
padding: 25px;
text-decoration: none;
color: white;
text-transform: uppercase;
font-weight: bold;
background: grey;
&:hover {
text-decoration: none;
color: yellow;
background: darken(grey, 10%);
}
}
As you can see, it is coded similarly to your github example. Any more questions, ask away!

Distribute <li> elements inside a div

I want to position list elements inside a bordered div, so they will go "all the way" from right to left, and to take exactly 960px width, but for some reason I can't space between them to catch all the 960px.
HTML:
<div class="wrapper">
<ul>
<li class="menu red">1</li>
<li class="menu blue">2</li>
<li class="menu yellow">3</li>
<li class="menu yellow">4</li>
</ul>
</div>
JSFiddle: http://jsfiddle.net/yuvalsab/MvaeF/
I'd use the display: table and display: table-cell technique. This way, you don't have to define a width on your items.
div.wrapper {
width:960px;
margin: 0 auto;
}
ul {
border:5px solid black;
display: table;
width: 100%;
margin: 0;
padding: 0;
}
ul li.menu {
display: table-cell;
height: 120px;
text-align: center;
padding: 55px 0 0 0;
border: 8px solid;
font-size: 25px;
}
Demo
You need to remove default paddings on ul and possibly margins on li's.
ul {
border:5px solid black;
margin:0;
}
Check this: http://jsfiddle.net/MvaeF/2/
First of all... you need to check your match.
960 - UL 5px Padding (5 x 2 = 10) = 950 for the LIs.
That leaves you with 950.
Divided by 4, and you have 237.5 each.
Now, you have 8px border... so 4 x 2 = 16.
237 - 16 = 221px wide for the LI.
Note, you have it at 200px not 221... so that is some of the space issue you're seeing.
The other issue, natively, the browser gives UL and LIs Margin, different amounts for different browsers. If you are not normalizing, then you have to manually.
ul, li {margin:0px; padding:0px; }
That will ensure that doesn't interfere with your math.
I think you just edited it.
Regardless... use the 0 padding and 0 margin.
Then make sure your math is solid.

HTML & CSS: How to create four equal size tabs that fill 100% width?

I am trying to create a navigation panel for my website. I would like it to consist of:
Four tabs in equal size with text-centered in each tab.
They should fill the whole page width.
I would really like the design to be flexible and browser friendly. I have tried various float techniques, but I can't get it to work. I hope that you can help me out!
Thank you.
HTML
EDIT: it's 2015 and HTML5 has been there for a while; following code should be inside a nav element (html5doctor) with landmark ARIA attribute role="navigation" on it (and 99.9% of the time be unique in any given page).
A navigation panel should use an unordered list of links:
<ul id="nav">
<li>One</li>
<li> Second</li>
<li>Third</li>
<li>Fourth and last, so large that... worst case</li>
</ul>
CSS
EDIT2: It's 2017, just use Flexbox 😲 (with or without flex-wrap: wrap)
inline-block is useful but has one drawback: whitespace between two elements must be carefully managed. Whether removed or no </li> in HTML5 or </li> at the beginning of the following line stuck like </li><li>next item or other tricks, you still have to do something or it'll create a ~4px gap between 2 elements.
25% + 25% + 25% + 25% doesn't equal 100% on all browsers if the total isn't a multiple of 4. Each browser has its own rounding method.
If you want elements to total 100% width and equal width, another method is to use display: table (and table-cell) with table-layout: fixed to force browsers to use the other table algorithm, the one that doesn't try to adapt cells width to content but respect the widths wanted by the designer/developer as far as possible.
ul {
margin: 0;
padding: 0;
}
li {
list-style-type: none;
}
#nav {
display: table;
table-layout: fixed;
text-align: center;
}
#nav li {
display: table-cell;
width: 25%;
padding-right: 1px;
height: auto;
vertical-align: bottom;
}
#nav a {
display: block;
min-height: 100%;
padding: 4px 10px;
background-color: #222;
color: white;
border-radius: 6px 6px 0 0;
}
Fiddle
http://jsfiddle.net/PhilippeVay/aHCy3/1/
edit: http://jsfiddle.net/PhilippeVay/aHCy3/2/ with another method for space between each tab, courtesy of my colleague.
You don't need floats for this. Just set the width to 25%, or a tiny bit less than 25%. If you're using this on a block level element, set display: inline-block. This will work for all browser sizes, as well as respond to window resize.
HTML
<div class="nav">Nav 1</div>
<div class="nav">Nav 2</div>
<div class="nav">Nav 3</div>
<div class="nav">Nav 4</div>​
CSS
body, html {
width: 100%;
padding: 0;
margin: 0;
}
.nav {
width: 24%; /*Slightly less than 1/4th of the width*/
display: inline-block;
padding: 0;
margin: 0;
text-align: center;
}​​
Live demo
css:
.tab {
float: left;
width:25%;
height:25px;
background:black;
border:1px solid #fff;
box-sizing: border-box;
}​
html:
<div class="tab"></div>
<div class="tab"></div>
<div class="tab"></div>
<div class="tab"></div>​
jsfiddle: http://jsfiddle.net/zP7Xh/6/

Is it possible for horizontally oriented tiles to stretch their parent's width in CSS?

If I have this layout in HTML:
<div>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
With this CSS:
div
{
width: 200px;
overflow: hidden;
}
ul
{
list-style: none;
height: 100px;
}
li
{
display: block;
width: 100px;
height: 100px;
float: left;
}
I want the <ul> to stretch to the size of its content (400px), rather than two of the <li> elements wrapping down onto the next line.
Is this possible with CSS? I've always just counted the <li> elements with JavaScript and set the <ul> width like that.
This should do it (see fiddle; of course, it hides some because of your overflow: hidden on the div, remove that and you see it is working):
ul
{
list-style: none;
height: 100px;
white-space: nowrap;
}
li
{
display: inline-block;
width: 100px;
height: 100px;
}
Though to actually get the ul to stretch in size (it doesn't with just the above code), it also needs to have display: inline-block (see fiddle) or a float applied (see fiddle) to get it to take on the size of its contents.
When you float the <li> like that, you effectively remove it from the <ul>. Are you sure that's what you want to do? Why not just float the whole list?