В прошлом уроке мы научились строить простые строки. Теперь разберём мощные инструменты: многострочные лейауты, контроль порядка элементов и тонкую настройку распределения пространства. Это то, что позволяет строить реальные интерфейсы — грид карточек, адаптивные лейауты.
Работает только когда flex-wrap: wrap и строк больше одной. Управляет распределением строк по поперечной оси.
align-content: flex-start; /* Строки к началу */
align-content: flex-end; /* Строки к концу */
align-content: center; /* Строки по центру */
align-content: space-between; /* Первая строка вверху, последняя внизу */
align-content: space-around; /* Равные отступы вокруг строк */
align-content: stretch; /* Строки растягиваются (по умолчанию) */align-items — выравнивает элементы внутри одной строки.
align-content — выравнивает строки внутри контейнера.
Позволяет переставить элементы без изменения HTML. По умолчанию всё имеет order: 0.
.btn-primary { order: -1; } /* Первый, хотя в HTML идёт последним */
.sidebar { order: 2; } /* Последний, хотя в HTML идёт первым */Применение: на мобильных вынести важный контент наверх, не меняя HTML.
@media (max-width: 768px) {
.main-content { order: 1; }
.sidebar { order: 2; }
}Если в контейнере есть свободное место, flex-grow определяет, как его поделить.
/* Лейаут: сайдбар + контент */
.sidebar { flex-grow: 1; } /* 1 часть свободного места */
.main { flex-grow: 3; } /* 3 части свободного места */
/* Итог: sidebar = 25%, main = 75% */По умолчанию flex-grow: 0 — элемент не растёт.
Определяет, как элемент уменьшается, когда места не хватает.
.logo { flex-shrink: 0; } /* Не сжимается никогда */
.search { flex-shrink: 1; } /* Сжимается (по умолчанию) */По умолчанию flex-shrink: 1 — все элементы сжимаются равномерно.
Задаёт исходный размер перед применением grow/shrink.
.card { flex-basis: 200px; } /* Начинает с 200px, потом растёт/сжимается */
.card { flex-basis: 30%; } /* Начинает с 30% от контейнера */
.card { flex-basis: auto; } /* Берёт размер из width/height (по умолчанию) */
.card { flex-basis: 0; } /* Начинает с нуля, растёт только через flex-grow */flex: <grow> <shrink> <basis>
flex: 1; /* = flex: 1 1 0 — растёт, сжимается, начинает с 0 */
flex: auto; /* = flex: 1 1 auto */
flex: none; /* = flex: 0 0 auto — не растёт, не сжимается */
flex: 0 0 200px; /* Фиксированный размер 200px, не меняется */Переопределяет align-items для конкретного дочернего элемента.
.container { align-items: center; }
.special { align-self: flex-start; } /* Только этот элемент прижат к верху */Значения те же: flex-start, flex-end, center, stretch, baseline.
/* Родитель */
.cards {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
/* Карточка */
.card {
flex: 1 1 280px; /* grow=1, shrink=1, basis=280px */
/* На широком экране: несколько в ряд */
/* На мобильном: каждая занимает всю строку */
}Это минимальный адаптивный грид без media queries!
Ошибка 1: Перепутать align-items и align-content
/* Одна строка — align-items работает, align-content нет */
.single-row { display: flex; align-items: center; } /* OK */
.single-row { display: flex; align-content: center; } /* Нет эффекта */
/* Несколько строк (flex-wrap: wrap) — нужен align-content */
.multi-row { display: flex; flex-wrap: wrap; align-content: center; }Ошибка 2: flex: 1 и фиксированный размер одновременно
.item { flex: 1; width: 200px; } /* width игнорируется! Используй flex-basis */
.item { flex: 0 0 200px; } /* Правильный фиксированный размер */Паттерн flex: 1 1 280px с flex-wrap: wrap — основа адаптивных карточных лейаутов на всех крупных интернет-магазинах. Instagram-подобная лента из трёх карточек в ряд на десктопе и одной на мобиле строится именно так, часто без единого media query.
flex-grow, flex-shrink, align-self и order — продвинутый Flexbox
const style = document.createElement('style')
style.textContent = `
* { box-sizing: border-box; }
body { font-family: Arial, sans-serif; padding: 16px; background: #f7fafc; }
`
document.head.appendChild(style)
// === Лейаут sidebar + main через flex-grow ===
const layout = document.createElement('div')
layout.style.display = 'flex'
layout.style.gap = '16px'
layout.style.marginBottom = '24px'
layout.style.height = '120px'
document.body.appendChild(layout)
const sidebar = document.createElement('aside')
sidebar.style.flexGrow = '1'
sidebar.style.backgroundColor = '#bee3f8'
sidebar.style.padding = '16px'
sidebar.style.borderRadius = '8px'
sidebar.textContent = 'Sidebar (flex-grow: 1)'
const main = document.createElement('main')
main.style.flexGrow = '3'
main.style.backgroundColor = '#c6f6d5'
main.style.padding = '16px'
main.style.borderRadius = '8px'
main.textContent = 'Main (flex-grow: 3 = 75% пространства)'
layout.appendChild(sidebar)
layout.appendChild(main)
// === align-self: каждый элемент выравнивается по-своему ===
const row = document.createElement('div')
row.style.display = 'flex'
row.style.alignItems = 'flex-start' // Default для всех
row.style.gap = '8px'
row.style.height = '100px'
row.style.backgroundColor = '#fff'
row.style.padding = '8px'
row.style.borderRadius = '8px'
row.style.marginBottom = '16px'
document.body.appendChild(row)
const alignments = [
{ text: 'flex-start', alignSelf: 'flex-start', bg: '#fed7d7' },
{ text: 'center', alignSelf: 'center', bg: '#fbb6ce' },
{ text: 'flex-end', alignSelf: 'flex-end', bg: '#d6bcfa' },
{ text: 'stretch', alignSelf: 'stretch', bg: '#bee3f8' },
]
alignments.forEach(({ text, alignSelf, bg }) => {
const el = document.createElement('div')
el.style.flex = '1'
el.style.alignSelf = alignSelf
el.style.backgroundColor = bg
el.style.padding = '8px'
el.style.borderRadius = '4px'
el.style.fontSize = '12px'
el.textContent = alignSelf
row.appendChild(el)
})
// === order: изменяем порядок ===
const orderContainer = document.createElement('div')
orderContainer.style.display = 'flex'
orderContainer.style.gap = '8px'
document.body.appendChild(orderContainer)
;[
{ text: 'Третий в HTML', order: 3, bg: '#fed7d7' },
{ text: 'Первый в HTML', order: 1, bg: '#c6f6d5' },
{ text: 'Второй в HTML', order: 2, bg: '#bee3f8' },
].forEach(({ text, order, bg }) => {
const el = document.createElement('div')
el.style.order = String(order)
el.style.flex = '1'
el.style.padding = '12px'
el.style.backgroundColor = bg
el.style.borderRadius = '8px'
el.style.textAlign = 'center'
el.style.fontSize = '13px'
el.textContent = text + ' (order: ' + order + ')'
orderContainer.appendChild(el)
})
// Лог
const sidebarStyle = window.getComputedStyle(sidebar)
const mainStyle = window.getComputedStyle(main)
console.log('Sidebar flex-grow:', sidebarStyle.flexGrow) // 1
console.log('Main flex-grow:', mainStyle.flexGrow) // 3
// Ширины — видим что main занимает ~75%
console.log('Sidebar ширина:', sidebar.offsetWidth + 'px')
console.log('Main ширина:', main.offsetWidth + 'px')В прошлом уроке мы научились строить простые строки. Теперь разберём мощные инструменты: многострочные лейауты, контроль порядка элементов и тонкую настройку распределения пространства. Это то, что позволяет строить реальные интерфейсы — грид карточек, адаптивные лейауты.
Работает только когда flex-wrap: wrap и строк больше одной. Управляет распределением строк по поперечной оси.
align-content: flex-start; /* Строки к началу */
align-content: flex-end; /* Строки к концу */
align-content: center; /* Строки по центру */
align-content: space-between; /* Первая строка вверху, последняя внизу */
align-content: space-around; /* Равные отступы вокруг строк */
align-content: stretch; /* Строки растягиваются (по умолчанию) */align-items — выравнивает элементы внутри одной строки.
align-content — выравнивает строки внутри контейнера.
Позволяет переставить элементы без изменения HTML. По умолчанию всё имеет order: 0.
.btn-primary { order: -1; } /* Первый, хотя в HTML идёт последним */
.sidebar { order: 2; } /* Последний, хотя в HTML идёт первым */Применение: на мобильных вынести важный контент наверх, не меняя HTML.
@media (max-width: 768px) {
.main-content { order: 1; }
.sidebar { order: 2; }
}Если в контейнере есть свободное место, flex-grow определяет, как его поделить.
/* Лейаут: сайдбар + контент */
.sidebar { flex-grow: 1; } /* 1 часть свободного места */
.main { flex-grow: 3; } /* 3 части свободного места */
/* Итог: sidebar = 25%, main = 75% */По умолчанию flex-grow: 0 — элемент не растёт.
Определяет, как элемент уменьшается, когда места не хватает.
.logo { flex-shrink: 0; } /* Не сжимается никогда */
.search { flex-shrink: 1; } /* Сжимается (по умолчанию) */По умолчанию flex-shrink: 1 — все элементы сжимаются равномерно.
Задаёт исходный размер перед применением grow/shrink.
.card { flex-basis: 200px; } /* Начинает с 200px, потом растёт/сжимается */
.card { flex-basis: 30%; } /* Начинает с 30% от контейнера */
.card { flex-basis: auto; } /* Берёт размер из width/height (по умолчанию) */
.card { flex-basis: 0; } /* Начинает с нуля, растёт только через flex-grow */flex: <grow> <shrink> <basis>
flex: 1; /* = flex: 1 1 0 — растёт, сжимается, начинает с 0 */
flex: auto; /* = flex: 1 1 auto */
flex: none; /* = flex: 0 0 auto — не растёт, не сжимается */
flex: 0 0 200px; /* Фиксированный размер 200px, не меняется */Переопределяет align-items для конкретного дочернего элемента.
.container { align-items: center; }
.special { align-self: flex-start; } /* Только этот элемент прижат к верху */Значения те же: flex-start, flex-end, center, stretch, baseline.
/* Родитель */
.cards {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
/* Карточка */
.card {
flex: 1 1 280px; /* grow=1, shrink=1, basis=280px */
/* На широком экране: несколько в ряд */
/* На мобильном: каждая занимает всю строку */
}Это минимальный адаптивный грид без media queries!
Ошибка 1: Перепутать align-items и align-content
/* Одна строка — align-items работает, align-content нет */
.single-row { display: flex; align-items: center; } /* OK */
.single-row { display: flex; align-content: center; } /* Нет эффекта */
/* Несколько строк (flex-wrap: wrap) — нужен align-content */
.multi-row { display: flex; flex-wrap: wrap; align-content: center; }Ошибка 2: flex: 1 и фиксированный размер одновременно
.item { flex: 1; width: 200px; } /* width игнорируется! Используй flex-basis */
.item { flex: 0 0 200px; } /* Правильный фиксированный размер */Паттерн flex: 1 1 280px с flex-wrap: wrap — основа адаптивных карточных лейаутов на всех крупных интернет-магазинах. Instagram-подобная лента из трёх карточек в ряд на десктопе и одной на мобиле строится именно так, часто без единого media query.
flex-grow, flex-shrink, align-self и order — продвинутый Flexbox
const style = document.createElement('style')
style.textContent = `
* { box-sizing: border-box; }
body { font-family: Arial, sans-serif; padding: 16px; background: #f7fafc; }
`
document.head.appendChild(style)
// === Лейаут sidebar + main через flex-grow ===
const layout = document.createElement('div')
layout.style.display = 'flex'
layout.style.gap = '16px'
layout.style.marginBottom = '24px'
layout.style.height = '120px'
document.body.appendChild(layout)
const sidebar = document.createElement('aside')
sidebar.style.flexGrow = '1'
sidebar.style.backgroundColor = '#bee3f8'
sidebar.style.padding = '16px'
sidebar.style.borderRadius = '8px'
sidebar.textContent = 'Sidebar (flex-grow: 1)'
const main = document.createElement('main')
main.style.flexGrow = '3'
main.style.backgroundColor = '#c6f6d5'
main.style.padding = '16px'
main.style.borderRadius = '8px'
main.textContent = 'Main (flex-grow: 3 = 75% пространства)'
layout.appendChild(sidebar)
layout.appendChild(main)
// === align-self: каждый элемент выравнивается по-своему ===
const row = document.createElement('div')
row.style.display = 'flex'
row.style.alignItems = 'flex-start' // Default для всех
row.style.gap = '8px'
row.style.height = '100px'
row.style.backgroundColor = '#fff'
row.style.padding = '8px'
row.style.borderRadius = '8px'
row.style.marginBottom = '16px'
document.body.appendChild(row)
const alignments = [
{ text: 'flex-start', alignSelf: 'flex-start', bg: '#fed7d7' },
{ text: 'center', alignSelf: 'center', bg: '#fbb6ce' },
{ text: 'flex-end', alignSelf: 'flex-end', bg: '#d6bcfa' },
{ text: 'stretch', alignSelf: 'stretch', bg: '#bee3f8' },
]
alignments.forEach(({ text, alignSelf, bg }) => {
const el = document.createElement('div')
el.style.flex = '1'
el.style.alignSelf = alignSelf
el.style.backgroundColor = bg
el.style.padding = '8px'
el.style.borderRadius = '4px'
el.style.fontSize = '12px'
el.textContent = alignSelf
row.appendChild(el)
})
// === order: изменяем порядок ===
const orderContainer = document.createElement('div')
orderContainer.style.display = 'flex'
orderContainer.style.gap = '8px'
document.body.appendChild(orderContainer)
;[
{ text: 'Третий в HTML', order: 3, bg: '#fed7d7' },
{ text: 'Первый в HTML', order: 1, bg: '#c6f6d5' },
{ text: 'Второй в HTML', order: 2, bg: '#bee3f8' },
].forEach(({ text, order, bg }) => {
const el = document.createElement('div')
el.style.order = String(order)
el.style.flex = '1'
el.style.padding = '12px'
el.style.backgroundColor = bg
el.style.borderRadius = '8px'
el.style.textAlign = 'center'
el.style.fontSize = '13px'
el.textContent = text + ' (order: ' + order + ')'
orderContainer.appendChild(el)
})
// Лог
const sidebarStyle = window.getComputedStyle(sidebar)
const mainStyle = window.getComputedStyle(main)
console.log('Sidebar flex-grow:', sidebarStyle.flexGrow) // 1
console.log('Main flex-grow:', mainStyle.flexGrow) // 3
// Ширины — видим что main занимает ~75%
console.log('Sidebar ширина:', sidebar.offsetWidth + 'px')
console.log('Main ширина:', main.offsetWidth + 'px')Создай шапку сайта с тремя блоками во flex-контейнере высотой 70px. Первый блок — логотип с `flex-shrink: 0` и `flex-basis: 80px` (не сжимается). Второй блок — контент с `flex-grow: 1` (занимает всё свободное место). Третий блок — бейдж с `align-self: flex-end` (прижат к нижней части контейнера).
`flex-shrink: 0` — логотип не сжимается при нехватке места. `flex-basis: 80px` — начальная ширина. `flex-grow: 1` — контент занимает всё свободное пространство. `align-self: flex-end` — бейдж прижат к нижней части контейнера.