Reputation: 431
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>
</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
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
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
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