7

Relevant information:

The page contains two elements:

  • An <aside> to the left.

  • A <main> to the right.

(Note: Throughout this post, heights are mentioned for the sake of completeness, but are irrelevant to producing this problem.)

All heights, widths, and margins are set with respect to var w = screen.width/100; (and var h = screen.height/100;) so that the page essentially looks the same in any display resolution. And they are set so that the width of <aside> and <main>, and the margin between them all add up to screen.width.

For example:

var w = screen.width/100;
document.getElementsByTagName('main')[0].style.width = 85.5*w + "px";
document.getElementsByTagName('aside')[0].style.width = 14*w + "px";
document.getElementsByTagName('aside')[0].style.marginRight = 0.5*w + "px";

(85.5 + 14 + 0.5 = 100)

The problem:

The <main> gets pushed down below the <aside> for unknown reasons. I can only think of a half-sensible hypothesis to somewhat explain this behavior.

However, if I set the font size of the body to 0 and zoom out (so that the elements take less space) and zoom back in, this gets fixed (I don't know why, and don't ask me how I found this out).

What is the reason for this behavior, and what is the proper fix?

The hypothesis (can be skipped):

The browser seems to think "What would happen if I display the scrollbars even though they are not needed?", and then notices that the scrollbars have a width > 0, which means that <aside> and <main> are taking more space than available (since they are set to take up 100% of the screen width, and now there is a scrollbar competing for the space). The browser therefore decides to reposition the <main> below the <aside> and ruin the design.

And now since <main> is under <aside> the elements no longer fit inside the screen and the scrollbars are actually needed now and therefore stay, even though they are the cause of their own existence (as far as this hypothesis goes).

Additional information:

  • I am not using any CSS-stylesheet: all my styling is done by JavaScript (for the simple reason that I want the sizes to depend on screen.width and screen.height).

  • The elements have display = "inline-block";. Using float produces horrendous behavior when the browser is anything but max size.

Here is the code to reproduce the problem:

<!DOCTYPE html>
<html>    
<body> 

<aside></aside>

<main></main> 

<script>
  var h = screen.height/100;
  var w = screen.width/100;

  var e = document.getElementsByTagName("aside")[0].style;
  e.display = "inline-block";
  e.backgroundColor = "lightblue";
  e.width = 14*w + "px";
  e.height = 69*h + "px";
  e.marginRight = 0.5*w + "px";

  e = document.getElementsByTagName("main")[0].style;
  e.display = "inline-block";
  e.backgroundColor = "green";
  e.width = 85.5*w + "px";
  e.height = 69*h + "px";   

  e = document.getElementsByTagName("body")[0].style;
  e.margin = e.padding = "0";
  e.backgroundColor = "black";
</script>
</body>
</html>
RaminS
  • 2,208
  • 4
  • 22
  • 30
  • Have you set `html, body {margin: 0; }` and using `box-sizing: border-box`? – Asons Nov 27 '15 at 21:16
  • 2
    Another tip, as you already use the percent to calc size, drop setting size to px, use % and you can skip calculate as browser will do that for you, which will increase performance. – Asons Nov 27 '15 at 21:23
  • 1
    Another tip, use vh (viewport height) and vw (viewport width) which is equivalent to screen height and width. – Jan Franta Apr 28 '16 at 12:59
  • @JanNahody No, it is absolutely not the same. It might give you the same result, _**if**_ the browser runs in maximized or theater mode though. – Asons Apr 29 '16 at 04:42
  • @LGSon You are right. Screen means device-size. I forgot. – Jan Franta Apr 29 '16 at 05:49
  • Have you thought about using media queries which will give you different grid sizes and can adapt accordingly and or use bootstrap for layout – codefreaK Apr 30 '16 at 19:27
  • _"all add up to screen.width"_ Not necessarily. The calculated values have fractions and the display pixels don't. So the browser may end up rendering 100w + 1 px. – a better oliver May 03 '16 at 13:27

7 Answers7

6

Update after the question edit

The reason why your main gets pushed down under aside is because you have an invisible space, in your case the line break but can be a simple blank space as well, between your elements in your markup, which actually adds size along with the elements making it all exceed 100% width, hence create the scroll bar.

That space, which exist for inline elements (which is not positioned with absolute or fixed, or floated), needs to be taken into account together with the elements size, when calculating their width to fit their parents width (in this case the body/viewport).

