Reputation: 14404
I'm trying to center some content. The content is larger than the parent and therefore requires scrolling. There is also a header at top. For some reason, the scroll height clips the scrollable content. Thoughts on how I should fix the issue?
Here's the fiddle: https://jsfiddle.net/xgsqyu45/
.app {
height: 400px;
}
.header {
width: 100%;
height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
border: 1px solid red;
height: 100%;
}
.scroll {
overflow: scroll;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.content {
height: 600px;
width: 300px;
margin: 12px;
border: 2px solid blue;
}
<div class="app">
<div class="header">
</div>
<div class="container">
<div class="scroll">
<div class="content">
</div>
</div>
</div>
</div>
Upvotes: 3
Views: 1567
Reputation: 1685
You can use grid
on the scroll
container to prevent the misalignment with the header
, and align-self: center;
to the content
to align itself vertically inside the container
and justify-self
to align itself horizontally without being clipped. I've also changed overflow
to overflow: auto;
as you only need the vertical scrolling in your sample. I've included two snippets to show the behavior when the content
is bigger than container and another one when content
is smaller than smaller than scroll
, both with the same code. See the snippets below:
Behavior when content
is bigger than scroll
:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.app {
height: 400px;
}
.header {
width: 100%;
height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
border: 1px solid red;
height: 100%;
}
.scroll {
overflow: auto;
height: 100%;
display: grid;
}
.content {
height: 1000px;
width: 300px;
margin: 12px;
border: 2px solid blue;
align-self: center;
justify-self: center;
}
<div class="app">
<div class="header">
</div>
<div class="container">
<div class="scroll">
<div class="content">
</div>
</div>
</div>
</div>
Behavior when content
is smaller than scroll
(same code):
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.app {
height: 400px;
}
.header {
width: 100%;
height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
border: 1px solid red;
height: 100%;
}
.scroll {
overflow: auto;
height: 100%;
display: grid;
}
.content {
height: 1000px;
width: 2000px;
margin: 12px;
border: 2px solid blue;
align-self: center;
justify-self: center;
}
<div class="app">
<div class="header">
</div>
<div class="container">
<div class="scroll">
<div class="content">
</div>
</div>
</div>
</div>
Upvotes: -1
Reputation: 18408
The documentation says:
center
The flex items' margin boxes are centered within the line on the cross-axis. If the cross-size of an item is larger than the flex container, it will overflow equally in both directions.
The flex center is calculated using height
400px. So align-items: center
is making the content overflow by 100px from top.
100 = (600 - 400) / 2
here,
600 = content height
400 = .scroll and container height
Notice how the .scroll
gets 500px scrollHeight and not 600px. It's 100px less.
Solution:
We can transform the content if it is taller than the container using CSS calc() function. Assuming you want to keep shorter content at center we can use below solution. View it in 'full page' mode.
:root {
--app-height: 400px;
--content-margin-top: 12px;
}
* {
box-sizing: border-box;
}
.app {
height: var(--app-height);
}
.header {
width: 100%;
height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
border: 1px solid red;
height: 100%;
position: relative;
}
.scroll {
overflow-y: scroll;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.content {
height: 600px;
width: 300px;
margin: 12px;
border: 2px solid blue;
position: relative;
transform: translateY(calc((max(100% + var(--content-margin-top), var(--app-height)) - var(--app-height)) / 2));
}
/* center lines for debugging */
.content::after {
content: "-";
color: blue;
position: absolute;
top: 50%;
left: 0%;
transform: translateY(-50%);
width: 100%;
border-bottom: 1px dashed rgba(0, 0, 255);
line-height: 3px;
}
.container::after {
content: "-";
color: red;
position: absolute;
top: 50%;
left: 0%;
transform: translateY(-50%);
width: 100%;
border-bottom: 1px dashed rgba(255, 0, 0);
line-height: 3px;
}
<div class="app">
<div class=header></div>
<div class="container">
<div class="scroll">
<div class="content">content1</div>
<div class="content" style="height: 200px;">content2</div>
</div>
</div>
</div>
Dashed lines mark the center for contents and container.
Update 10th Feb 2022 : Simple solution to cover the new requirements mentioned in comments.
I think we are over complicating things. We just need margin:auto
and flex-shrink:0
to address all the issues:
* {
box-sizing: border-box;
}
.app {
height: 400px;
width: 80%;
margin-bottom: 5rem;
}
.header {
width: 100%;
height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
border: 1px solid red;
height: 100%;
position: relative;
}
.scroll {
overflow: auto;
height: 100%;
padding: 12px;
display: flex;
}
.content {
position: relative;
border: 2px solid blue;
margin: auto;
flex-shrink: 0;
}
/* center lines for debugging */
.content::after {
content: "-";
color: blue;
position: absolute;
top: 50%;
left: 0%;
transform: translateY(-50%);
width: 100%;
border-bottom: 1px dashed rgba(0, 0, 255);
line-height: 3px;
}
.container::after {
content: "-";
color: red;
position: absolute;
top: 50%;
left: 0%;
transform: translateY(-50%);
width: 100%;
border-bottom: 1px dashed rgba(255, 0, 0);
line-height: 3px;
}
<div>1. Small content</div>
<div class="app">
<div class=header></div>
<div class="container">
<div class="scroll">
<div class="content" style="height: 100px; width:100px;">content1</div>
</div>
</div>
</div>
<hr>
<div>2. Taller content</div>
<div class="app">
<div class=header></div>
<div class="container">
<div class="scroll">
<div class="content" style="height: 600px; width:100px;">content1</div>
</div>
</div>
</div>
<hr>
<div>3. Wider content</div>
<div class="app">
<div class=header></div>
<div class="container">
<div class="scroll">
<div class="content" style="height: 100px; width:120vw;">content1</div>
</div>
</div>
</div>
<hr>
<div>4. Taller and wider content</div>
<div class="app">
<div class=header></div>
<div class="container">
<div class="scroll">
<div class="content" style="height: 600px; width:120vw;">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Rem ad perspiciatis earum atque dolorum laborum corrupti animi? Dolor, laudantium! Non cupiditate in eligendi modi temporibus at maiores iste tempora accusantium dolorem quibusdam magnam
totam, rem voluptatum distinctio sapiente debitis praesentium unde esse corporis perferendis id amet autem. Reprehenderit, non molestias?
</div>
</div>
</div>
</div>
In case 3, note scrollbars shifts content a little bit due to this issue. User agents handle scrollbar widths differently.
Upvotes: 4
Reputation: 10398
The "intermediate flexbox" solution provides automatic switching between center and edge alignment of content, but only in one direction - either horizontally or vertically. Therefore, I propose to choose which centering to keep and which to sacrifice.
For example, in this solution, the narrow content is centered and the wide content starts from the left edge of the container. In this case, the content will start from the top edge, regardless of its height. For clarity, I have included four examples with different content sizes in the code.
(The "vertical centered" solution is below in the first version of the answer.)
https://jsfiddle.net/glebkema/8yqxcm9e/
.container {
border: 2px solid red;
display: flex;
height: 250px;
overflow: auto;
}
.intermediate {
display: flex;
flex-grow: 1;
justify-content: center;
}
.content {
border: 2px solid blue;
height: 150px;
margin: 12px;
width: 150px;
}
.content.high {
height: 600px;
}
.content.wide {
width: 900px;
}
@media (min-width: 500px) {
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
}
body {
margin: 0;
}
<div class="grid">
<div class="container">
<div class="intermediate">
<div class="content">Narrow content is centered horizontally</div>
</div>
</div>
<div class="container">
<div class="intermediate">
<div class="content wide">Narrow content is centered horizontally</div>
</div>
</div>
<div class="container">
<div class="intermediate">
<div class="content high">Wide content starts from the left edge</div>
</div>
</div>
<div class="container">
<div class="intermediate">
<div class="content wide high">Wide content starts from the left edge</div>
</div>
</div>
</div>
I've understood the desired behavior like this:
Problem:
If we just use the justify-content: center;
property, and the height of the child block is greater than the height of the parent, then the top part of the child block is cut off and not available for vertical scrolling.
Solution:
Use a hierarchy of three nested blocks too.
But make the outer container a flex block with a column direction, and give it the overflow-y: scroll;
property.
And the intermediate block remains a flex block with the justify-content: center;
and flex-grow: 1;
properties.
.container.scroll {
display: flex;
flex-direction: column;
overflow-y: scroll;
}
.intermediate {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
}
So if the child block is smaller than the parent block, it is centered. And if its height is greater than the parent's height, it starts from the parent's top edge.
Notice:
In the source code, the .app
and .header
blocks have a height in pixels, and .container
has a height of 100%
. Thus, it goes beyond the .app
block.
To prevent this, I've used flex-boxes for the entire layout too. I also changed the header's height
property to min-height
to prevent it from shrinking as the container grows.
And here are demos for similar tasks:
.app {
height: 400px;
display: flex;
flex-direction: column;
}
.header {
min-height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
border: 1px solid red;
flex-grow: 1;
}
.container.scroll {
display: flex;
flex-direction: column;
overflow-y: scroll;
}
.intermediate {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
align-items: center;
}
.content {
height: 600px;
width: 300px;
margin: 12px;
border: 2px solid blue;
}
.content.small {
height: 250px;
}
.app + .app {
margin-top: 50px;
}
<div class="app">
<div class="header"></div>
<div class="container scroll">
<div class="intermediate">
<div class="content">Large content starts from the container's top edge</div>
</div>
</div>
</div>
<div class="app">
<div class="header"></div>
<div class="container scroll">
<div class="intermediate">
<div class="content small">Small content is centered</div>
</div>
</div>
</div>
Upvotes: 0
Reputation: 15349
Simplest solution - replace the margin of your .content
element to this:
.content {
margin:auto 12px;
}
Updated jsfiddle: https://jsfiddle.net/odjnL09g/
Explanation: Essentially what is required is that margin-top:auto
and margin-bottom: auto
are set - this is explicit to children of flexbox elements. This keeps the element vertically centered when it's smaller than the parent container, but when it's larger it scrolls properly. If you need the 12px vertical spacing, you should place a child element inside .content
and transfer any styling to that and add a padding
property to the .content
.
Edit: Updating the answer to reflect what the OP wanted, centering, both vertically and horizontally.
It required that the .content
has margin:auto
applied and remove the justify-content:center
from the parent flexbox container to avoid a nasty clipping bug.
.scroll {
// justify-content:center; // This is commented out to avoid clipping bug
}
.content {
margin:auto;
}
Here is the fiddle to the final working code: https://jsfiddle.net/c9foy54q/
Upvotes: 1
Reputation: 449
Just use display:grid instead of display:flex in .scroll class:
.app {
height: 400px;
}
.header {
width: 100%;
height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
marign-top: 60px;
border: 1px solid red;
height: 100%;
}
.scroll {
overflow: scroll;
height: 100%;
display: grid;
justify-content: center;
align-items: center;
}
.content {
height: 600px;
width: 300px;
margin: 12px;
border: 2px solid blue;
}
<div class="app">
<div class="header">
</div>
<div class="container">
<div class="scroll">
<div class="content">
</div>
</div>
</div>
</div>
Upvotes: 0
Reputation: 1384
easily with set align-items: flex-start;
on .scroll
you can prevent that.
.app {
height: 400px;
}
.header {
width: 100%;
height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
border: 1px solid red;
height: 100%;
}
.scroll {
overflow: scroll;
height: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
}
.content {
height: 600px;
width: 300px;
margin: 12px;
border: 2px solid blue;
background-image: url(https://yari-demos.prod.mdn.mozit.cloud/en-US/docs/Web/HTML/Element/img/clock-demo-400px.png);
}
<div class="app">
<div class="header">
</div>
<div class="container">
<div class="scroll">
<div class="content">
</div>
</div>
</div>
</div>
Upvotes: -1
Reputation: 1604
You are limiting the height of the .content
element but you do not set overflow:auto
or overflow:scroll
on it - you only do on it's parent.
By the way, the height:100%
on .container
is problematic because the header already takes part of the parent's height. Better leave this height to be automatic.
Demo: Here is the code in the question, with a long text added inside `.content', and below that the same code with little modifications in the CSS.
.app {
height: 400px;
}
.header {
width: 100%;
height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
border: 1px solid red;
height: 100%;
}
.scroll {
overflow: scroll;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.content {
height: 600px;
width: 300px;
margin: 12px;
border: 2px solid blue;
}
<div class="app">
<div class="header">
</div>
<div class="container">
<div class="scroll">
<div class="content">
Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text
</div>
</div>
</div>
</div>
.app {
height: 400px;
}
.header {
width: 100%;
height: 44px;
background-color: grey;
border: 2px solid;
}
.container {
border: 1px solid red;
height: calc(100% - 50px);
display: flex;
justify-content: center;
align-items: center;
}
.scroll {
overflow: scroll;
margin: 12px;
height: calc(100% - 24px);
border: 2px solid blue;
}
.content {
width: 300px;
}
<div class="app">.app
<div class="header">.header
</div>
<div class="container">.container
<div class="scroll">.scroll
<div class="content">.content
Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text
</div>
</div>
</div>
</div>
Upvotes: 0
Reputation: 270
If you set style for scrollbar see this code
.auto {
border: 1px solid green;
height: 70px;
overflow-y: auto;
display: inline-block;
}
.scroll {
height: 70px;
overflow-y: auto;
display: inline-block;
padding-right: 17px;
}
.scroll div {
border: 1px solid gold;
padding: 2px;
float: left;
}
::-webkit-scrollbar {
width: 10px;
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
}
<p>scroll bar thakes up the whole div:</p>
<div class="auto">
<div>
a<br /> b
<br /> c
<br /> d
<br />
</div>
</div>
<p>it should look like this :</p>
<div class="scroll">
<div>
a<br /> b
<br /> c
<br /> d
<br />
</div>
</div>
<p>but without this effect when there is no overflow:</p>
<div class="scroll">
<div>
a<br /> b
<br /> c
<br />
</div>
</div>
Upvotes: 0