Trying to do something theoretically very simple - using display: grid to have a fixed header row and a scrollable content row - but getting totally different results in Chrome and Safari. Distilled down to the basic problem:
The code:
<html>
<body>
<div id="root" style="background: gray; height: 100%; width: 100%; overflow: hidden">
<div id="grid" style="height: 100%; width: 100%; display:grid; grid-template-rows: auto 1fr; height: 100%">
<div id="row1" style="width: 100%; height: 100px; background: green; grid-row: 1">
</div>
<div id="row2-container" style="width:100%; height: 100%; max-height:100%; grid-row: 2; overflow: auto">
<div id="row2-content" style="width: 100%; height: 10000px; background: blue; overflow: visible">
</div>
</div>
</div>
</div>
</body>
</html>
Chrome (good):
Safari (bad):
In case the problem isn't obvious, in Safari row2-container is growing to fit its child rather than the available space in row 2 of the parent grid, and thus not scrolling the overflow from row2-content. In other words it seems height: 100% is getting ignored for grid children if the row size is 1fr.
How can I get what I want in both Safari and Chrome? Thanks!
The problem seems to have been fixed in Safari 14. I was using 13.
Also setting row2-container height: auto seems to give the desired result as well even in Safari 13. Either way this seems to have been a bug prior to version 14 that is now fixed.
Related
I'm trying to hide half-overflowed items in my CSS. I found a great method using css columns: https://stackoverflow.com/a/48378030/1305699
It works great in Firefox, but in Chrome I found some really odd behaviour under certain seemingly random combinations. For example, I managed to re-produce it by adding a height to one of the items, when the container is certain specific sizes, it causes the layout to randomly flicker into very odd sizes.
This is it working fine:
But sometimes when the last item, with a height: 20px style, it randomly looks like this:
In some positions, chrome even thinks it's rendering it correctly in the (hidden) second column, but it's actually being drawn half off, and at an odd size, in the first column:
Has anyone seen this issue and know a solution or workaround?
html,
body {
height: 100%;
width: 100%;
}
#container {
padding: 5px;
height: 50px;
resize: both;
overflow: hidden;
}
#container-2 {
height: 100%;
width: 200%;
column-count: 2;
column-fill: auto;
}
.item {
background: aliceblue;
margin: 2px;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
overflow: hidden;
}
.item div {
margin: 0.3rem;
}
.item span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<div id="container" style="width: 150px; outline: 1px red solid;">
<div id="container-2">
<div class="item">ONE LINE</div>
<div class="item">
<div>i</div><span>SECOND LINE</span>
</div>
<div class="item">
<div>i</div><span>THIRD LINE</span>
</div>
<div class="item">
<div>i</div><span style="height: 20px;">FOURTH LINE</span>
</div>
</div>
</div>
</body>
</html>
Ah, turns out I need the break-inside: avoid on the items which makes the column avoid attempting to break items over the column.
https://css-tricks.com/almanac/properties/b/break-inside/
I am trying to create a margin that disappears as the viewport becomes smaller, while the main content remains the same size as long as possible. This margin is supposed to have a maximum size, so using auto is out of the question.
In other terms, my question is how to control the shrink priority of different items on the website.
The Flexbox model allows to set the ratio of shrinkage, but not the order of it.
Which options, not using Javascript, do I have to achieve this?
Note: I'm answering my own question here but I'm also looking for better answers.
flex-shrink/flex-grow
One way is to have one flexbox item flex-shrink, and to have the other grow to the size it should have with flex-grow. This way, the latter item will 'shrink' (decrease its growth) first.
More than two levels can be achieved by nesting this construction.
.flex-container {
display: flex;
}
.first-to-shrink {
flex-shrink: 0;
flex-grow: 1;
flex-basis: 0px;
min-width: 0px;
background: coral;
}
.second-to-shrink {
flex-shrink: 1;
flex-grow: 0;
background: cornflowerblue;
}
<div class="flex-container">
<div class="first-to-shrink flex-container">
<div class="first-to-shrink">
This is going to shrink first!
</div>
<div class="second-to-shrink">
And then this one is going to shrink!
</div>
</div>
<div class="second-to-shrink flex-container">
<div class="first-to-shrink">
This one is going next!
</div>
<div class="second-to-shrink">
This is the last one to disappear!
</div>
</div>
</div>
Size Calculation
Another way is to calculate the size for each item as the difference of the viewport and the size of all other items that should shrink after it (this requires knowing the size of each of them), as follows:
.shrinkable {
overflow: hidden;
max-height: 100px;
}
#first-to-shrink {
height: calc(100vh - 300px);
background: coral;
}
#second-to-shrink {
height: calc(100vh - 200px);
background: cornflowerblue;
}
#third-to-shrink {
height: calc(100vh - 100px);
background: coral;
}
#fourth-to-shrink {
height: 100vh;
background: cornflowerblue;
}
<div id="first-to-shrink" class="shrinkable">
This is going to shrink first!
</div>
<div id="second-to-shrink" class="shrinkable">
And then this one is going to shrink!
</div>
<div id="third-to-shrink" class="shrinkable">
This one is going next!
</div>
<div id="fourth-to-shrink" class="shrinkable">
This is the last one to disappear!
</div>
If I set up a nested flexbox container like so:
<div class="container1">
<div class="grow1">
<div class="container2">
<div class="grow2"></div>
</div>
</div>
</div>
...and then set the width of grow2 such that it is wider than container1 then grow2 overflows container1.
I believe this should not happen since flex elements are supposed to shrink when they are larger than the flex container.
If I set the flex-basis of grow2 then this works as expected.
Please see the following example for a demo:
https://jsfiddle.net/chris00/ot1gjjtk/20/
Please use Chrome or Firefox for this
Furthermore, I read that the flexbox spec says that width and flex-basis should have the same effect (when using horizontal layouts) which they clearly don't.
Now I could just use flex-basis instead of width, but... Edge does the same thing for both flex-basis and width, and it does it in the "wrong" way. IE11 does it wrong also (although that appears to have multiple flexbox bugs).
Please check out the demo with Edge.
So how is this supposed to work?
Are there bugs in all browsers?
Is flex-basis actually supposed to be different from width (in simple horizontal layouts)?
Or is Edge correct and both width and flex-basis are supposed to overflow the parent container?
Finally, is there a workaround that can fix the overflow for Edge (and even IE11)?
.container1 {
margin-top: 10px;
display: flex;
width: 200px;
height: 50px;
background-color: red;
}
.grow1 {
flex-grow: 1;
height: 40px;
background-color: green;
}
.container2 {
display: flex;
height: 30px;
background-color: yellow;
}
.grow2a {
flex-grow: 1;
flex-basis: 400px;
height: 20px;
background-color: turquoise;
}
.grow2b {
flex-grow: 1;
width: 400px;
height: 20px;
background-color: turquoise;
}
<div class="container1">
<div class="grow1">
<div class="container2">
<div class="grow2a">Working (flex-basis)</div>
</div>
</div>
</div>
<div class="container1">
<div class="grow1">
<div class="container2">
<div class="grow2b">Not working (width)</div>
</div>
</div>
</div>
The Problem
As far as the spec is concerned, this isn't an issue pertaining to flex-basis, width, flex-grow or flex. It's something entirely different.
4.5. Implied Minimum Size of Flex
Items
To provide a more reasonable default minimum size for flex items, this
specification introduces a new auto value as the initial value of
the min-width and min-height properties defined in CSS 2.1.
In other words, a flex item, by default, cannot be smaller than the length of its content (essentially, the longest word or fixed-size element).
The item cannot stay within its container (or even render a scroll bar or ellipsis), because its content is not permitted to overflow. The content simply expands the item. This behavior applies to fixed-sizing, as well (such as the flex-basis: 400px in your code).
Again, the initial settings are:
min-width: auto, in row-direction
min-height: auto, in column-direction
For a more complete explanation see this post:
Why doesn't flex item shrink past content size?
Solution for Chrome, Safari, Firefox and Edge
The standard solution to this problem is simple: override the default.
In your code, add min-width: 0 to .grow1.
That solves the problem in Chrome, Safari, FF and Edge.
.container1 {
margin-top: 10px;
display: flex;
width: 200px;
height: 50px;
background-color: red;
}
.grow1 {
flex-grow: 1;
height: 40px;
background-color: green;
min-width: 0; /* NEW */
}
.container2 {
display: flex;
height: 30px;
background-color: yellow;
}
.grow2a {
flex-grow: 1;
flex-basis: 400px;
height: 20px;
background-color: turquoise;
}
.grow2b {
flex-grow: 1;
width: 400px;
height: 20px;
background-color: turquoise;
}
<div class="container1">
<div class="grow1">
<div class="container2">
<div class="grow2a">Working (flex-basis)</div>
</div>
</div>
</div>
<div class="container1">
<div class="grow1">
<div class="container2">
<div class="grow2b">Not working (width)</div>
</div>
</div>
</div>
revised fiddle 1
Solution for IE11
In IE11, contrary to spec guidance, the flex min-width / min-height default values are already 0, yet the flex item still breaks out.
The defaults are 0 because when the flexbox spec was first released, the min-* properties did not deviate from the CSS 2.1 initial values, which are 0.
Later, after browsers had completed their implementations, the flex min-* values were updated to auto. Chrome, Safari, FF and Edge made the update. IE11 did not.
The reason the flex items break out in IE11 relates to another issue: the browser wants an explicit width on the container
In your code, add flex-basis: 100% to .grow1.
More details here:
Why IE11 doesn't wrap the text in flexbox?
flexbox flex-basis: 0px in Chrome
.container1 {
margin-top: 10px;
display: flex;
width: 200px;
height: 50px;
background-color: red;
}
.grow1 {
flex-grow: 1;
height: 40px;
background-color: green;
flex-basis: 100%; /* NEW */
}
.container2 {
display: flex;
height: 30px;
background-color: yellow;
}
.grow2a {
flex-grow: 1;
flex-basis: 400px;
height: 20px;
background-color: turquoise;
}
.grow2b {
flex-grow: 1;
width: 400px;
height: 20px;
background-color: turquoise;
}
<div class="container1">
<div class="grow1">
<div class="container2">
<div class="grow2a">Working (flex-basis)</div>
</div>
</div>
</div>
<div class="container1">
<div class="grow1">
<div class="container2">
<div class="grow2b">Not working (width)</div>
</div>
</div>
</div>
revised fiddle 2 (IE11)
More Browser Discrepancies
Evidence appears to exist (in this question and other examples I've seen) that Webkit-based browsers are no longer honoring the auto default defined in the spec.
Moreover, the adherence to the auto standard may vary based on which property is used for sizing: flex-basis vs. width / height
As discussed in the following post, these properties should render the same way.
What are the differences between flex-basis and width?
In IE11, if you define a width for a flex-box and a child img element has a max-width of 100%, it will not respect the max-width. Has anyone found a solution for this?
This works in IE10, Chrome, and Firefox but breaks for IE11:
http://jsfiddle.net/3ky60heq/
.container {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
width: 500px;
height: 125px;
}
.image1 {
width: 100%;
}
.image2,
.image3 {
max-width: 100%;
}
<div class='container'>
<img class='image1' src="https://clagnut.com/sandbox/imagetests/wideimg.png">
</div>
<div class='container'>
<img class='image2' src="https://clagnut.com/sandbox/imagetests/wideimg.png">
</div>
<div class='container'>
<img class='image3' src="https://clagnut.com/sandbox/imagetests/smimg1.jpg">
</div>
I recognize this question has been asked before and has many different answers however every solution I found on StackOverflow or elsewhere either effectively forces the width of the image to 100% or breaks on other browsers.
I think I have it figured out.
If I set this style on the image:
flex-shrink: 0;
then it seems to work across the board.
http://jsfiddle.net/qgybon8q/2/
EDIT: My example was to complicated. So I made a simpler one.
http://codepen.io/knobo/pen/gaZVoN
.top grows beyond the available size of the html element. I don't want any content outside the current viewport which is 100vh, but I don't know the height of .bottom which can vary.
This line:
max-height: calc(100vh - 60px);
Makes it look like this works. But it does not, because I don't know the height of .bottom, which I just estimated to 60px;
<div class="page">
<div class="top">
<div class="left">Some text</div>
<div class="right">
<img src="http://placehold.it/350x1800">
</div>
</div>
<div class="bottom">
<button>Click</button>
<button>Click</button>
<button>Could be several lines</button>
</div>
</div>
html, body {
max-height: 100vh;
}
Css
.page {
display: flex;
flex-direction: column;
height: 100%;
}
.top {
flex: 1 1 auto;
display: flex;
overflow: hidden;
max-height: calc(100vh - 60px);
/*
I don't know the height of .bottom
It can change when browser is resized too..
How do I solve this.
*/
}
.left {
flex: 1 1 auto;
}
.bottom {
padding: 10px;
flex: 1 1 auto;
background-color: teal;
}
EDIT2: (included the original links from the first version)
http://codepen.io/knobo/pen/epboBv (css version. Does not work)
http://codepen.io/knobo/pen/wKRNjr/ (js version. Works. But I want to know how to do it with css.)
EDIT3
Screenshots:
When browser window is small, the bottom row disappears, when div.right is too big.
When browser window is large everything shows up (corectly)
This is how it should be: div.top is scaled down, and bottom row is stil visible. I was able to do it with javascript. I guess it should be possible with css too.
The solution is surprisingly easy.
.right {
position: relative;
/* width: Do something with width here. */
}
.nooverflow {
position: absolute;
}
then wrap the content of .right with class="nooverflow"
<div class="right">
<div class="overflow">
{{ Content of .right }}
</div>
</div>