使用CSS Grid折叠边框

68
我很喜欢学习新的CSS Grid规范,但我在边框方面遇到了一些问题。请问是否有可能在CSS Grid中折叠边框,或者有没有办法样式化gutter?正如您在下面的片段中所看到的,10px边框在块之间堆叠(总共为20px)。我知道这个问题不是CSS Grid独有的,但我希望它能够为创建所有框之间的统一10px边框和外部边缘提供新的解决方案。我的实际用例是一个日历,我正在练习使用Grids和React组件。您可以在此处查看我遇到的问题:CSS Grid Calendar因为每个月都不同,我需要考虑很多不同的边缘情况。

.container {
  display: grid;
  grid-template-columns: 120px 120px;
  box-sizing: border-box;
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
  border: 10px solid palegreen;
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
</div>


1
仅供参考,这是某人构建的CSS Grid日历:https://dev59.com/W1gQ5IYBdhLWcg3wGgFY - Michael Benjamin
此外,解决这个问题的常见方法是用上个月和下个月的淡色单元格填充空单元格。 - Michael Benjamin
1
感谢 @Michael_B!我的计划是制作一个超级可定制的日历前端,这样我就可以打印出看起来完全符合我的喜好的月历。不过现在,主要还是为了学习而做。期待着审查那个问题及其答案;看起来它会很有帮助。 - Tim Foley
2
这些现代绘制网格的方法仍然存在如此根本的缺陷,这是非常显著的。与 HTML 初期的表格布局相比,人们可以使用 border-collapse: collapse - Ciantic
5个回答

55

你可以使用 grid-gap 和 box-shadow 属性:

.container {
  display: grid;
  grid-template-columns: 100px 100px;
  box-sizing: border-box;
  grid-gap:10px;
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
 box-shadow:0 0 0 10px palegreen;
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
</div>

或者结合行和列的模板设置:

.container {
  display: grid;
  grid-template-columns: 110px 110px;
  grid-template-rows:110px;
  box-sizing: border-box;
  
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
 border:solid 10px palegreen;
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
</div>

请注意,当盒子设置为100px时,120px的列和行会显示两侧边框...

如果对于列使用了fr值,则不要在盒子上设置宽度(行也应遵循相同的限制)。

.container {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-template-rows: 110px;
  /*whatever else */
  box-sizing: border-box;
}

.block {
  margin: 0 -10px 0 0;/* fixed width value missing */
  height: 100px;
  background-color: lightgrey;
  border: solid 10px palegreen;
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
  <div class='block'>4</div>
  <div class='block'>5</div>
  <div class='block'>6</div>
  <div class='block'>7</div>
</div>


太棒了!我一定会尝试这两个。 - Tim Foley
如果我使用fr定义了我的网格模板,您认为这些方法仍然有效吗? - Tim Foley
1
@TimFoley 如果你这样做,请不要在子元素上设置宽度,只需在模板中设置列数。编辑添加了一个片段示例。 - G-Cyrillus
1
@TimFoley 这是一个并排放置的两个网格示例,行和列都设置为1fr: https://codepen.io/gc-nomade/pen/QvpBaO - G-Cyrillus
1
刚刚测试了一下,我认为你已经解决了它!非常感谢。 - Tim Foley

33

我刚刚发现了一种简单的方法,使用CSS的outline而不是border

outline属性在元素外绘制一条线,因此有1像素的间隙可以使两条线重合。

.container {
  display: grid;
  grid-template-columns: 100px 100px 100px;
  gap: 1px; /* you can use gap instead of grid-gap */
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
  outline: 1px solid darkgreen; /* Use outline instead of border */
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
  <div class='block'>4</div>
  <div class='block'>5</div>
  <div class='block'>6</div>
</div>

正如TylerH所评论的那样,轮廓并不占用空间,可以重叠,这就是为什么你需要使用间隙,如果你想要一条5像素的线,你应该在两个属性上都写上5像素,即轮廓和间隙。

.container {
  display: grid;
  grid-template-columns: 100px 100px 100px;
  gap: 5px;
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
  outline: 5px solid darkgreen; /* The same width as the gap */
}

2
虽然轮廓线在元素外绘制了一条线,但重要的是要注意它不占用空间,因此不会导致布局变化。这也允许一个奇怪的“特性”,即您可以将两个元素并排放置,并且它们的轮廓线似乎会“重叠”。 - TylerH
2
我发现这个答案是最简单和最通用的解决方案。谢谢您。 - vanowm
1
我以为这对我是解决方案,直到我发现没有 outline-topoutline-left 等等 :( - Kip
1
不需要为.block设置大小。.container也不需要设置大小。这个也可以:grid-template-columns: repeat(3, 1fr) - milahu
1
当使用轮廓透明度时,您会特别注意到轮廓堆叠,使外部和内部轮廓看起来不同。 - DannyMeister

12

考虑在网格容器级别控制所有尺寸和间距,而不是在网格项目级别进行控制。移除应用于项目的边框和大小调整。

.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); /* 1 */ /* 2 */
  grid-auto-rows: 100px; /* 3 */
  grid-gap: 5px; /* 4 */
  padding: 5px;
  background-color: tomato;
}

.block {
  background-color: lightgrey;
}

/* for demo only */
.block:nth-child(-n + 2) {
  visibility: hidden;
}
<div class='container'>
  <div class='block'>0</div>
  <div class='block'>0</div>
  <div class='block'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
  <div class='block'>4</div>
  <div class='block'>5</div>
  <div class='block'>6</div>
  <div class='block'>7</div>
  <div class='block'>8</div>
  <div class='block'>9</div>
  <div class='block'>10</div>
  <div class='block'>11</div>
  <div class='block'>12</div>
  <div class='block'>13</div>
  <div class='block'>14</div>
  <div class='block'>15</div>
  <div class='block'>16</div>
  <div class='block'>17</div>
  <div class='block'>18</div>
  <div class='block'>19</div>
  <div class='block'>20</div>
  <div class='block'>21</div>
  <div class='block'>22</div>
  <div class='block'>23</div>
  <div class='block'>24</div>
  <div class='block'>25</div>
  <div class='block'>26</div>
  <div class='block'>27</div>
  <div class='block'>28</div>
  <div class='block'>29</div>
  <div class='block'>30</div>
  <div class='block'>31</div>  
</div>

jsFiddle演示

注:

  1. auto-fit:填充尽可能多的列以适应行。溢出列将换行。
  2. minmax():每列的最小宽度为120px,最大宽度为可用空闲空间。 fr单位可与flex布局的flex-grow属性相比较。
  3. grid-auto-rows:自动创建的行(隐式行)高度为100px。
  4. grid-gap:四周有5px的间距。缩写形式为grid-column-gapgrid-row-gap

不错!我希望能更好地控制未填充空间的情况。我一定会记住这个方法,用于其他应用程序。 - Tim Foley
1
我指的是左上角和右下角显示的绿色背景颜色。 - Tim Foley
尽管这不能让您控制 border-style(这很遗憾),但这可能是网格布局的最佳解决方案。 - Igor

0

我正在寻找一种纯CSS的方法来折叠网格的边框,但是由于我找不到,所以我做了一个小的原型。

CSS Grid Collapsed Borders Rounded Corners

HTML

<div class="container">
  <div id="grid" class="grid">
    <div class="element">1</div>
    <div class="element">2</div>
    <div class="element">3</div>
    <div class="element">4</div>
    <div class="element">5</div>
    <div class="element">6</div>
    <div class="element">7</div>
    <div class="element">8</div>
    <div class="element">9</div>
    <div class="element">10</div>
    <div class="element">11</div>
  </div>
</div>

CSS

.container {
  max-width: 720px;
  margin: 0 auto;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));  
}

.element {
  text-align: center;
  padding: 20px;
  background: #f4f4f4;
  border-bottom: 1px solid black;
  border-right: 1px solid black;
}

.border-top {
  border-top: 1px solid black;
}

.border-left {
  border-left: 1px solid black;
}

.border-top-left-rounded {
  border-top-left-radius: 8px;
}

.border-top-right-rounded {
  border-top-right-radius: 8px;
}

.border-bottom-left-rounded {
  border-bottom-left-radius: 8px;
}

.border-bottom-right-rounded {
  border-bottom-right-radius: 8px;
}

JS

function dynamicRoundedCorners() {
  // get
  const grid = document.getElementById("grid");
  const elements = grid.children;
  const gridStyle = getComputedStyle(grid);

  // reset
  for (element of elements) {
    element.classList = "";
    element.classList.add("element");
  }

  // analyze
  const elementsPerRowCount = gridStyle.gridTemplateColumns
    .split(" ")
    .filter((element) => Number(element.replace("px", ""))).length;
  const rowCount = Math.ceil(elements.length / elementsPerRowCount);
  const rowsFirstAndLastElements = [];
  let firstAndLastElementIndex = 0;

  for (let i = 1; i <= rowCount; i++) {
    const rowFirstAndLastElements = [firstAndLastElementIndex];

    if (i === rowCount && rowCount > 1) {
      rowFirstAndLastElements.push(
        firstAndLastElementIndex + (elements.length % elementsPerRowCount) - 1
      );
    } else {
      rowFirstAndLastElements.push(
        firstAndLastElementIndex + elementsPerRowCount - 1
      );
    }
    rowsFirstAndLastElements.push(rowFirstAndLastElements);
    firstAndLastElementIndex += elementsPerRowCount;
  }

  // apply
  // -> add border-top on the first row
  for (let i = 0; i <= rowsFirstAndLastElements[0][1]; i++) {
    elements[i].classList.add("border-top");
  }

  // -> add border-left on every first element of a row
  for (let i = 0; i < rowCount; i++) {
    elements[rowsFirstAndLastElements[i][0]].classList.add("border-left");
  }

  // -> add top-left rounded corner on first element of first row
  elements[0].classList.add("border-top-left-rounded");
  // -> add top-right rounder corner on last element of first row
  elements[rowsFirstAndLastElements[0][1]].classList.add(
    "border-top-right-rounded"
  );
  // -> add bottom-left rounded corner on first element of last row
  elements[rowsFirstAndLastElements[rowCount - 1][0]].classList.add(
    "border-bottom-left-rounded"
  );
  // -> add bottom-right rounder corner on last element of last row
  elements[elements.length - 1].classList.add("border-bottom-right-rounded");
  // -> if elements.length % elementsPerRowCount != 0, add bottom-right rounder corner on last element of second to last row
  if (elements.length % elementsPerRowCount !== 0) {
    elements[
      rowsFirstAndLastElements[rowsFirstAndLastElements.length - 2][1]
    ].classList.add("border-bottom-right-rounded");
  }
}

// call
dynamicRoundedCorners();
window.addEventListener("resize", dynamicRoundedCorners);

这是链接: https://codepen.io/RilDev/pen/gOmjNrQ


0

如果您可以接受间隔边框颜色与不在当前月份的日期单元格相同,那么您可以采取另一种方法,即将一个 div 包装在整个网格容器周围,并将其 background-color 设置为所需的边框颜色,并给它 1px 的 padding 和 1px 的 grid-gap。使用这种方法,您可以实现统一边框的网格,而无需使用 box-shadow 这样的复杂技巧,这对我来说感觉像是一个 hack。


1
这不就是@Michael_B的答案吗?除了他使用5像素而不是1像素的边框。 - Håken Lid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接