Horizontally centering elements to their container

2019-02-25 17:34发布

I ran into an interesting CSS problem today, and I have been wracking my brain trying to solve it.

This is similar to the trivial problem of "a row of three elements, with a left, a right, and center," which can be solved easily with flexbox — but it has a couple of caveats that make it (I think) an impossible layout without JavaScript.


The desired goal

Consider a row-like container element and three children, "left", "right", and "center". The children may be of varying widths, but they are all the same height.

"Center" should try to stay centered relative to its container — but the three sibling elements must not overlap, and may push outside the container if necessary.

The markup, then, might look something like this:

<div class="container">
    <div class="left">I'm the left content.</div>
    <div class="center">I'm the center content. I'm longer than the others.</div>
    <div class="right">Right.</div>
</div>

The CSS is where the challenge is.


Examples of what should happen

For wide containers, "center" is centered relative to the container (i.e., its siblings' widths do not matter), as in the image below; notice that midpoint of the "center" element matches the midpoint of the container, and that the left and right "leftover" spaces are not equal:

Wide

For narrower containers, "center" abuts the widest sibling, but it does not overlap. The remaining space is distributed only between the narrow sibling and the "center" sibling. Notice also that the container's midpoint, indicated by the caret, is no longer the same as "center's" midpoint:

Narrower

Finally, as the container continues to shrink, there's no other option but to have all three elements lined up in a row, overflowing the parent:

Narrowest


My attempts to solve this

Surprisingly, I haven't found a good way to implement this in pure CSS.

You'd think flexbox would be the winner, but you can't really get flexbox to do it right: The space-between property distributes the space uniformly between the elements, so the center element doesn't actually end up centered. The flex-grow/shrink/basis properties aren't especially useful for this either, since they're responsible for controlling the size of the child elements, not for controlling the size of the space between them.

Using position:absolute can solve it as long as the container is wide enough, but when the container shrinks, you end up with overlap.

(And float layouts can't get within a mile of getting this right.)

I could combine the best two solutions above, and switch between them with a @media query — if all of the widths were known in advance. But they aren't, and the sizes may vary widely.

In short, there's no pure-HTML-and-CSS solution to this problem that I know of.


Conclusion, and a JSFiddle to experiment with

I created a JSFiddle that shows both the desired goal and a few non-solutions. Feel free to fork it and experiment. You can simulate the container resizing by grabbing the bar to the left of the content and dragging it. You are allowed to rearrange/restructure the HTML and CSS, if rewriting it gets you closer to a working answer.

https://jsfiddle.net/seanofw/35qmdnd6

So does anyone have a solution to this that doesn't involve using JavaScript to intelligently distribute the space between the elements?

1条回答
淡お忘
2楼-- · 2019-02-25 17:43

With flexbox you should be able to solve that, by giving the left/right elements flex: 1 and the right text-align: right.

The main trick is flex: 1, which will make them share available space equally.

For more versions, see this brilliant question/answer, flexbox-justify-items-and-justify-self-properties

Fiddle snippet

Stack snippet

body {
  font: 14px Arial;
}
.container {
  display: flex;
  border: 1px solid #00F;
}
.container > div > span {
  display: inline-block;
  background: #36F;
  white-space: nowrap;
  padding: 2px 4px;
  color: #FFF;
}
.container > .center > span {
  background: #696;
}

.container .left,
.container .right {
  flex: 1;
}
.container .right {
  text-align: right;
}

.center-mark {
  text-align: center;
  font-size: 80%;
}
.note {
  text-align: center;
  color: #666;
  font-style: italic;
  font-size: 90%;
}
<div class="container">
  <div class="left">
    <span>
      I'm the left content.
    </span>
  </div>
  <div class="center">
  <span>I'm the center content. I'm longer than the others.</span>
  </div>
  <div class="right">
    <span>
      Right.
    </span>
  </div>
</div>
<div class="center-mark">^</div>
<div class="note">(centered marker/text)</div>

查看更多
登录 后发表回答