Flexbox: ред и колона
Накратко: Flexbox подрежда елементите в ред или колона по главната ос, подравнява ги по напречната и дели свободното място с flex-grow.
Проблем
Шапката на три реда
Новата шапка на магазина трябва да е един ред: логото вляво, менюто до него, кошницата вдясно. На компютър обаче тя е на три реда един под друг.
Конзола
В CSS няма грешка. <nav> е блоков елемент, а блоковите елементи застават един под друг и всеки взима цял ред. Връзките са редови елементи и стоят на един ред с текста около тях. Затова логото е на първия ред, менюто на втория, а кошницата на третия.
Идеята
Контейнер и елементи
Нужен е друг начин да се нареждат децата на шапката. Той е display: flex на родителя:
header { display: flex;}- Елементът с
display: flexе flex контейнер. Децата му (само преките деца) са flex елементи. - Елементите застават един до друг по главната ос. По подразбиране тя е отляво надясно.
flex-direction: columnя обръща отгоре надолу. - Перпендикулярно на нея е напречната ос.
Подреждането се казва на контейнера, а не на децата:
justify-contentподрежда по главната ос:flex-start(в началото),center,flex-end,space-between(първият в началото, последният в края, разстоянието е между тях).align-itemsподравнява по напречната ос:stretch(по подразбиране, всички са високи колкото реда),center,flex-start,flex-end.gapслага еднакво разстояние между елементите, без отстъп преди първия и след последния.
Един елемент може да е и flex елемент, и flex контейнер. nav { display: flex; gap: 12px; } нарежда връзките в менюто, а самото меню е елемент на шапката.
Пробвай
Логото вляво, кошницата вдясно
Направи шапката на един ред: логото в левия край, кошницата в десния, менюто между тях. Ползвай justify-content. Проверката мери на колко реда са децата на <header>.
Конзола
Решение
index.html<!doctype html>
<html lang="bg">
<head>
<meta charset="utf-8">
<title>Магазинът</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<a class="logo" href="/">Магазинът</a>
<nav>
<a href="/bread">Хляб</a>
<a href="/milk">Мляко</a>
<a href="/fruit">Плодове</a>
</nav>
<a class="cart" href="/cart">Кошница (2)</a>
</header>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
header {
padding: 12px 16px;
background: #f3efe6;
display: flex;
justify-content: space-between;
align-items: center;
}
a { color: #1f2430; }
.logo { font-weight: 700; }
nav a { margin-right: 12px; }
display: flex на header слага трите деца в ред. justify-content: space-between пуска първото в началото, последното в края, а свободното място е между тях. align-items: center ги центрира по височина. Тук трите са еднакво високи, но ако кошницата стане бутон с padding, разликата се вижда.
Пробвай
Намери грешката
Шапката е в ред, но кошницата не отива вдясно. Защо?
Избери реда с грешката.
justify-content подрежда децата на контейнера, затова се пише на контейнера. На ред 6 той е на .cart: тя няма flex деца и свойството не прави нищо. Трябва justify-content: space-between в header, или margin-left: auto на .cart, за което е следващата карта.
Идеята
Свободното място
В шапката има и поле за търсене. То трябва да взима всичкото място между логото и кошницата, колкото и широк да е екранът. Тук помагат три свойства на flex елемента:
flex-basisе основата: ширината на елемента, преди да се дели каквото и да е. По подразбиране еauto: ширината му (width) или колкото е съдържанието.flex-growказва колко дяла от свободното място взима елементът. По подразбиране е 0: не расте.flex-shrinkказва колко се свива, ако мястото не стига. По подразбиране е 1.
Браузърът смята така:
- Слага всеки елемент с основата му.
- Свободното място е ширината на контейнера минус основите и gap-овете.
- Дели свободното място по
flex-growи всеки елемент расте със своя дял.
Свойството flex пише трите наведнъж. flex: 1 е flex-grow: 1, flex-shrink: 1 и flex-basis: 0%: елементът расте и основата му е нула. Затова три елемента с flex: 1 стават еднакво широки, колкото и да е текстът в тях.
Виж го
Виж как се дели мястото
Лента от 600px с три елемента. Гледай сметката отгоре и зеленото свободно място.
.bar { display: flex; width: 600px; gap: 20px;}.logo { flex-basis: 120px; }.search { flex-basis: 100px; flex-grow: 2; }.cart { flex-basis: 100px; flex-grow: 1; }Стъпка по стъпка, с думи
- .bar е flex контейнер, широк 600px. Децата му са flex елементи: застават в ред по главната ос, отляво надясно.
- Първо всеки елемент получава своя flex-basis: 120, 100 и 100px. Това е ширината му, преди да се дели каквото и да е.
- Между три елемента има два gap по 20px. Заети са 120 + 100 + 100 + 2 × 20 = 360px.
- Това, което остава, е свободното място: 600 − 360 = 240px.
- flex-grow казва по колко дяла взима всеки: logo 0, search 2, cart 1, общо три. Един дял е 240 : 3 = 80px.
- Всеки расте със своите дялове: search с 160px, cart с 80px, а logo остава същото. Ширините са 120, 260 и 180px.
- 120 + 20 + 260 + 20 + 180 = 600: редът е пълен. Браузърът дава точно тези ширини. search не е два пъти по-широк от cart: 2 : 1 се дели само свободното място.
search е с flex-grow: 2, а cart с 1, но search не е два пъти по-широк: 260 срещу 180. Дели се само свободното място, а основите остават. Затова, когато искаш еднакви колони, основата трябва да е нула: flex: 1.
Пробвай
Търсачката взима остатъка
Логото е 120px, кошницата 110px, а шапката е до 640px. Направи полето за търсене да взима цялото място между тях. Проверката мери ширината му в пиксели: 640 − 2 × 16 (padding) − 120 − 110 − 2 × 16 (gap) = 346px.
Конзола
Решение
index.html<!doctype html>
<html lang="bg">
<head>
<meta charset="utf-8">
<title>Магазинът</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<a class="logo" href="/">Магазинът</a>
<input type="search" aria-label="Търси" placeholder="Хляб, мляко…">
<a class="cart" href="/cart">Кошница (2)</a>
</header>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
header {
max-width: 640px;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 16px;
background: #f3efe6;
}
a { color: #1f2430; }
.logo { width: 120px; font-weight: 700; }
.cart { width: 110px; text-align: right; }
input {
flex: 1;
padding: 6px 8px;
}
flex: 1 на полето: то е единственият елемент с flex-grow, затова взима цялото свободно място. Логото и кошницата остават с ширините си. width: 100% не става: полето иска цялата шапка, а мястото не стига и flex-shrink свива и логото, и кошницата.
Пробвай
Едно нещо вдясно
Понякога искаш логото и менюто вляво, а само кошницата вдясно. space-between не става: тогава менюто отива в средата. Има свойство на самия елемент: margin със стойност auto взима цялото свободно място от своята страна и бута елемента.
header { display: flex; gap: 16px; }
.cart { margin-left: ; }margin-left: auto: лявото поле на кошницата изяжда свободното място и тя застава в десния край. Логото и менюто остават едно до друго вляво. Във flex контейнер auto полетата се смятат преди justify-content.
Пробвай
Нов ред, когато не се събират
Flex контейнерът по подразбиране държи всички елементи на един ред (flex-wrap: nowrap). Ако не се събират, той ги свива, а когато вече не може, редът излиза извън екрана. С flex-wrap: wrap елементите, които не се събират, минават на нов ред.
На телефон (360px) четирите продукта са смачкани в един ред и страницата се плъзга встрани. Направи ги по два на ред, всеки поне 150px, а двата в реда да са еднакво широки. Проверката брои редовете и колоните и мери ширината на картите.
Конзола
Решение
index.html<!doctype html>
<html lang="bg">
<head>
<meta charset="utf-8">
<title>Предложения</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Предложения</h1>
<div class="products">
<article class="card">
<h2>Бял хляб</h2>
<p>1,20 €</p>
</article>
<article class="card">
<h2>Прясно мляко</h2>
<p>1,60 €</p>
</article>
<article class="card">
<h2>Сирене</h2>
<p>7,90 €</p>
</article>
<article class="card">
<h2>Ябълки</h2>
<p>2,10 €</p>
</article>
</div>
</main>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
main { padding: 12px; }
h1 { margin: 0 0 12px; font-size: 1.4rem; }
.products {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.card {
flex: 1 1 150px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
}
.card h2 { margin: 0 0 4px; font-size: 1rem; }
.card p { margin: 0; }
flex-wrap: wrap на контейнера пуска нов ред. flex: 1 1 150px на картата: основата е 150px, затова се събират две (150 + 12 + 150 = 312 ≤ 336), а три не. После свободното място на реда се дели поравно: всяка става (336 − 12) : 2 = 162px. Само flex-wrap: wrap прави редове, но всяка карта е широка колкото текста си.
Идеята
Името, което не се свива
В кошницата името на продукта е до картинката, а до него са бройката и цената. Името е дълго. Искаме да се реже с „…“ на един ред: white-space: nowrap, overflow: hidden и text-overflow: ellipsis. Блокът с името е с flex: 1. На телефон обаче редът излиза извън екрана, а цената е смачкана:
Конзола
Причината е едно правило. Flex елементът не се свива под най-тясното си съдържание: min-width по подразбиране е auto, а не 0. За .info най-тясното съдържание е цялото име на един ред. min-width: 0 на flex елемента маха тази граница. Тогава .info се свива, а заглавието вътре реже текста.
Някой е добавил min-width: 0, но името пак не се реже. Кой ред е грешен?
min-width: 0 на ред 4 е на заглавието .name. То не е flex елемент: flex елементът е .info, родителят му. Границата е на .info, затова min-width: 0 трябва да е там.
По-точно, за любопитните
Свиване. Ако основите и gap-овете са повече от контейнера, свободното място е отрицателно и елементите се свиват. Делът не е само по flex-shrink, а по flex-shrink × основата: широкият елемент се свива повече. Лентата от сцената, но широка 280px: 120 + 100 + 100 + 40 = 360, липсват 80px. Теглата са 120, 100 и 100. logo губи 80 × 120 : 320 = 30px, а другите по 25px: 90, 75 и 75px. Елемент с flex-shrink: 0 не се свива.
Стойностите на flex. flex: 1 е 1 1 0%. flex: auto е 1 1 auto: расте, но от ширината на съдържанието си. flex: none е 0 0 auto: нито расте, нито се свива. flex: 0 0 64px е елемент с точно 64px.
Кой не се свива. Картинки и полета имат своя ширина и min-width: auto ги пази от свиване. Затова в кошницата се смачква цената (<strong>), а не картинката.
Друг начин. overflow: hidden на самия flex елемент също маха границата: елемент, който реже съдържанието си, може да се свие до 0.
Един елемент иначе. align-self подравнява само един елемент по напречната ос. order сменя мястото му на екрана, но не и в дървото: клавиатурата и екранният четец вървят по дървото. Затова не местим с order неща, които се натискат.
Редовете при wrap. align-content подрежда самите редове по напречната ос, когато контейнерът е по-висок от тях. display: inline-flex прави контейнер, който стои в текста като дума.
Упражнение
Ред в кошницата
Подреди реда в кошницата на телефон (360px): картинката, името, бройката и цената в ред, центрирани по височина, с 12px между тях. Картинката остава 64px, бройката 56px, цената 72px. Името взима останалото място (тук 108px) и е на един ред, а ако е дълго, се реже с „…“. Страницата не се плъзга встрани.
Конзола
Решение
index.html<!doctype html>
<html lang="bg">
<head>
<meta charset="utf-8">
<title>Кошница</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Кошница</h1>
<div class="line">
<img class="thumb" src="img/bread.svg" alt="">
<div class="info">
<h2 class="name">Пълнозърнест хляб с ръж и семена, нарязан, 500 г</h2>
<p class="unit">1,90 € / бр.</p>
</div>
<input class="qty" type="number" value="2" min="1" aria-label="Брой">
<strong class="price">3,80 €</strong>
</div>
</main>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
h1 { margin: 0; padding: 12px; font-size: 1.4rem; }
.line {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-top: 1px solid #ddd;
}
.thumb { width: 64px; height: 64px; border-radius: 8px; }
.info {
flex: 1;
min-width: 0;
}
.name {
margin: 0 0 4px;
font-size: 1rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.unit { margin: 0; color: #6b7180; }
.qty { width: 56px; }
.price { width: 72px; text-align: right; }
.line { display: flex; align-items: center; gap: 12px;}.info { flex: 1; min-width: 0; }.name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}flex: 1 дава на .info основа 0 и цялото свободно място: 336 − 64 − 56 − 72 − 36 = 108px. С flex-grow: 1 основата остава дължината на името. Тогава мястото е отрицателно и flex-shrink свива цената. Без min-width: 0 името не се свива и редът излиза извън екрана.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: HTML документът · Формуляри · Селектори и каскада · Box model
- Продължава в: Grid: редове и колони · Responsive дизайн · Витрината на магазина
- Задачи по темата: CSS: как изглежда страницата