vio
vio

Reputation: 177

How percentage truly works compared to other units in different situations

so basically I've been experimenting with CSS recently and I came across something which looked seemed new to me. I usually use units such as em, or px when setting the padding of an element but this time I tried using percentages and to my surprise it worked very differently than the other units.

So I set up three different situations:

body {
  margin: 0;
}
.container {
  display: flex;
  background-color: lightblue;
  justify-content: space-between;
  margin: 30px 0;
}
.flex {
   display: flex;
}
.container a {
  color: #000;
  padding: 50px;
  background-color: rgba(0, 0, 0, 0.3)
}
.container.two {
  background-color: lightgreen;
}
.container.two a {
  padding: 30%;
}
.container.three {
  background-color: beige;
}
.container.three a {
  padding: 30%;
}
<div class="container">
  <div class="flex">
    <a href="#">Item 1</a>
    <a href="#">Item 2</a>
  </div>
  <div class="flex">
    <a href="#">Item 1</a>
    <a href="#">Item 2</a>
  </div>
</div>
<div class="container two">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>
<div class="container three">
  <div>
    <a href="#">Item 9</a>
    <a href="#">Item 10</a>
  </div>
  <div>
    <a href="#">Item 11</a>
    <a href="#">Item 12</a>
  </div>
</div>

In each situation I have a navbar with 2 items on either side of the navbar. I use pixels and percentage to set a padding to the items. The navbars itself are flex containers. In the first two cases the container of the items were flex containers but the 3rd one was not.

  1. Using pixel (along with flexbox): When using pixel along with flexbox everything seemed normal to me.

  2. Using percentages (along with flexbox): So this is where things started to get weird. The items get pushed out of the navbar.

  3. Using percentages (without flexbox): Here, the items did not get pushed of the navbar but instead just grew inside their direct parent and eventually they started wrapped at higher percentage values.

When I remove flex from the navbars everything gets weirder.

I just wanna know why percentage acts differently than the other units when dealing with flexboxes. I've never used percentages to do such a thing so this is very new to me. I am curious. I'm suspecting this has something to do with flexbox and not the percentage unit directly.

Here is a reproduction of the same:

https://jsfiddle.net/sLnvzrmb/

Upvotes: 2

Views: 102

Answers (2)

Temani Afif
Temani Afif

Reputation: 273571

You are facing the combination of a lot of things at once.

First, percentange used with padding will consider the parent element width as reference (and not the element itself)

The percentage is calculated with respect to the width of the generated box's containing block, even for 'padding-top' and 'padding-bottom' ref

The containing block is the parent element in our cases.

Second, applying padding on inline element is not the same as applying it to block element. top and bottom padding on inline element will not affect the layout but you will have a kind of overlap:

The vertical padding, border and margin of an inline, non-replaced box start at the top and bottom of the content area, and has nothing to do with the 'line-height'. But only the 'line-height' is used when calculating the height of the line box. ref

Third, when using flexbox you have to consider the default nowrap behavior ref and the fact the flex item get blockified (even if their dispaly is set to inline)

The display value of a flex item is blockified .. ref


Now, if we consider the first case using flexbox. The items are blockified and percentage will consider the parent width as reference BUT you have a complex case of cyclic behavior because the parent (that is also a flex items) has its width based on its content. I know it's not easy to digest all these information so here is a step-by-step illustration to understand:

.container {
  display: flex;
  background-color: lightblue;
  justify-content: space-between;
}
.flex {
   display: flex;
   border:2px solid red;
}
.container a {
  color: #000;
  background-color: rgba(0, 0, 0, 0.3)
}
.container.two a {
  padding: 30%;
}
Before padding
<div class="container">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>
After padding
<div class="container two">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

Look closely at the red border I am adding and how its width is the same in both situations. The trick is that the browser will first ignore the padding (because it's a percentage value) to calculate the parent width and later will use that width to find the value of padding and you will logically have an overflow. An overflow equal to the padding you are having (30%*4 of the width exactly). The elements will not wrap due to the default nowrap behavior but you can disable this:

.container {
  display: flex;
  background-color: lightblue;
  justify-content: space-between;
}
.flex {
   display: flex;
   border:2px solid red;
   flex-wrap:wrap;
}
.container a {
  color: #000;
  background-color: rgba(0, 0, 0, 0.3)
}
.container.two a {
  padding: 30%;
}
Before padding
<div class="container">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>
After padding
<div class="container two">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

Now if you remove display:flex from the parent, you no more have flex item and now you deal with inline elements and you get the following

.container {
  display: flex;
  background-color: lightblue;
  justify-content: space-between;
}
.flex {
   border:2px solid red;
}
.container a {
  color: #000;
  background-color: rgba(0, 0, 0, 0.3)
}
.container.two a {
  padding: 30%;
}
Before padding
<div class="container">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>
After padding
<div class="container two">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

Same logic as before, the width is first defined and later used as reference but you will not have overflow because inline element will wrap to the next line AND you will have the overlap between the lines because padding-top/bottom doesn't apply to inline elements.

add inline-block and see the difference

.container {
  display: flex;
  background-color: lightblue;
  justify-content: space-between;
}
.flex {
   border:2px solid red;
}
.container a {
  color: #000;
  display:inline-block;
  background-color: rgba(0, 0, 0, 0.3)
}
.container.two a {
  padding: 30%;
}
Before padding
<div class="container">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>
After padding
<div class="container two">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

Finally, if you remove flexbox from the navbar, the parent element that was a flex item is now a block element and its width is no more based on its content but it's now full width by default and you have

.container {
  background-color: lightblue;
  justify-content: space-between;
  width:100px;
}
.flex {
   border:2px solid red;
}
.container a {
  color: #000;
  background-color: rgba(0, 0, 0, 0.3)
}
.container.two a {
  padding: 30%;
}
Before padding
<div class="container">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>
After padding
<div class="container two">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

bigger width
<div class="container two" style="width:300px">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

full width
<div class="container two" style="width:auto">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

So our parent element is full width, the padding:30% is based on that width. Imagine how big it will be since each element is having 60% of padding (left + right) so a total of 120%. and our elements are inline elements so the top and bottom will not apply and will simply overlap

Use inline-block and you disable the overalp and now top and bottom are part of the layout:

.container {
  background-color: lightblue;
  justify-content: space-between;
  width:100px;
}
.flex {
   border:2px solid red;
}
.container a {
  color: #000;
  display:inline-block;
  background-color: rgba(0, 0, 0, 0.3)
}
.container.two a {
  padding: 30%;
}
Before padding
<div class="container">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>
After padding
<div class="container two">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

bigger width
<div class="container two" style="width:300px">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

full width
<div class="container two" style="width:auto">
  <div class="flex">
    <a href="#">Item 5</a>
    <a href="#">Item 6</a>
  </div>
  <div class="flex">
    <a href="#">Item 7</a>
    <a href="#">Item 8</a>
  </div>
</div>

Upvotes: 2

Zahid Wadiwale
Zahid Wadiwale

Reputation: 116

If you specify the width of a div as a percentage, it refers to the percentage of the divs parent's computed width, when you specify viewport it refers to percentage of the window screen. Pixels on other-hand are absolute unit they are not relative like percentage. That is the primary reason percentage acts differently with flexbox and not just flexbox but with everything. See some of this articles for reference: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Values_and_Units https://developer.mozilla.org/en-US/docs/Web/CSS/percentage

Upvotes: 0

Related Questions