Reputation: 169528
I am working on a project where I will be injecting markup into pages I don't have control over (an embed script). An interesting case has come up where my injected div is contained within a table. A child of my div is a horizontal menu that should scroll (overflow-x: auto
) when it exceeds the width of the parent element, but in the case where a parent element is a table without table-layout: fixed
, my injected content instead causes the parent table to expand, horribly breaking some layouts.
In this case, the table is contained within a fixed-width div, something like this:
<div style="width: 600px;">
<table width="100%">
<tr>
<td>
<div> <!-- my content --> </div>
</td>
</tr>
</table>
</div>
I found that setting table-layout: fixed
on the table fixes this problem. However, this markup is beyond my control -- I can only change markup/CSS starting from the innermost div.
I did find one hack that works: setting width: 100%; display: table; table-layout: fixed;
on my div. However, I'm not sure if this is compliant with any relevant specs, as the contents of this display: table
div are all display: block
.
Here is markup that reproduces the problem, as well as demonstrates the hack:
<div class="outer">
<table class="bad-table">
<tr>
<td>
<div class="wrapper">
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam imperdiet pharetra nunc at condimentum.</div>
<div class="target">
<ul class="menu">
<li style="background-color: #800;"></li>
<li style="background-color: #880;"></li>
<li style="background-color: #080;"></li>
<li style="background-color: #008;"></li>
</ul>
</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam imperdiet pharetra nunc at condimentum.</div>
</div>
</td>
</tr>
</table>
</div>
<div class="outer">
<table class="bad-table">
<tr>
<td>
<div class="wrapper hack">
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam imperdiet pharetra nunc at condimentum.</div>
<div class="target">
<ul class="menu">
<li style="background-color: #800;"></li>
<li style="background-color: #880;"></li>
<li style="background-color: #080;"></li>
<li style="background-color: #008;"></li>
</ul>
</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam imperdiet pharetra nunc at condimentum.</div>
</div>
</td>
</tr>
</table>
</div>
CSS:
.outer {
width: 200px;
border: 1px solid #0f0;
}
.bad-table {
width: 100%;
border: 1px solid #f00;
}
.target {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
}
.wrapper {
width: 100%;
}
.wrapper.hack {
display: table;
table-layout: fixed;
}
ul.menu {
list-style: none;
padding: 0;
margin: 0;
white-space: nowrap;
width: 100%;
}
ul.menu li {
display: inline-block;
width: 100px;
height: 50px;
padding: 0;
margin: 0;
}
(Fiddle)
Note that in the first example, the menu blocks cause the table (red border) to expand beyond its containing div (green border). Compare to the second example, which uses my (standards-violating?) hack that successfully prevents the parent table from growing, while also allowing the menu blocks to be scrolled. (Tested with Chrome and Firefox.)
Note the following constraints:
position: absolute
will tend to be problematic as they will remove my content from the page flow, making preceding and/or following content overlap the injected div. The usual fix for this (setting a fixed height) means my element will be unable to fit its height to match its contents.Is this hack a legitimate way to solve this problem, or is there a better approach?
Upvotes: 5
Views: 4496
Reputation: 1534
Potentially try display: inline-block
on the div you want to shrink. It worked for me just now.
Upvotes: -1
Reputation: 3606
It's possible to scale an element to its parent's width if that is a table cell, but you will have to workaround some negative consequences. Consider this:
<style>
table {
border-collapse: collapse;
width: 100%;
}
td {
border: 1px solid gray;
padding: 20px;
}
.prevent-table-expand-wrap {
position: relative;
}
.prevent-table-expand-inner {
/* Add ellipsis. */
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* Make div the width of its parent. */
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
</style>
<table>
<tr><td>
Column 1.
</td><td>
Column 2.
<div class="prevent-table-expand-wrap">
<div class="prevent-table-expand-inner" title="This contains way too much text, so prevent scaling table">
Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text. Lots of text.
</div>
</div>
</td><td>
Column 3.
</td></tr>
</table>
https://jsfiddle.net/zgwmLms1/19/
The way this works is that the wrapper div just gets width 100% of its container, the table cell. It doesn't have content that affects the scaling of the table columns (well, the nbsp is too narrow). The inner div then gets absolutely positioned and constrained to the wrapper on three sides. Might as well do all 4 sides, but it's not necessary to do both the top and bottom explicitly in this case, as the height of the wrapper and inner are the same.
The biggest difference with the other answer is that mine combines content that does influence the column width, and content that doesn't, in the same table cell. You can see that all 3 columns are evenly sized, that is due to the Column n
text.
Upvotes: 0
Reputation: 87313
table
's is always tricky but I have used position: absolute
with success in many situations, so my answer/question will be: Will this work for you?
Note though, the wrapper
is just a wrapper and should only contain the target
, so all content goes into the target
, or else the target
will overlap its siblings, unless fixed margins/paddings is set.
.outer {
width: 200px;
}
.bad-table {
width: 100%;
}
.wrapper {
position: relative;
}
.target {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: auto;
overflow: auto;
}
ul.menu {
list-style: none;
table-layout: fixed;
padding: 0;
margin: 0;
white-space: nowrap;
width: 100%;
}
ul.menu li {
display: inline-block;
width: 100px;
height: 50px;
padding: 0;
margin: 0;
}
<div class="outer">
<table class="bad-table">
<tr>
<td>
<div class="wrapper">
<div class="target">
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam imperdiet pharetra nunc at condimentum.</div>
<ul class="menu">
<li style="background-color: #800;"></li>
<li style="background-color: #880;"></li>
<li style="background-color: #080;"></li>
<li style="background-color: #008;"></li>
</ul>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam imperdiet pharetra nunc at condimentum.</div>
</div>
</div>
</td>
</tr>
</table>
</div>
For comparison, here is the same code snippet using display: table
.outer {
width: 200px;
}
.bad-table {
width: 100%;
}
.wrapper {
width: 100%;
display: table;
table-layout: fixed;
}
.target {
overflow: auto;
}
ul.menu {
list-style: none;
table-layout: fixed;
padding: 0;
margin: 0;
white-space: nowrap;
width: 100%;
}
ul.menu li {
display: inline-block;
width: 100px;
height: 50px;
padding: 0;
margin: 0;
}
<div class="outer">
<table class="bad-table">
<tr>
<td>
<div class="wrapper">
<div class="target">
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam imperdiet pharetra nunc at condimentum.</div>
<ul class="menu">
<li style="background-color: #800;"></li>
<li style="background-color: #880;"></li>
<li style="background-color: #080;"></li>
<li style="background-color: #008;"></li>
</ul>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam imperdiet pharetra nunc at condimentum.</div>
</div>
</div>
</td>
</tr>
</table>
</div>
Update
After some more testing, and reading these,
I can't find any other way to solve this case, so if there will be content before and after the uncontrolled outer div/table to which your content div's content need to have a normal flow, position: absolute
might not work depending how the rest of the page's content is set, but display: table; table-layout: fixed
will.
And according to the above sources, there shouldn't be any compliance issues using display: table
1, though you need to test the behavior in the major browsers, as they all might deal with div
's inside td
's different (I did test Chrome,FF,Edge,IE11 on Windows, all worked).
1 Specifically, this text allows for a display: block
element directly inside of a display: table
element, as the intermediate table-row and table-cell boxes are automatically created as anonymous:
- Generate missing child wrappers:
- If a child C of a 'table' or 'inline-table' box is not a proper table child, then generate an anonymous 'table-row' box around C and all consecutive siblings of C that are not proper table children.
- If a child C of a row group box is not a 'table-row' box, then generate an anonymous 'table-row' box around C and all consecutive siblings of C that are not 'table-row' boxes.
- If a child C of a 'table-row' box is not a 'table-cell', then generate an anonymous 'table-cell' box around C and all consecutive siblings of C that are not 'table-cell' boxes.
Upvotes: 3