Here you can read more about it and how to get rid of it or make it become 0 in size (if to keep the elements inline).

Other ways to line up elements side-by-side is to use display: flex or display: table-cell, both with a similar behavior as inline elements (in the meaning of stacking horizontal in opposite to block elements, which stacks vertical), though doesn't suffer from the white space in the same way when it comes to its set size compared to actual size.

To clarify, i.e. if a width is set to 14.5% on a flex item, it takes 14.5% and no more, in opposite to an inline, which will be 14.5% plus the white space (where the white space size actually depends on the set font size)

Sample display: flex (recommended)

* {
  box-sizing: border-box;
}
html, body {
  margin: 0;
  height: 100%;
}

body {
  display: flex;
  height: 100%;
}
aside {
  width: 14%;
  margin-right: 0.5%;
  background-color: #f66;
}
main {
  width: 85.5%;  
  background-color: #66f;
}
<aside>
  aside
</aside>
<main>
  main
</main>

Sample display: table-cell (for older browsers)

* {
  box-sizing: border-box;
}
html, body {
  margin: 0;
  height: 100%;
}

body {
  display: table;
  width: 100%;
  height: 100%;
}
aside {
  display: table-cell;
  width: 14%;
  background-color: #f66;
}
main {
  display: table-cell;
  width: 85.5%;
  background-color: #66f;
}
.margin {
  display: table-cell;
  width: 0.5%;
}
<aside>
  aside
</aside>

<div class="margin"></div>

<main>
  main
</main>

Note:

Other ways to create a margin between the aside and main when using display: table, is to use cell padding, border width etc.


With your existing code, and since you don't use normal flow, absolute positioning could be an option.

<!DOCTYPE html>
<html>    
<body> 

<aside></aside>

<main></main> 

<script>
  var h = screen.height/100;
  var w = screen.width/100;

  var e = document.getElementsByTagName("aside")[0].style;
  e.position = "absolute";                                     /*  changed  */
  e.backgroundColor = "lightblue";
  e.width = 14*w + "px";
  e.height = 69*h + "px";
  e.marginRight = 0.5*w + "px";

  e = document.getElementsByTagName("main")[0].style;
  e.position = "absolute";                                     /*  changed  */
  e.backgroundColor = "green";
  e.width = 85.5*w + "px";
  e.height = 69*h + "px";   
  e.left = 14.5*w + "px";                                      /*  added  */
  
  e = document.getElementsByTagName("body")[0].style;
  e.margin = e.padding = "0";
  e.backgroundColor = "black";
  
</script>
</body>
</html>

Update 2

The problem with your code is it runs before the DOM is completely finished, hence create scroll bars. Try below sample, where I added a delay, and you'll see it works (when browser runs maximized).

<!DOCTYPE html>
<html>
<script>

  function runOnLoad() {

  setTimeout(function() {

  var h = screen.height/100;
  var w = screen.width/100;

  var e = document.getElementsByTagName("aside")[0].style;
  e.display = "inline-block";
  e.backgroundColor = "lightblue";
  e.width = 14*w + "px";
  e.height = 69*h + "px";
  e.marginRight = 0.5*w + "px";

  e = document.getElementsByTagName("main")[0].style;
  e.display = "inline-block";
  e.backgroundColor = "green";
  e.width = 85.5*w + "px";
  e.height = 69*h + "px";   

  e = document.getElementsByTagName("body")[0].style;
  e.margin = e.padding = "0";
  e.backgroundColor = "black";
  e.fontSize = "0";

  }, 200)

  }
</script>

<body onload="runOnLoad();"> 

<aside></aside>

<main></main> 

</body>
</html>
Community
  • 1
  • 1
