最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Using negative margins to control space in flex-box - Stack Overflow

programmeradmin0浏览0评论

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-selfproperty 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?

Share Improve this question edited May 23, 2017 at 11:45 CommunityBot 11 silver badge asked Aug 19, 2016 at 19:34 4lackof4lackof 1,39016 silver badges35 bronze badges 10
  • 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
 |  Show 5 more comments

5 Answers 5

Reset to default 2

One 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

  1. Make the flex container wrap
  2. Add a pseudo element to force a line wrap where set
  3. add sort properties so that in the first line there will be only the center element, followed by the pseudo that forces the wrap
  4. Make the span on the second line go to the begging and end of the line
  5. Do some transform trick to make what are really 2 lines appear as a single line.
  6. 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 to flex: 1; to leave space for centered h2.
  • Centered h2 will replace native flex h2 with negative margin-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 set absolute and placed above flex h2.
  • 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

  1. Designate an element to always be at the center of the array
  2. Retain use of the flex layout
  3. Use no JavaScript

Here's my solution at JSFiddle

How this works

  1. We break the h2 element out of the flow of the container by giving it an absolute position.
  2. We set the container element to a relative position to ensure its children are positioned relative to itself.
  3. We tell the h2 element that we want it at the center of the container element by setting the top and left properties to 50%.
  4. 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 the h2. The translate values are relative to the size of the affected element, telling it to slide 50% of its width to the left, and 50% of its height to the top.
  5. 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.
  6. We set the visibility of the dummy element to hidden. Using visibility: 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;
}
发布评论

评论列表(0)

  1. 暂无评论