使用负边距来控制 flex-box 中的空间

Posted

技术标签:

【中文标题】使用负边距来控制 flex-box 中的空间【英文标题】:Using negative margins to control space in flex-box 【发布时间】:2016-12-27 01:17:46 【问题描述】:

我一直在阅读 flex-boxes 以解决我一直遇到的问题。我读过的关于/用作问题研究的主要文章是this。

我最近在this 和this 问题中提出了这个主题,并且感谢@Michael_B 的帮助,解决了如何将元素(在本例中为<h2>)居中在一个flex -box,只要弹性盒中总是且只有三个弹性项目(在此布局中:<div><h2><div>)。

我想扩展这些点,因为我意识到如果弹性项目总数超过或(更实际地)少于 3 个,可能会有一个解决方案。

它涉及使用边距。如果你在一个弹性项目上使用margin: auto;,它的作用是使项目在主轴上居中(参见下图了解弹性盒的工作原理)。

img 
 width: 100%;
 height: 100%;
<img src="http://i.stack.imgur.com/9Oxw7.png" alt="flex-box demo" />

(这是我能想出如何获取下拉照片的最佳方法...)

据我了解,使用margin: auto 是当前对应于交叉轴的align-selfproperty。

因此,可以通过以下方式在弹性框中将弹性项目居中:

$(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>

从上面的 sn-p 中可以看出,它只有在 &lt;h2&gt; 的每一侧都有相同数量的 flex-items 时才有效(您还可以看到 justify-content: space-around 不适用于 just一个对象)。

但是,如果您要居中的对象的每一侧都有数量不平衡的 flex 项目怎么办? - 当然,您可以将空白元素放在那里并使用 flex: 1;,但那看起来有点hacky。

我的想法是利用负边距。我读了一篇很棒的文章 (here) 关于使用负边距以及它们如何将对象拉向它们等等(另外,甚至 w3 说它们是合法代码 - 不是 hacky)。

我们的想法是将&lt;h2&gt; 设置为margin: auto;,然后以某种方式(我强调不知何故,就像我一直在做的所有研究一样,我开始失去希望......)任何同级 flex-item 的宽度并从 &lt;h2&gt; margin: auto;... 中否定它听起来很老套,希望有一种方法可以在没有 JS/jQuery 或 CSS 编译器的情况下做到这一点。

有没有人知道一种方法可以从弹性项目的边缘否定同一个弹性盒子中同级弹性项目的宽度?

谢谢。

附注/更新

可能有一种方法可以使用position 属性来完成此操作;然而,我所做的所有测试都没有产生任何结果......也许其他人已经用这种方法成功了?

【问题讨论】:

如果 Michael_B 不知道...那么没人知道... @Paulie_D,或者我正在工作,现在无法进入 :-) 不管怎样,我经常看到你(和@Oriol)处理比这更复杂的问题。 可能,但整个问题是基于做一些 flexbox 不打算做的事情。这不是解决任何和所有布局问题的灵丹妙药。 javascript 存在是有原因的...进行 CSS 并非旨在执行的那种计算。 你到底想做什么? 【参考方案1】:

解决方案一:否定margin-top

不涉及定位(但 html 结构已更改)。 原生 flex h2 被隐藏并设置为 flex: 1; 为居中的 h2 留出空间。 居中的h2 将用负的margin-top 替换本机flex h2

* 
  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>

解决方案 2: absolute position

居中 h2 设置为 absolute 并置于 flex h2 上方。 HTML 结构已更改。

* 
  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>

解决方案 3: absolute + translate

HTML 结构未更改。 总体上最短的代码,最少,但值得注意。

* 
  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>

【讨论】:

【参考方案2】:

我不太明白,你为什么不简单地使用justify-content: center;如下:

div
  align-items: center;
  display: flex;  
  justify-content: center;
<div>
  <h2>I'm an h2</h2>
</div>

【讨论】:

【参考方案3】:

一种方法是将中心元素从正常的 flex 流中取出。 可以通过将其绝对定位在容器内。 这样做的缺点是 flex 容器不再考虑绝对定位元素的高度。

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>

【讨论】:

【参考方案4】:

我已经设置了一个hacky方式来处理这个

    让弹性容器包裹起来 添加一个伪元素以在设置的地方强制换行 添加排序属性,以便在第一行中只有中心元素,然后是强制换行的伪元素 使第二行的 span 到行尾的行尾 执行一些转换技巧,使真正的 2 行显示为单行。 当然还有使用自动边距居中的中心元素

sn-p 中的第一个容器像往常一样居中作为比较。 第二个容器左侧的元素多于右侧的元素,但工作正常:

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>

【讨论】:

【参考方案5】:

所以,偶然发现了这个问题。我有点不清楚你到底想做什么,但我会尽我所能。

给定容器内任意数量的元素

    指定一个元素始终位于数组的中心 保留使用 flex 布局 不使用 JavaScript

Here's my solution at JSFiddle

这是如何工作的

    我们将h2 元素从容器流中分离出来,方法是给它一个absolute 位置。 我们将容器元素设置为relative 位置,以确保其子元素相对于自身定位。 我们通过将topleft 属性设置为50% 来告诉h2 元素我们希望它位于容器元素的中心。 我们通过在h2 上设置transform: translate(-50%, -50%) 属性来更正元素的偏移量(因为它的跟踪是基于左上角的序数)。 translate 值与受影响元素的大小相关,告诉它将其宽度的50% 滑动到左侧,并将其高度的50% 滑动到顶部。 我们添加了一个“虚拟”元素来模拟流中绝对定位的h2 顺序。我们为虚拟元素提供了一个尺寸以保留空间,但您也可以轻松添加一些文本或其他值以确保其布局。 我们将虚拟元素的可见性设置为hidden。在元素上使用 visibility: hidden 会隐藏它,但仍会在文档流中保留其空间。

在创建此布局的过程中,我破坏了您示例中的按钮,但是我确实清理了您的 JS 以使其更具可读性。如果这些按钮对您的理解很重要,我很乐意修复它们。

相关的 CSS 如下:

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;

【讨论】:

以上是关于使用负边距来控制 flex-box 中的空间的主要内容,如果未能解决你的问题,请参考以下文章

负边距

奇怪的 Firefox 负边距

负边距三栏布局(圣杯布局双飞翼布局)

负边距布局

margin 负边距 的知识点

Firefox 中的负边距值问题