user2171796
user2171796

Reputation: 431

Why doesn't the inner box expand to the width of the outer box?

This is not really an academic question. It came up during work. I just boiled it down to a simple question.

I put one div inside another. I expect the inner div to take on the width of the outer div. Initially, that is what happens.

Then if I put a bunch of text in the outer div so that it is wider than the viewport, tell the outer div not to wrap text, and tell the outer div to scroll, the situation changes.

The inner div takes on the width of the viewport. You can scroll the outer div back and forth, and see that the outer div is the width of the text, but the inner div is the width of the viewport.

What's going on here? I would have said that the inner div took on the width of the outer div, period.

https://plnkr.co/edit/XaysEo?p=preview

  <body>
    <my-app>
    loading...
  </my-app>
  <div>
    This is after the angular part.
  </div>
  <div id="outerDiv" style="background-color:blue;white-space:nowrap;overflow-x:scroll;">
    This is a bunch of text.  It is here to make the outer box wider than the viewport.  Today, Bette and I had lunch at Outback.  Outback's steak was good.
    <div id="innerDiv" style="background-color:red;">
      Just some text to make it appear.
    </div>
  </div>
  <div>
    &nbsp;
  </div>
  <div id="outerDivWidth">
    Hello?
  </div>
  <div id="innerDivWidth">
    Hello?
  </div>
  <script>
    let od = document.getElementById('outerDiv');
    console.log(od);
    let ow = od.offsetWidth;
    console.log(ow);
    let odw = document.getElementById('outerDivWidth')
    odw.textContent = ow;

    let id = document.getElementById('innerDiv');
    let iw = id.offsetWidth;
    let idw = document.getElementById('innerDivWidth')
    idw.textContent = iw;
  </script>
  </body>

Upvotes: 4

Views: 458

Answers (3)

agrm
agrm

Reputation: 3842

I'll give a step-by-step explanation as per my understanding of how browsers handle and render block level elements.

Width of block level elements Ref

The width of a <div>, which is a block level element by default, will equal the computed width of the containing block generated by its parent.

This means #innerDiv will stretch to match the computed width of #outerDiv, and #outerDiv will stretch to match the computed width of the <body>. In your case this means they will be more or less equal to the viewport width.

Anonymous block boxes Ref

When a block container box (like #outerDiv) contains another block-level box (#innerDiv), it will be forced to contain only block-level boxes.

In your case you also have text inside #outerDiv, but outside #innerDiv. This text will be rendered as if it was the contents of an anonymous block-level box. Just as if your first line of text was wrapped inside an invisible <div>.

Suppressing line breaks

Applying white-space:nowrap to #outerDiv will prevent the text inside the anonymous block-level box from breaking to a second line. Its contents will ignore the right margin of its parent element, and eventually flow past the right edge of #outerDiv. Combined with overflow-x:scroll this will enforce a horizontal scrollbar.

The result

As you describe it; "The inner div takes on the width of the viewport. You can scroll the outer div back and forth. What's going on here? I would have said that the inner div took on the width of the outer div, period"

You are correct as #innerDiv actually has the width of #outerDiv. And as the width of #outerDiv depends on its parent, it will be unaffected by the width of the anonymous block-level box inside it.

What we are seeing is more of an optical illusion so to speak. It is actually the anonymous block box and #innerDiv scrolling inside the block container generated by #outerDiv.

This becomes visible if you apply a background image to #outerDiv. As you scroll, you will see the background image staying put:

// You'll see .outer and .inner have equal widths
let o = document.querySelector('.outer'),
    i = o.querySelector('.inner');
console.log(o.clientWidth, i.clientWidth);
body {
  /*Only for this test*/
  max-width: 500px;
}

.outer {
  background-color: blue;
  white-space: nowrap;
  overflow-x: scroll;
  background: url(https://www.google.com/logos/doodles/2017/kenya-independence-day-2017-5686012280832000-2x.png) 50% 50%;
}

.inner {
  display: block;
  background-color: red;
}
<div class="outer">
  This is a bunch of text. It is here to make the outer box wider than the viewport. Today, Bette and I had lunch at Outback. Outback's steak was good.
  <div class="inner">
    Just some text to make it appear.
  </div>
</div>

Grid to the rescue?

If you are looking for a solution, there are several ways as proposed already in other answers. Another take would be to apply display:grid to #outerDiv. I'm still not too familiar with CSS grids but my understanding is this will enforce #innerDiv to match the width of the rest of the contents of #outerDiv. This will probably not work for all cases but it will render a result more like what you describe you would expect.

Note the element widths in the console where .inner is wider than .outer.

// You'll see .inner is wider than .outer
let o = document.querySelector('.outer'),
    i = o.querySelector('.inner');
console.log(o.clientWidth, i.clientWidth);
body {
  /*Only for this test*/
  max-width: 500px;
}

.outer {
  display: grid;
  background-color: blue;
  white-space: nowrap;
  overflow-x: scroll;
}

.inner {
  display: block;
  background-color: red;
}
<div class="outer">
  This is a bunch of text. It is here to make the outer box wider than the viewport. Today, Bette and I had lunch at Outback. Outback's steak was good.
  <div class="inner">
    Just some text to make it appear.
  </div>
</div>

Upvotes: 2

barbarossa
barbarossa

Reputation: 129

The standard width of a div is set to 100%, related to its parent div. So, your outerDiv spans 100% of the viewport. Now, when you allow overflow, the offsetWidth of the div doesn't change and remains 100% of the viewport. What's changed, is the content's width*.

I assume, the issue results because the innerDiv still relates to the width of the outerDiv, and not to the expanded content's width (might be a minor negligence of the W3C). I hope this is relatively clear?

*So, you're actually scrolling the content within the borders of the div. Not the div within the borders of the body.


A possible solution:

You can get the scrollWidth of an element (which equals the content's width). Hence, using JavaScript, you can set the innerDiv's width accordingly:

let od = document.getElementById('outerDiv');
let ow = od.scrollWidth;

let id = document.getElementById('innerDiv');
let iw = id.style.width = ow+"px";

This post might help.

Upvotes: 1

Tom Oakley
Tom Oakley

Reputation: 6403

You can use display: table; on the #outerDiv. Not ideal as tables (even CSS tables) are not for layout, but it works. May introduce some side-effects though.

This related question is useful for explaining in a bit more detail.

EDIT: explaining in more detail as per MrLister's request. I believe that width, for a block element, will default to 100% of the parent element unless explicitly stated. However, as width is not stated on the parent, it's defaulting to 100% of the viewport as that is the root element. (Try setting width to 1000px on the #outerDiv, it the #innerDiv will also stretch). Using display: table resets this as all block child elements stretch to the width of the table.

Upvotes: 0

Related Questions