我该如何针对CSS网格布局中的特定列或行进行定位?

121

使用CSS是否可以选择特定的网格列或行?

例如,假设我有一个3行2列的CSS网格布局:grid-template-rows: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr;。如何选择第二列中的所有元素?例如:grid:nth-child(column:2)(这只是我的想法,不是有效的代码)。

我尝试了在

元素上使用nth-child选择器,但当项目由CSS网格布局引擎自动放置时,这并不允许我指定行或列。

body {
  display: grid;
  grid-template-rows: 1fr 1fr 1fr;
  grid-template-columns: 1fr 1fr;
  grid-gap: 10px;
}

.item {
  background: #999;
}
<div class="item">
  <p>Customer Name</p>
  <p>Element 1 | Element 2</p>
</div>

<div class="item">
  <p>Right Justify</p>
  <p>Element 1 | Element 2</p>
</div>

<div class="item">
  <p>Customer Name</p>
  <p>Element 1 | Element 2</p>
</div>

<div class="item">
  <p>Customer Name</p>
  <p>Element 1 | Element 2</p>
</div>
<div class="item">
  <p>Customer Name</p>
  <p>Element 1 | Element 2</p>
</div>
<div class="item">
  <p>Customer Name</p>
  <p>Element 1 | Element 2</p>
</div>
<div class="item">
  <p>Customer Name</p>
  <p>Element 1 | Element 2</p>
</div>


在JavaScript中使用CSS选择器:https://stackoverflow.com/q/56069793 - djvg
7个回答

95

要为任意行设置样式,您可以使用一个包装元素,并将其display设置为contents。请参见下面的代码片段:

.grid-container {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-gap: 2px;
}

.grid-item {
  border: 1px solid black;
  padding: 5px;
}

.grid-row-wrapper {
  display: contents;
}

.grid-row-wrapper > .grid-item {
  background: skyblue;
}
<div class="grid-container">
  <div class="grid-item">1</div>
  <div class="grid-item">2</div>
  <div class="grid-item">3</div>
  <div class="grid-item">4</div>
  <div class="grid-item">5</div>
  <div class="grid-row-wrapper">
    <div class="grid-item">6</div>
    <div class="grid-item">7</div>
    <div class="grid-item">8</div>
    <div class="grid-item">9</div>
    <div class="grid-item">10</div>
  </div>
  <div class="grid-item">11</div>
  <div class="grid-item">12</div>
  <div class="grid-item">13</div>
  <div class="grid-item">14</div>
  <div class="grid-item">15</div>
  <div class="grid-item">16</div>
  <div class="grid-item">17</div>
  <div class="grid-item">18</div>
  <div class="grid-item">19</div>
  <div class="grid-item">20</div>
</div>

与所有实现一样,您应该检查以确保它在目标环境中正常工作。您可以在MDNCan I use上检查兼容性表格,以获取对display: contents的支持:


4
这应该是正确的答案。浏览器的支持并不完美,但 display: contents; 旨在解决此问题而无需使用 hack。它非常适合用于 DOM 步进以为正确的行进行样式设置,同时又不可见并且被跳过网格渲染(它不会搞乱任何东西)。 - Jens Törnell
4
很好的解决方案,但截至2021年1月,浏览器支持仍然非常缺乏。 - Julian Suggate
6
不,这不是一个有效的答案。使用静态网格对行和列进行样式设计从来不是问题,因为您始终知道每个元素会出现在哪里。最初提出的问题是如何通过动态网格自动放置在特定行/列上的特定项目。 - Mesqalito
3
如果您使用媒体查询更改列数,那么这在响应式设计中将无法正常工作。例如,如果您在较小的屏幕上从5列更改为3列,则样式会溢出到另一行。 - Christian Pavilonis
1
虽然在处理行时效果很好,但在处理列时会出现问题。不过这个回答是一个不错的尝试,对其他解决方案肯定有用。 - Joshua Robison
显示剩余3条评论

52

没有可以定位的列或行元素,但如果网格是均匀的(每行有相同数量的单元格),则可以选择单元格。以下是一些示例。

1. 列

5列网格中的最后一列:

.item:nth-child(5n) { /* ... */ }

5列网格中的第四列(倒数第二列):
.item:nth-child(5n-1) { /* ... */ }

在一个5列网格中,第一列(倒数第5列):

.item:nth-child(5n-4) { /* ... */ }

2. 行

5列网格中的第一行(前五个单元格):

.item:nth-child(-n+5) { /* ... */ }

一个有5列的网格中的第二行(单元格从6到10):
.item:nth-child(n+6):nth-child(-n+10) { /* ... */ }

在一个5列网格中的第三行(单元格从11到15):

.item:nth-child(n+11):nth-child(-n+15) { /* ... */ }

在一个有5列、20个单元格的网格中,最后一行(从第16个单元格开始):

.item:nth-child(n+16) { /* ... */ }

2
静态工作正常,但动态工作不正常。 - Royer Adames
为了使其动态化,您可以在要循环的元素组中添加一个类。例如,在Angular中,您可以使用*ngFor循环奇数或偶数来为单元格行提供动态类,然后选择它们并对其进行样式设置。 - Royer Adames
1
如果您有一个单元格或跨越多列的单元格,我认为这种方法不起作用。 - JeffreyPia

