I have been reading up on flex-boxes in order to solve a problem I have been having. The main article I have read up on /used as research for the problem is this.
I recently brought this topic up in this and this questions and thanks to the help of @Michael_B, it was worked out how to center an element (in this case an <h2>
) in a flex-box as long as there are always and only three flex-items in the flex-box (in this layout: <div><h2><div>
).
I wanted to expand upon these points as I realized there might be a solution which could solve if there are more or (more realistically) less than 3 total flex-items.
It involves using margins. If you use margin: auto;
on a flex item, it acts to center an item on the main axis (see following picture for how a flex-box works).
img {
width: 100%;
height: 100%;
}
<img src=".png" alt="flex-box demo" />
I have been reading up on flex-boxes in order to solve a problem I have been having. The main article I have read up on /used as research for the problem is this.
I recently brought this topic up in this and this questions and thanks to the help of @Michael_B, it was worked out how to center an element (in this case an <h2>
) in a flex-box as long as there are always and only three flex-items in the flex-box (in this layout: <div><h2><div>
).
I wanted to expand upon these points as I realized there might be a solution which could solve if there are more or (more realistically) less than 3 total flex-items.
It involves using margins. If you use margin: auto;
on a flex item, it acts to center an item on the main axis (see following picture for how a flex-box works).
img {
width: 100%;
height: 100%;
}
<img src="https://i.sstatic.net/9Oxw7.png" alt="flex-box demo" />
(That was the best way I could figure out how to get a dropdown-photo...)
From what I am understanding, using margin: auto
is the current counterpart to the align-self
property for the cross axis.
Thus one could do this to center a flex item in a flex box in these ways:
$(document).on('click', 'button', function(e) {
$this = $(this);
if ($this.attr('id') === 'button1') {
$('h2').parent().attr("class", "");
$('h2').attr("class", "toggle1");
}
if ($this.attr('id') === 'button2') {
$('h2').parent().attr("class", "");
$('h2').attr("class", "toggle1");
}
if ($this.attr('id') === 'button3') {
$('h2').parent().attr("class", "toggle3");
$('h2').attr("class", "");
}
if ($this.attr('id') === 'button4') {
$('h2').parent().attr("class", "toggle4");
$('h2').attr("class", "");
}
if ($this.attr('id') === 'button0') {
$('h2').parent().attr("class", "");
$('h2').attr("class", "");
}
});
div:not(.cont),
div:not(.cont) * {
border: 1px dotted red;
}
div {
align-items: center;
display: flex;
}
button {
margin: 16px;
}
.toggle1 {
margin: auto;
}
.toggle2 {
flex: 1;
}
.toggle3 {
justify-content: center;
}
.toggle4 {
justify-content: space-between;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<div>
<h2>I'm an h2</h2>
</div>
<div>
<span>I'm a span!</span>
<h2>I'm an h2</h2>
<span>I'm a span!</span>
</div>
<div>
<span>I'm a span!</span>
<span>I'm a span!</span>
<h2>I'm an h2</h2>
<span>I'm a span!</span>
<span>I'm a span!</span>
</div>
<div class="cont">
<button id="button0">clear</button>
<button id="button1">margins</button>
<button id="button2">flex-grow</button>
</div>
<div class="cont">
<button id="button3">justify-content: center
<br />(on parent)</button>
<button id="button4">justify-content: space-around
<br />(on parent)</button>
</div>
As you can see from the above snippet, it only works if there are the same amount of flex-items on each side of the <h2>
(and you can also see that justify-content: space-around
does not work with just one object).
But what if there are an unbalanced amount of flex items on each side of the object you are trying to center? - Sure, you could put blank elements there and use
flex: 1;
, but that seems a bit hacky.
My idea is to make use of negative-margins. I read a great article (here) about using negative-margins and how they can pull objects towards them, etc. (plus, even w3 says they are legitimate code - not hacky).
The idea is to set <h2>
to margin: auto;
and then somehow (I emphasis somehow as with all the research I have been doing I am starting to lose hope...) take the width of any sibling flex-item and negate that from the <h2> margin: auto;
... sounds hacky and hopefully there is a way to do it without JS/jQuery or a CSS compiler.
Does anyone know of a way to negate from the margin of a flex item the width of a sibling flex item in the same flex-box?
Thank you.
P.S./UPDATE
There might be a way to accomplish this with the position
property; however, all the testing I have done has produced no results... Maybe someone else has been successful with this method?
- 4 If Michael_B doesn't know...then no-one does... – Paulie_D Commented Aug 19, 2016 at 20:17
- 1 @Paulie_D, or maybe I'm at work and can't get into it right now :-) – Michael Benjamin Commented Aug 19, 2016 at 20:38
- Regardless, I've often seen you (and @Oriol) tackle questions more complex than this. – Michael Benjamin Commented Aug 19, 2016 at 20:39
- 4 Possibly, but the whole question is based on doing something flexbox wasn't designed to do. It's not a magic bullet for any and all layout issues. There's a reason Javascript exists...to do the sort of calculations that CSS wasn't designed to do. – Paulie_D Commented Aug 19, 2016 at 20:44
- 3 what exactly do you want to do? – Chet Commented Nov 3, 2016 at 3:24
5 Answers
Reset to default 2One way could be to take the central element out of the normal flex flow. It is possible by absolute positioning it inside the container. The downside of this is that the flex container do not take into account the height of the absolute positioned element anymore.
codepen
.container {
position: relative;
display: flex;
width: 500px;
justify-content: flex-start;
align-items: center;
outline: 1px solid blue;
}
.item {
flex: 0 0 auto;
outline: 1px solid red;
margin: 0;
}
.item--center {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.item--right {
margin-left: auto;
}
<div class="container">
<span class="item">I'm a span!</span>
<span class="item">I'm a span!</span>
<h2 class="item item--center">I'm an h2</h2>
<span class="item item--right">I'm a span!</span>
</div>
I have set a hacky way to handle this
- Make the flex container wrap
- Add a pseudo element to force a line wrap where set
- add sort properties so that in the first line there will be only the center element, followed by the pseudo that forces the wrap
- Make the span on the second line go to the begging and end of the line
- Do some transform trick to make what are really 2 lines appear as a single line.
- And of course center the center element with auto margins
The first container in the snippet is centering as usual as a comparison. The second container as more elements on the left than on the right, but is working ok:
div {
display: flex;
background-color: lemonchiffon;
margin-top: 10px;
}
h2 {
margin: auto;
}
span {
border: solid 1px red;
padding: 3px;
}
.container {
flex-wrap: wrap;
transform: scaleY(0.5);
}
.container:after {
content: "";
height: 0px;
width: 100%;
flex-shrink: 1;
background-color: green;
order: 10;
}
.container span {
transform-origin: center top;
transform: translateY(-100%) scaleY(2);
position: relative;
order: 20;
}
.center {
transform-origin: center top;
transform: scaleY(2) ;
}
.center ~ span {
order: 30;
}
.center + span {
margin-left: auto;
}
<div>
<span>I'm a span!</span>
<span>I'm a span!</span>
<h2>I'm an h2</h2>
<span>I'm a span!</span>
<span>I'm a span!</span>
</div>
<div class="container">
<span>I'm a span!</span>
<span>I'm a span!</span>
<span>I'm a span!</span>
<h2 class="center">I'm an h2</h2>
<span>I'm a span!</span>
<span>I'm a span!</span>
</div>
I do not quite understand, why don't you simply use justify-content: center;
as below:
div{
align-items: center;
display: flex;
justify-content: center;
}
<div>
<h2>I'm an h2</h2>
</div>
Solution 1: negative margin-top
- Doesn't involve positioning (HTML structure altered though).
- Native flex
h2
is hidden and set toflex: 1;
to leave space for centeredh2
. - Centered
h2
will replace native flexh2
with negativemargin-top
.
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
div {
display: flex;
flex-flow: row wrap;
background: gainsboro;
}
div h2 {
flex: 1;
color: transparent;
}
h2.center {
color: black;
text-align: center;
background: transparent;
margin-top: -28px;
}
span {
border: 1px solid gray;
display: flex;
justify-content: flex-start;
align-items: center;
}
<div>
<span>I'm a span!</span>
<span>I'm a span!</span>
<h2>I'm an h2</h2>
<span>I'm a span!</span>
</div>
<h2 class="center">I'm an h2</h2>
Solution 2: absolute
position
- Centered
h2
is setabsolute
and placed above flexh2
. - HTML structure is altered.
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
div {
display: flex;
flex-flow: row wrap;
background: gainsboro;
}
div h2 {
flex: 1;
color: transparent;
}
h2.center {
color: black;
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: center;
line-height: 28px;
}
span {
border: 1px solid gray;
display: flex;
justify-content: flex-start;
align-items: center;
}
<div>
<span>I'm a span!</span>
<span>I'm a span!</span>
<h2>I'm an h2</h2>
<span>I'm a span!</span>
</div>
<h2 class="center">I'm an h2</h2>
Solution 3: absolute
+ translate
- HTML structure not altered.
- Shortest code overall, minimal, but worth to note it.
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
div {
display: flex;
justify-content: flex-start;
background: gainsboro;
position: relative;
width: 100%;
}
div h2 {
position: absolute;
left: 50%;
transform: translate(-50%, 0);
line-height: 20px;
}
span {
border: 1px solid gray;
}
div h2 + span {
margin-left: auto;
}
<div>
<span>I'm a span!</span>
<span>I'm a span!</span>
<h2>I'm an h2</h2>
<span>I'm a span!</span>
</div>
So, just stumbled on this question. I'm a little unclear about exactly what you're trying to do but I'll frame it to the best of my ability.
Given an arbitrary number of elements inside a container
- Designate an element to always be at the center of the array
- Retain use of the
flex
layout - Use no JavaScript
Here's my solution at JSFiddle
How this works
- We break the
h2
element out of the flow of the container by giving it anabsolute
position. - We set the container element to a
relative
position to ensure its children are positioned relative to itself. - We tell the
h2
element that we want it at the center of the container element by setting thetop
andleft
properties to50%
. - We correct the offset of the element (due to its tracking being based on the top-left ordinal) by setting a
transform: translate(-50%, -50%)
property on theh2
. Thetranslate
values are relative to the size of the affected element, telling it to slide50%
of its width to the left, and50%
of its height to the top. - We add a "dummy" element to mimic the absolutely positioned
h2
order in the flow. We give the dummy element a dimension to reserve space, but you could just as easily add some text or other value to ensure it gets layout. - We set the visibility of the dummy element to
hidden
. Usingvisibility: hidden
on the element hides it from display, but still reserves its space in document flow.
In the process of creating this layout I broke the buttons on your example, however I did clean up your JS a little to make it more readable. If the buttons are important for your understanding I will gladly fix them.
The pertinent CSS follows:
div {
position: relative;
}
h2 {
position:absolute;
margin: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
line-height: 40px;
}
h3.filler {
height: 40px;
visibility: hidden;
}