Asons
  • 84,923
  • 12
  • 110
  • 165
  • What is the reason that the DOM isn't completely finished? Do you know? – RaminS Apr 29 '16 at 19:19
  • @Gendarme Don't know, it should work but obviously doesn't. And as I wrote in one of the previous comments we deleted, the normal way of having 2 inline block lined up next to each other is using `white-space: nowrap`, and when using that, you don't need delays, font-size trick, margins, etc ... it just works, like in this demo fiddle I made: https://jsfiddle.net/cvfht4om/ – Asons Apr 29 '16 at 19:31
  • I already have a solution by giving the width of the body `screen.width`. I am just curious to understand what is happening. – RaminS Apr 29 '16 at 19:40
  • @Gendarme What is happening is you try squeeze 2 child elements (aside, main), side by side, into a parent element (body), where the children is bigger than the parent, therefore the second child gets pushed down since there is no place. Either one set the width of the children to the parent (body's width), or tell the children not to wrap. All this is basic html when one understand how it works. Now, when these children is inline element, they have a white space margin which also needs to be taken into account when setting the parents width, hence tricks like `font-size: 0` etc. – Asons Apr 29 '16 at 20:04
  • I understand what you are describing. What I don't understand is how the size of the body changes when you add elements and why the delay fixes it. I'll probably ask question about that some time. – RaminS Apr 29 '16 at 20:21
  • These 'white-space margins' do not exist. All inline elements are treated as characters, as are white spaces. These white spaces in between elements take up space, as expected. However, these white spaces have NO relation to the inline element, other than being children of the same parent element and appearing in the same line. Therefore they cannot be called margins, nor do they belong to the inline element. Also, flex and table do not behave similar to inline-block at all. Therefore I think this answer is wrong. – Mr. Hugo May 04 '16 at 17:07
  • @JoostS You are right, it is not a "margin", it is a white space, so I updated my answer to address that. That white space though, is the reason why the 2 elements in the question doesn't fit, as when one set those to be 100%, they will be 100% + the white space, hence scroll bars appears, and 2:nd element jumps down below the 1:st. To make that white space "go away" and make them fit within 100%, one can do in several ways, which I linked to in my answer, or make the 2 display as flex, table, or event as block in combination with absolute positioning, which make my answer a valid solution. – Asons May 04 '16 at 17:36
  • Thank you! This addition does/is. – Mr. Hugo May 04 '16 at 17:39
  • @JoostS As my English is not native I sometimes use the wrong words making what I write become technical incorrect. Made a final update and will delete all these comments now, since they are obsolete. – Asons May 04 '16 at 20:53
  • OK. Thank you! Good idea. – Mr. Hugo May 04 '16 at 20:55
3

The scrollbar hypothesis is right. Here is the solution: How to get screen width without (minus) scrollbar?. I tested it and using document.body.clientWidth solves the issue. The other problem is that inline-block elements need to be chained together without spaces or newlines (to avoid unwanted margins).

<body><aside></aside><main></main></body>
Community
  • 1
  • 1
Mr. Hugo
  • 11,887
  • 3
  • 42
  • 60
2

You problem is the white-space between your aside and main, taking some space since your are using inline-block for display.

The answer from @Shah Abaz Khan is right, You have to get rid of this white space.

This can be done by removing it, as suggested :

<aside></aside><main></main> 

But you want to keep your html indentation, you can also use html comments <!-- -->:

<aside>
    //stuff
</aside><!--

--><main>
    //other stuff
</main>
hereismass
  • 223
  • 1
  • 14
1

A nearly complete explanation:

The following works (but there are other ways to do it as well):

  • Change the font size of the body to 0 so that the spaces between the inline-block elements become 0 (or use any other technique). This is thoroughly explained in other answers.

  • Give the body a fixed width. In this case screen.width. The reason to why this works is slightly complex. Note that if not specified, the body will take up the whole width of the browser (not necessarily the screen). There are two scenarios:

    • (Warning: This part is closer to a hypothesis than a proper answer.) When the browser is maximized (width of the screen and the browser are the same) the body will have the maximum width of screen.width (if needed), so the elements are not taking up too much space. However, since the body does not immediately know how wide it needs to be, it seems to not "be prepared", so the elements end up taking too much space after all. This is why giving the body its width in advance fixes this issue (there are also other ways around this - see other answers).

    • When the browser is not maximized, the maximum width of the body is the width of the browser window, and not the screen. The combined width of the elements is now larger than the width of the body, hence this behavior. Giving the body the fixed width of screen.width fixes this, but there are other solutions too (for example setting the white-space to no-wrap).


Here is the code after the suggested changes:

<!DOCTYPE html>
<html>    
<body> 

<aside></aside>

<main></main> 

<script>
  var h = screen.height/100;
  var w = screen.width/100;

  var e = document.getElementsByTagName("aside")[0].style;
  e.display = "inline-block";
  e.backgroundColor = "lightblue";
  e.width = 14*w + "px";
  e.height = 69*h + "px";
  e.marginRight = 0.5*w + "px";

  e = document.getElementsByTagName("main")[0].style;
  e.display = "inline-block";
  e.backgroundColor = "green";
  e.width = 85.5*w + "px";
  e.height = 69*h + "px";   

  e = document.getElementsByTagName("body")[0].style;
  e.margin = e.padding = "0";
  e.backgroundColor = "black";
  e.fontSize = "0";            //this line
  e.width = 100*w + "px";      //and this line have been added
</script>
</body>
</html>
RaminS
  • 2,208
  • 4
  • 22
  • 30
  • The explanation is simple: inline-block elements are treated as characters, thus newlines and spaces create actual spaces (space characters) in between them. Font size: 0 removes those spaces, because it makes them 0 in size. – Mr. Hugo Apr 27 '16 at 12:49
1

I am not using any CSS-stylesheet: all my styling is done by JavaScript (for the simple reason that I want the sizes to depend on screen.width and screen.height).

The elements have display = "inline-block";

You should be aware that this is a special units for what you are dealing with :

  • vh : viewport height
  • vw : viewport width

Example

Note: you can ignore the section "Visual Stuff"

/* VISUAL STUFF */

aside {
  background-color: hsl(0, 85%, 65%);
}
main {
  background-color: hsl(180, 85%, 65%);
}

/* SIZING STUFF */

body {
  /* I guess you don't need it */
  margin: 0;
  /* prevent white space display */
  font-size: 0;
  /* better keep*/
  white-space: nowrap;
}

aside,main {
  /* your constraint */
  display: inline-block;
  /* Set font size back to normal */
  font-size: initial;
  /* if you want the height to fit sreen */
  /* this means "height = 100% of viewport's height" */
  height: 100vh;
}

aside {
  /* 14% of viewport's width */
  width: 14vw;
  /* and so on ... */
  margin-right: 0.5vw;
}

main {
  width: 85.5vw;
}
<!DOCTYPE html>
<html>

<body>
  <aside>aside</aside>
  <main>main</main>
</body>

</html>
Apolo
  • 3,844
  • 1
  • 21
  • 51
1

This is a simple css solution

body {
     white-space: nowrap;
}

aside, main { 
     white-space: normal;
}

but since you are trying to avoid the use of style element completely, alternatively, JavaScript equivalent style setters can be used.:

<!DOCTYPE html>
<html>    
    <body> 

        <aside></aside>

        <main></main> 

        <script>
          var h = screen.height/100;
          var w = screen.width/100;

          var e = document.getElementsByTagName("aside")[0].style;
          e.whiteSpace = "normal";
          e.display = "inline-block";
          e.backgroundColor = "lightblue";
          e.width = 14*w + "px";
          e.height = 69*h + "px";
          e.marginRight = 0.5*w + "px";

          e = document.getElementsByTagName("main")[0].style;
          e.whiteSpace = "normal";
          e.display = "inline-block";
          e.backgroundColor = "green";
          e.width = 85.5*w + "px";
          e.height = 69*h + "px";   

          e = document.getElementsByTagName("body")[0].style;
          e.margin = e.padding = "0";
          e.whiteSpace = "nowrap";
         /*additional precaution*/
          e.overflow = "visible";
          e.backgroundColor = "black";
        </script>
    </body>
</html>

However, I notice that your calculations are depending on the screen size value, which is most probably the source of the initial problem to begin with.

Regards.

ayushgp
  • 4,891
  • 8
  • 40
  • 75
Bekim Bacaj
  • 5,707
  • 2
  • 24
  • 26
1

I found the reason!

Change your html to

<aside></aside><main></main> 

No space in between the tags because apparently inline block elements do take a ghost space in between! Tackle that by removing spaces between them or adding blank comments in between.

This isn't a "bug" (I don't think). It's just the way setting elements on a line works. You want spaces between words that you type to be spaces right? The spaces between these blocks are just like spaces between words. That's not to say the spec couldn't be updated to say that spaces between inline-block elements should be nothing, but I'm fairly certain that is a huge can of worms that is unlikely to ever happen.

Read more here.

Here is a demo JS FIDDLE

Shah Abaz Khan
  • 565
  • 4
  • 21
  • The ghost spaces are caused by the fact that the inline-block elements are treated as characters, thus newlines and spaces create actual spaces (space characters) in between them. I am pretty sure that this is exactly how it should be and is specified (hence the 'inline' in inline-block). – Mr. Hugo May 04 '16 at 15:15