43

使用CSS是不可能的。

CSS只能选择HTML元素、属性和属性值。

网格列和行没有这些“钩子”。

您必须直接选择网格项。

您写道:

例如,假设我有一个3行2列的CSS网格布局:grid-template-rows: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr;。如何选择第二列中的所有元素?

grid-container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  grid-gap: 10px;
  padding: 10px;
  height: 50vh;
  background-color: gray;
}

grid-item {
  background-color: lightgreen;
}

grid-item:nth-child(2n) {
  border: 2px dashed red;
}
<grid-container>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
</grid-container>


8

你不能这样做,因为没有这样的选择器。

但这很奇怪,因为你可以轻松地从CSS中定位行/列:

#item3 {
  background-color: blue;
  grid-row: span 2 / 7;
}

因此,自然而然地期望有类似以下的内容:

div[style*="display:grid"]:grid-row(3) {
    background-color: blue;
}

div[style*="display:grid"]:grid-column(3) {
    background-color: green;
}

我不知道为什么还没有提出这个草案的原因。

CSSWG有一份关于列选择的草案

W3C存储库中也有一个问题请求


这是正确的答案。 - chovy

6
在不久的将来,我们将能够通过Grid-Structural Selectors实现这一点。
在分层标记语言中,无法通过父子关系表示2D网格中单元格与其行和列的双重关联。其中一种关联只能在文档语言语义上显式或隐式地定义。在HTML和DocBook中,两种最常见的分层标记语言中,标记是以行为主导的(即,行关联以分层方式表示);列必须暗示。为了能够表示这种暗示的基于列的关系,定义了column combinator和:nth-col():nth-last-col() 伪类。在以列为主导的格式中,这些伪类匹配行关联。
你需要使用的是::nth-col(),它的行为方式与:nth-child()相同。 :nth-col(An+B)伪类符号表示一个单元格元素属于一个具有An + B-1个单元格的列之前。 ref

这非常有趣,你能指出一些实现演示吗? - Diode Dan
@DiodeDan 目前还没有,因为目前没有任何浏览器支持此功能,但很快我们将在某些浏览器中作为实验性功能推出,届时我们可以进行测试。 - Temani Afif
它到了吗? - Peter Mortensen
@PeterMortensen 还没有。 - Temani Afif

5
如果您想要为行添加样式,可以遵循同样的原则。以上面的示例为例:

grid-container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr 1fr;
  grid-gap: 10px;
  padding: 10px;
  height: 50vh;
  background-color: gray;
}

grid-item {
  background-color: lightgreen;
}

grid-item:nth-child(4n+3),grid-item:nth-child(4n) {
  border: 2px dashed red;
}
<grid-container>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
</grid-container>


2
这个“解决方案”与项目数量耦合,并且没有考虑任何换行符(也称为wrap),因此它并不是很有用。 - vsync
@vsync:这不是flexbox,没有wrap/line break。在网格中,您拥有固定的布局,并可以对这些项目应用固定的样式。 - Domenik Reitzner
但是你不能像在CSS display: table中那样设置跨越整个行的背景图像。 - James Cat

0

我会再添加一个答案,它基于动态更新css规则。不幸的是,我们必须采用这种方法,因为没有办法通过css变量设置nth-child

const data =[{"Name":"John","Age":25,"Gender":"Male"},{"Name":"Emily","Age":33,"Gender":"Female"},{"Name":"Michael","Age":42,"Gender":"Male"},{"Name":"Sarah","Age":29,"Gender":"Female"},{"Name":"William","Age":37,"Gender":"Male"},{"Name":"Samantha","Age":26,"Gender":"Female"},{"Name":"Benjamin","Age":48,"Gender":"Male"},{"Name":"Elizabeth","Age":31,"Gender":"Female"},{"Name":"David","Age":45,"Gender":"Male"},{"Name":"Olivia","Age":28,"Gender":"Female"}]


const colors = ['255,0,0', '0,255,0', '0,0,255']
const COLUMNS = 3
let rows = 2
const wp = document.querySelector('.wrapper')
let lastSelectedColumn = null

document.querySelector('button').onclick = function(){
  if (rows > data.length -1) return
  Object.values(data[rows++])
    .map(e => Object.assign(document.createElement('div'),{textContent: e}))
    .forEach(e => wp.appendChild(e)) 
  selectColumn(lastSelectedColumn)
}

wp.onclick = function({target}){
  if (target.className !== "") return
  const index = Array.from(wp.children).indexOf(target)
  selectColumn(lastSelectedColumn = index % COLUMNS)// console.log(lastSelectedColumn)
}

