David
David

Reputation: 14404

How to center scrollable content with header?

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

Answers (8)

Yong
Yong

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

the Hutt
the Hutt

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

Gleb Kemarsky
Gleb Kemarsky

Reputation: 10398

Update. Content can be both higher and wider than container

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>

1st answer. Content can be higher but not wider than container

I've understood the desired behavior like this:

  1. If the child box is small, it should be centered on the parent.
  2. If the child box is large, then it should not be cut off at the top, but start from the top edge of the parent.
  3. At the same time, the sizes of the blocks are not known in advance and their behavior must be adjusted to the sizes of each other.

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

scooterlord
scooterlord

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

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

Ahmad MRF
Ahmad MRF

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

Noam
Noam

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

ali memar
ali memar

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

Related Questions