← HTML & CSS/CSS Grid: продвинутые техники#33 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: старт в frontendПрактика: DOM и событияТермин: DOMМаршрут: старт с нуля

CSS Grid: продвинутые техники

Базовый Grid — строки и столбцы. Продвинутый Grid — subgrid для выравнивания вложенных элементов, именованные области для журнальных раскладок, auto-fit и minmax для автоматически адаптивных сеток без media queries.

subgrid

Главная проблема вложенных сеток: дочерняя сетка не выравнивается по треки родителя. Subgrid решает это:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

/* Без subgrid — карточка создаёт свою независимую сетку */
.card {
  display: grid;
  grid-template-rows: auto 1fr auto;  /* Каждая карточка сама выравнивает строки */
}

/* С subgrid — карточка участвует в родительской сетке */
.card {
  grid-row: span 3;         /* Занимает 3 строки родителя */
  display: grid;
  grid-template-rows: subgrid;  /* Использует треки РОДИТЕЛЯ */
}

Теперь заголовки всех карточек находятся на одной линии, даже если текст разной длины.

grid-template-areas — именованные зоны

.layout {
  display: grid;
  grid-template-columns: 240px 1fr 300px;
  grid-template-rows: 64px 1fr 48px;
  grid-template-areas:
    "header  header  header"
    "sidebar main    aside"
    "footer  footer  footer";
  min-height: 100vh;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.aside   { grid-area: aside; }
.footer  { grid-area: footer; }

Точка . в template-areas обозначает пустую ячейку:

grid-template-areas:
  "logo   nav    nav"
  ".      hero   hero"
  "cards  cards  cards";

auto-fill vs auto-fit

/* auto-fill: заполняет треками, даже если элементов нет */
.gallery {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

/* auto-fit: схлопывает пустые треки */
.gallery {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

Разница видна при малом количестве элементов: auto-fill оставляет пустые колонки, auto-fit растягивает имеющиеся элементы.

minmax() для адаптивных сеток без media queries

/* Адаптивная сетка: минимум 250px, растягивается до 1fr */
/* При ширине 800px: 3 колонки. При 500px: 2 колонки. Без @media! */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 16px;
}

Именованные линии

.grid {
  grid-template-columns:
    [sidebar-start] 240px
    [sidebar-end content-start] 1fr
    [content-end aside-start] 300px
    [aside-end];
}

/* Размещение по именованным линиям */
.main {
  grid-column: content-start / content-end;
}

grid-auto-flow: dense

.masonry {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-flow: dense;  /* Заполняет «дыры» меньшими элементами */
}

.featured {
  grid-column: span 2;
  grid-row: span 2;
}

Без dense широкие элементы оставляют пустые ячейки. С dense браузер заполняет их следующими подходящими элементами.

Размещение элементов

.item {
  grid-column: 2 / 4;       /* От линии 2 до 4 */
  grid-column: 2 / span 2;  /* От линии 2, span на 2 колонки */
  grid-row: 1 / -1;         /* От первой строки до последней */
}

Примеры

Построение журнальной раскладки с именованными областями и вывод структуры сетки

// Журнальная раскладка через grid-template-areas
const style = document.createElement('style')
style.textContent = `
  * { box-sizing: border-box; margin: 0; }
  body { font-family: sans-serif; padding: 12px; }

  .magazine {
    display: grid;
    grid-template-columns: 2fr 1fr;
    grid-template-rows: auto auto auto;
    grid-template-areas:
      "hero    hero"
      "article sidebar"
      "cards   cards";
    gap: 12px;
    max-width: 700px;
  }

  .hero    { grid-area: hero;    background: #7b2ff7; color: white; }
  .article { grid-area: article; background: #dbeafe; }
  .sidebar { grid-area: sidebar; background: #fef3c7; }
  .cards   { grid-area: cards;   background: #d1fae5; }

  .grid-zone {
    padding: 12px;
    border-radius: 6px;
    font-weight: 600;
    font-size: 14px;
  }
`
document.head.appendChild(style)

const layout = document.createElement('div')
layout.className = 'magazine'
document.body.appendChild(layout)

const zones = [
  { area: 'hero',    label: 'HERO — занимает 2 колонки' },
  { area: 'article', label: 'Основная статья' },
  { area: 'sidebar', label: 'Sidebar' },
  { area: 'cards',   label: 'Карточки — занимает 2 колонки' },
]

zones.forEach(({ area, label }) => {
  const zone = document.createElement('div')
  zone.className = `grid-zone ${area}`
  zone.textContent = label
  layout.appendChild(zone)
})

// Анализируем расположение каждого элемента
console.log('=== Анализ Grid-раскладки ===')
const gridStyle = getComputedStyle(layout)
console.log('grid-template-columns:', gridStyle.gridTemplateColumns)
console.log('grid-template-rows:', gridStyle.gridTemplateRows)

// Читаем расположение каждого дочернего элемента
Array.from(layout.children).forEach(child => {
  const cs = getComputedStyle(child)
  console.log(`${child.className.split(' ')[1]}:`)
  console.log(`  grid-column: ${cs.gridColumnStart} / ${cs.gridColumnEnd}`)
  console.log(`  grid-row: ${cs.gridRowStart} / ${cs.gridRowEnd}`)
})

// auto-fit с minmax — адаптивная сетка без @media
const autoGrid = document.createElement('div')
autoGrid.style.cssText = `
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 8px;
  margin-top: 16px;
  max-width: 700px;
`
document.body.appendChild(autoGrid)

;['CSS', 'Grid', 'auto-fit', 'minmax', 'без @media'].forEach(text => {
  const item = document.createElement('div')
  item.textContent = text
  item.style.cssText = 'background: #ede9fe; padding: 10px; border-radius: 4px; text-align: center; font-size: 13px;'
  autoGrid.appendChild(item)
})

console.log('\nauto-fit grid columns:', getComputedStyle(autoGrid).gridTemplateColumns)

CSS Grid: продвинутые техники

Базовый Grid — строки и столбцы. Продвинутый Grid — subgrid для выравнивания вложенных элементов, именованные области для журнальных раскладок, auto-fit и minmax для автоматически адаптивных сеток без media queries.

subgrid

Главная проблема вложенных сеток: дочерняя сетка не выравнивается по треки родителя. Subgrid решает это:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

/* Без subgrid — карточка создаёт свою независимую сетку */
.card {
  display: grid;
  grid-template-rows: auto 1fr auto;  /* Каждая карточка сама выравнивает строки */
}

/* С subgrid — карточка участвует в родительской сетке */
.card {
  grid-row: span 3;         /* Занимает 3 строки родителя */
  display: grid;
  grid-template-rows: subgrid;  /* Использует треки РОДИТЕЛЯ */
}

Теперь заголовки всех карточек находятся на одной линии, даже если текст разной длины.

grid-template-areas — именованные зоны

.layout {
  display: grid;
  grid-template-columns: 240px 1fr 300px;
  grid-template-rows: 64px 1fr 48px;
  grid-template-areas:
    "header  header  header"
    "sidebar main    aside"
    "footer  footer  footer";
  min-height: 100vh;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.aside   { grid-area: aside; }
.footer  { grid-area: footer; }

Точка . в template-areas обозначает пустую ячейку:

grid-template-areas:
  "logo   nav    nav"
  ".      hero   hero"
  "cards  cards  cards";

auto-fill vs auto-fit

/* auto-fill: заполняет треками, даже если элементов нет */
.gallery {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

/* auto-fit: схлопывает пустые треки */
.gallery {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

Разница видна при малом количестве элементов: auto-fill оставляет пустые колонки, auto-fit растягивает имеющиеся элементы.

minmax() для адаптивных сеток без media queries

/* Адаптивная сетка: минимум 250px, растягивается до 1fr */
/* При ширине 800px: 3 колонки. При 500px: 2 колонки. Без @media! */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 16px;
}

Именованные линии

.grid {
  grid-template-columns:
    [sidebar-start] 240px
    [sidebar-end content-start] 1fr
    [content-end aside-start] 300px
    [aside-end];
}

/* Размещение по именованным линиям */
.main {
  grid-column: content-start / content-end;
}

grid-auto-flow: dense

.masonry {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-flow: dense;  /* Заполняет «дыры» меньшими элементами */
}

.featured {
  grid-column: span 2;
  grid-row: span 2;
}

Без dense широкие элементы оставляют пустые ячейки. С dense браузер заполняет их следующими подходящими элементами.

Размещение элементов

.item {
  grid-column: 2 / 4;       /* От линии 2 до 4 */
  grid-column: 2 / span 2;  /* От линии 2, span на 2 колонки */
  grid-row: 1 / -1;         /* От первой строки до последней */
}

Примеры

Построение журнальной раскладки с именованными областями и вывод структуры сетки

// Журнальная раскладка через grid-template-areas
const style = document.createElement('style')
style.textContent = `
  * { box-sizing: border-box; margin: 0; }
  body { font-family: sans-serif; padding: 12px; }

  .magazine {
    display: grid;
    grid-template-columns: 2fr 1fr;
    grid-template-rows: auto auto auto;
    grid-template-areas:
      "hero    hero"
      "article sidebar"
      "cards   cards";
    gap: 12px;
    max-width: 700px;
  }

  .hero    { grid-area: hero;    background: #7b2ff7; color: white; }
  .article { grid-area: article; background: #dbeafe; }
  .sidebar { grid-area: sidebar; background: #fef3c7; }
  .cards   { grid-area: cards;   background: #d1fae5; }

  .grid-zone {
    padding: 12px;
    border-radius: 6px;
    font-weight: 600;
    font-size: 14px;
  }
`
document.head.appendChild(style)

const layout = document.createElement('div')
layout.className = 'magazine'
document.body.appendChild(layout)

const zones = [
  { area: 'hero',    label: 'HERO — занимает 2 колонки' },
  { area: 'article', label: 'Основная статья' },
  { area: 'sidebar', label: 'Sidebar' },
  { area: 'cards',   label: 'Карточки — занимает 2 колонки' },
]

zones.forEach(({ area, label }) => {
  const zone = document.createElement('div')
  zone.className = `grid-zone ${area}`
  zone.textContent = label
  layout.appendChild(zone)
})

// Анализируем расположение каждого элемента
console.log('=== Анализ Grid-раскладки ===')
const gridStyle = getComputedStyle(layout)
console.log('grid-template-columns:', gridStyle.gridTemplateColumns)
console.log('grid-template-rows:', gridStyle.gridTemplateRows)

// Читаем расположение каждого дочернего элемента
Array.from(layout.children).forEach(child => {
  const cs = getComputedStyle(child)
  console.log(`${child.className.split(' ')[1]}:`)
  console.log(`  grid-column: ${cs.gridColumnStart} / ${cs.gridColumnEnd}`)
  console.log(`  grid-row: ${cs.gridRowStart} / ${cs.gridRowEnd}`)
})

// auto-fit с minmax — адаптивная сетка без @media
const autoGrid = document.createElement('div')
autoGrid.style.cssText = `
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 8px;
  margin-top: 16px;
  max-width: 700px;
`
document.body.appendChild(autoGrid)

;['CSS', 'Grid', 'auto-fit', 'minmax', 'без @media'].forEach(text => {
  const item = document.createElement('div')
  item.textContent = text
  item.style.cssText = 'background: #ede9fe; padding: 10px; border-radius: 4px; text-align: center; font-size: 13px;'
  autoGrid.appendChild(item)
})

console.log('\nauto-fit grid columns:', getComputedStyle(autoGrid).gridTemplateColumns)

Задание

Создай журнальный лейаут страницы с помощью `grid-template-areas`. Макет должен содержать: hero-секцию на всю ширину (2 колонки), основную статью и сайдбар рядом, раздел карточек на всю ширину. Используй именованные области (`grid-area`) для каждой части.

Подсказка

`grid-template-columns: 2fr 1fr` — две колонки. В `grid-template-areas` каждая строка в кавычках: `"hero hero"` — hero занимает обе колонки, `"article sidebar"` — рядом, `"cards cards"` — снова на всю ширину. Каждый элемент получает `grid-area: hero` (или другое имя).

Загружаем среду выполнения...
Загружаем AI-помощника...