function getRules(column, columns, rows, color){
  const index = (column + 1) % columns 
  const i = index  ? `+ ${index}` : ''
  return [
    `.wrapper div:nth-child(${columns}n${i}){
      border-width: 0px 3px 0 3px; 
      border-color: rgb(${color}); 
      border-style: solid;
      background: rgba(${color},.1);
    }`, /* top cell */
    `.wrapper div:nth-child(${column + 1}) {
      border-width: 3px 3px 0 3px; 
      /* font-weight: bold; */
    }`, /* bottom cell */
    `.wrapper div:nth-child(${(rows-1) * columns  + column + 1}) {
      border-width: 0 3px 3px 3px; 
    }`
  ]
}

function cleanupRules(stylesheet){
  let newRuleIndex = stylesheet.sheet.cssRules.length
  for (; newRuleIndex > 2; newRuleIndex--)
     stylesheet.sheet.deleteRule(newRuleIndex - 1)
  return newRuleIndex 
}

function selectColumn(column){
  const stylesheet = document
    .querySelector('style[title="grid-wrapper"]')
  if (!stylesheet || column === null) return
  const rules = getRules(column, COLUMNS,
    wp.children.length / COLUMNS, colors[column])
  let newRuleIndex = cleanupRules(stylesheet)
  for (const rule of rules){
    stylesheet.sheet.insertRule(rule, newRuleIndex++)
  } // console.log(stylesheet.sheet.cssRules)
}
<style title="grid-wrapper">
  .wrapper {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    width: fit-content;
    background: #e5e5e5;
    color: black;
    margin: 10px;
  }
  .wrapper div {
    padding: 5px;
    box-sizing:content-box;
    box-shadow: 1px 1px 1px  black;
  }
</style>  
<p>Please, select a column, and click on `Add`</p>
<div class="wrapper">
  <div>Name</div>
  <div>Age</div>
  <div>Gender</div>
  <div>John</div>
  <div>25</div>
  <div>Male</div>
  <div>Emily</div>
  <div>33</div>
  <div>Female</div>
</div>
<button>Add</button>

更新

另一种方法是仅重写现有 CSS 规则中的选择器(而不添加或删除它们):

const wp = document.querySelector('.wrapper')
const config = {
  data :[{"Name":"John","Age":25,"Gender":"Male"},{"Name":"Emily","Age":33,"Gender":"Female"},{"Name":"Michael","Age":42,"Gender":"Male"},{"Name":"Sarah","Age":29,"Gender":"Female"},{"Name":"William","Age":37,"Gender":"Male"},{"Name":"Samantha","Age":26,"Gender":"Female"},{"Name":"Benjamin","Age":48,"Gender":"Male"},{"Name":"Elizabeth","Age":31,"Gender":"Female"},{"Name":"David","Age":45,"Gender":"Male"},{"Name":"Olivia","Age":28,"Gender":"Female"}],
  colors : ['255,0,0', '0,255,0', '0,0,255'],
  cols : 3,
  rows : 3,
  lastSelectedColumn: null
}

function getSelectorTexts(column, columns, rows, color){
  const index = (column + 1) % columns 
  const i = index  ? `+ ${index}` : ''
  return [
    `.wrapper div:nth-child(${columns}n${i})`, 
    `.wrapper div:nth-child(${column + 1})`, 
    `.wrapper div:nth-child(${(rows-1) * columns  + column + 1})`
  ]
}

function selectColumn(column){
  const stylesheet = document
    .querySelector('style[title="grid-wrapper"]')
  if (!stylesheet || column === null) return
  const rules = getSelectorTexts(column, config.cols,
     wp.children.length / config.cols, config.colors[column])
  let i = 2  
  for (const rule of rules){
    stylesheet.sheet.rules[i++].selectorText = rule
  }
}

document.querySelector('button').onclick = function(){
  if (config.rows > config.data.length -1) return
  Object.values(config.data[config.rows++])
    .map(e => Object.assign(document.createElement('div'),{textContent: e}))
    .forEach(e => wp.appendChild(e)) 
  selectColumn(config.lastSelectedColumn)
}

wp.onclick = function({target}){
  if (target.className !== "") return
  const index = Array.from(wp.children).indexOf(target)
  selectColumn(config.lastSelectedColumn = index % config.cols)
}
<style title="grid-wrapper">
  .wrapper {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    width: fit-content;
    background: #e5e5e5;
    color: black;
    margin: 10px;
  }
  .wrapper div {
    padding: 5px;
    border-width: 3px;
    border-color: transparent; 
    box-shadow: 1px 1px 1px  black;
  }

  .rule1 {
    border-width: 0px 3px 0 3px; 
    border-color: rgb(0, 0, 255);
    border-style: solid;
    background: rgba(0, 0, 255, .1);
  }

  .rule2 {
    border-width: 3px 3px 0 3px;
    border-color: rgb(0, 0, 255);
  }

  .rule2 {
    border-width: 0 3px 3px 3px; 
    border-color: rgb(0, 0, 255);
  }
</style>   
<p>Please, select a column, and click on `Add`</p>
<div class="wrapper">
  <div>Name</div>
  <div>Age</div>
  <div>Gender</div>
  <div>John</div>
  <div>25</div>
  <div>Male</div>
  <div>Emily</div>
  <div>33</div>
  <div>Female</div>
  <div>Michael</div>
  <div>42</div>
  <div>Male</div>
</div>
<button>Add</button>


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