Веб-программирование
Веб-программирование · лекция 3 из 16
Коротко: научились выбрать элемент и оформить его. Сегодня учимся ставить элементы на места.
Типографика в порядке. Цвета подобраны. Рамки скруглены, отступы расставлены, ссылки подсвечиваются на наведении.
И всё это лежит в одну колонку сверху вниз.
Это поведение по умолчанию, и у него есть имя — поток документа. Браузер раскладывает элементы сам, ещё до того, как вы написали первую строчку про раскладку.
Вся сегодняшняя лекция — про то, как договориться с потоком: где его хватает, где его меняют и где из него выходят совсем.
displayz-indexИ ответ на вопрос, который веб задаёт себе двадцать лет.
display
Что браузер делает сам
Его не нужно включать. Он работает всегда, пока вы не попросили иного.
занимают всю доступную ширину, даже если текста два слова
ширина по содержимому, перенос по словам
«Блочный» и «строчный» — не врождённое качество элемента. Это значение
свойства display, которое пришло из
стилей браузера, — того самого user agent stylesheet
из прошлой лекции.
div { display: block; }
span { display: inline; }
Значит, его можно переписать. И это первое, что мы сделаем.
Отвечаем вслух, потом проверим
span {
width: 300px;
height: 200px;
margin-top: 50px;
}
Насколько большим станет <span> и отодвинет ли он
соседей?
width и height не работают, вертикальные
margin не раздвигают соседей. Браузер честно пропустит все
три строки.
Строчный элемент — часть текстовой строки, а строка не станет двухсот пикселей высотой по просьбе одного слова. Хотите геометрию — выйдите из строки.
inline-block: гибридведёт себя как строчный: встаёт в ряд с соседями
ведёт себя как блочный: ширина, высота, отступы работают
<span class="box">A</span>
<span class="box">B</span>
Три блока по 33.33% с inline-block не
помещаются в строку и последний съезжает вниз. Почему?
/* убить пробелы размером шрифта */
.parent { font-size: 0; }
.child { font-size: 16px; } /* и вернуть его детям */
<span>A</span><!--
--><span>B</span> <!-- перенос внутри комментария -->
Так действительно писали. Сегодня так не пишут: есть
gap, до которого дойдём в части 4.
Блок с фиксированной высотой и лишним текстом
Привет из лекции 2: браузер не станет ломать вашу высоту ради содержимого.
overflow: что делать с лишним
Есть и раздельные overflow-x и overflow-y.
Важное следствие: всё, кроме visible, делает элемент
контейнером прокрутки. Запомните — в части 3 это объяснит одну
загадку.
display: none против visibility: hiddendisplay: noneэлемента нет ни в потоке, ни в дереве доступности: места не занимает, скринридер не читает
visibility: hiddenэлемент невидим, но место осталось: в потоке зияет дыра точно его размера
Спрятать от глаз и спрятать от всех — разные задачи.
1996
<table width="760" cellspacing="0"><tr>
<td><img src="spacer.gif" width="1" height="1"></td>
<td><table> ... </table></td>
</tr></table>
spacer.gif — прозрачный пиксель, растянутый до
нужного отступа. Распорка.
2005
float придумали для газеты
Задача была одна: обтекание картинки текстом. Потом заметили,
что два float: left дают две колонки — и
пятнадцать лет веб верстали вот этим:
.clearfix::after {
content: ""; display: table; clear: both;
}
.outer { display: table; width: 100%; height: 300px; }
.inner { display: table-cell; vertical-align: middle; }
Вертикальное центрирование через таблицу — без таблицы. Работало, но требовало лишнего элемента-обёртки и ломалось от любого изменения макета.
Чем мерить, кроме пикселей
Это было сознательное упрощение: в лекции 2 хватало
px, и лишние единицы только мешали бы.
Дальше упрощение начинает вредить. Макет должен выживать на разных экранах и при увеличенном шрифте, — а для этого размеры должны быть относительными.
em и rememот размера шрифта текущего элемента
remот размера шрифта корня, то есть <html>
Разница в одной букве, а поведение отличается принципиально.
html { font-size: 16px; }
li { font-size: 0.9em; }
<ul><li>первый уровень
<ul><li>второй
<ul><li>третий
<ul><li>четвёртый</li></ul>
С 0.9rem все четыре уровня были бы одинаковыми: отсчёт
всегда от корня, а не от соседа сверху.
rem — для шрифтаem — внутри элемента
padding: 0.5em 1em у кнопки масштабируется вместе с её
текстом — крупная кнопка сама станет просторнее. Это ровно то, что нужно.
Пользователь может увеличить базовый размер шрифта в настройках браузера — так делают люди со слабым зрением, и таких больше, чем кажется.
Макет на rem вырастет вместе с ним. Макет на
px — нет.
Та же тема, что alt в лекции 1 и :focus в
лекции 2. Доступность — не отдельная дисциплина, а следствие мелких
решений.
.child { width: 50%; } /* от ширины родителя — ожидаемо */
С шириной интуиция работает. Дальше она перестаёт.
.box {
/* ← 10% от ШИРИНЫ родителя, не от высоты */
padding-top: 10%;
margin-top: 10%; /* ← и здесь тоже от ширины */
}
Даже по вертикали. Это кажется произволом, но именно на этом построен
старый трюк с пропорциональными блоками: видео 16:9 задавали
через padding-top: 56.25%.
Сегодня для этого есть aspect-ratio.
calc(): арифметика прямо в CSS.main {
width: calc(100% - 320px);
}
Главное здесь — смешивание единиц. Проценты знает
только браузер, пиксели знаете вы; посчитать это заранее нельзя, а
calc() считает уже на месте.
calc(100%-320px)
calc(100% - 320px)
Пробелы вокруг - и + обязательны, иначе минус
читается как часть числа. Правило молча не применится — и вы будете
искать опечатку глазами, как в лекции 2.
height: 100% не работает/* эту строку все копируют не глядя */
html, body { height: 100%; }
.page { height: 100%; }
Процент высоты считается от высоты родителя. Если у родителя высота
auto — считать не от чего, и браузер просто игнорирует
правило.
Отсюда цепочка до самого корня. И отсюда же привычка брать вместо этого
min-height: 100vh.
Удобно для экранов «на всю высоту» и крупных заголовков. И ровно здесь веб столкнулся с телефонами.
100vh на телефоне врётАдресная строка то появляется, то прячется при прокрутке. Высота окна меняется на ходу — и блок «ровно на экран» либо прыгает, либо вылезает за нижний край вместе с кнопкой, ради которой его делали.
Несколько лет это чинили джаваскриптом, пересчитывая высоту вручную.
Хороший пример того, как работает CSS: сначала появляется боль, потом —
единица измерения. 100dvh — то, что вы хотели написать
с самого начала.
/* ch — ширина нуля в текущем шрифте */
.article { max-width: 65ch; }
Комфортная длина строки — 50–75 знаков. 65ch говорит это
прямо, а 750px — только приблизительно и только для одного
шрифта.
fr — доля свободного места, живёт только в гридах,
встретим в части 5. clamp() — «не меньше, не больше,
а между» — в лекции 4.
Как выйти из потока
positionРазбираем по порядку — каждое следующее радикальнее предыдущего.
.box {
top: 40px; /* написано */
left: 20px; /* написано */
} /* и ничего не происходит */
top, right, bottom,
left работают только у элементов
с position, отличным от static.
Половина вопросов «почему не двигается» — про это.
relative: сдвиг без последствийэлемент уехал визуально, но место в потоке за ним осталось
relativeВ реальной работе его почти никогда не пишут ради сдвига. Пишут ради побочного эффекта: стать системой отсчёта для абсолютно позиционированного потомка.
/* ничего не сдвинул */
.card { position: relative; }
.badge { position: absolute; top: 10px; right: 10px; }
Эта пара — самая частая связка во всём CSS. Дальше поймём, почему.
absolute: элемента больше нет в потокеабсолютный элемент исчез из расчёта: соседи схлопнулись, как будто его и не было
Он никуда не делся — просто теперь он в слое над потоком.
position не
static. Если такого нет — от корня документа.
Вот зачем родителю position: relative, который ничего не
сдвигает: он объявляет себя точкой отсчёта и не даёт потомку улететь
в угол страницы.
.modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
top: 50% ставит левый верхний угол в центр — это ещё
не центрирование. translate(-50%, -50%) отодвигает элемент
на половину его собственного размера, и вот теперь центр совпал.
transform.modal {
position: absolute;
inset: 0; /* все четыре стороны */
margin: auto;
width: 300px;
height: 200px;
}
Элемент растянут на все стороны, но размер задан точно —
и margin: auto делит остаток поровну.
fixed: привязка к окнуКоординаты считаются от окна браузера, а не от документа. Элемент не уезжает при прокрутке: липкие шапки, кнопка «наверх», подложка модального окна.
.header { position: fixed; top: 0; left: 0; right: 0; }
/* где-то выше по дереву */
.wrapper { transform: translateZ(0); }
/* и он больше не fixed */
.header { position: fixed; top: 0; }
transform, filter или
will-change у любого предка создают новый контейнер,
и fixed внутри начинает считаться от него, а не от окна.
Услышать об этом заранее дешевле, чем искать самому.
sticky: гибридЭлемент лежит в потоке как обычный и прокручивается вместе со страницей — пока не дойдёт до заданной границы. Дальше прилипает и держится, пока не кончится родитель.
thead th {
position: sticky;
/* без этой строки не прилипнет ни к чему */
top: 0;
}
top — не от чего прилипатьoverflow: hidden или autoТретья — самая обидная: всё написано правильно, а эффекта нет, потому что прокручиваться внутри родителя нечему.
z-index
.parent { opacity: 0.99; }
/* внутри .parent */
.a { position: relative; z-index: 9999; }
/* снаружи */
.b { position: relative; z-index: 1; }
Девять тысяч против единицы. Голосуем руками.
.b с единицей. И никакое число
в .a этого не изменит — хоть миллион.
z-index сравнивается только между соседями
внутри одного контекста наложения. opacity: 0.99
создала родителю свой контекст — и всё, что внутри, поехало вместе с ним
одним слоем.
z-index, кроме autoisolate — намеренноПервые пять создают его побочно: человек хотел прозрачность, а получил новый слой.
isolation: isolate вместо гонки
вооружений
Главный инструмент раскладки
У каждой задачи было по три решения, и все три — с оговорками.
boxflexboxflex
Три несовместимых синтаксиса подряд. Именно поэтому в старых проектах
встречается display: -webkit-box — это привет из первого
подхода.
.container { display: flex; }
Одно правило на родителе — и три блочных div встали в ряд.
Ни float, ни inline-block, ни
clearfix.
Половина ошибок новичка — свойство написано не там.
главная ось — слева направо
поперечная — сверху вниз
главная ось — сверху вниз
поперечная — слева направо
/* по умолчанию: слева направо */
flex-direction: row;
flex-direction: row-reverse; /* справа налево */
flex-direction: column; /* сверху вниз */
flex-direction: column-reverse; /* снизу вверх */
Про reverse — та же оговорка, что будет про
order: меняется картинка, но не порядок чтения.
justify-content — вдоль главной осиjustify-content: flex-start; /* по умолчанию */
justify-content: center;
/* крайние по краям, остальное поровну */
justify-content: space-between;
justify-content: space-around;
justify-content: space-evenly;
align-items — поперёк главной оси/* по умолчанию: растянуть на высоту строки */
align-items: stretch;
align-items: flex-start;
align-items: center;
align-items: baseline; /* по базовой линии текста */
Зал обычно делится ровно пополам
.container {
display: flex;
flex-direction: column;
justify-content: center;
}
Элементы встанут по центру по горизонтали или по вертикали?
column главная ось направлена
сверху вниз, а justify-content всегда работает по главной —
значит, теперь он центрирует вертикально.
Чтобы отцентрировать такой контейнер по горизонтали, нужен
align-items: center. Да, в точности наоборот привычному.
flex-direction,
а не привычка читать слева направо
.parent {
display: flex;
justify-content: center;
align-items: center;
}
Три строки — и по обеим осям. Ради этого половина зала сюда и шла.
В DevTools рядом с флекс-контейнером стоит значок
flex. Нажмите — браузер нарисует поверх страницы обе оси,
промежутки и то, как распределилось свободное место.
Там же, в панели Styles, у justify-content и
align-items есть картинки вместо списка:
видно, что делает каждое значение, ещё до применения.
Путаницу с осями быстрее всего лечит именно этот значок.
gap — расстояние между.container { display: flex; gap: 20px; }
Одна строка. Промежутки между элементами, но не по краям контейнера — ровно то, что обычно нужно.
gap.item {
margin-right: 20px;
}
.item:last-child {
margin-right: 0;
}
.list {
gap: 20px;
}
А при переносе на две строки сверху вешали ещё и отрицательный
margin на контейнер.
.container {
display: flex;
/* по умолчанию nowrap: сжимает, но не переносит */
flex-wrap: wrap;
}
Без wrap флекс будет сжимать элементы до последнего, лишь
бы уместить их в одну строку. Иногда это ровно то, что нужно; чаще — нет.
align-content против align-itemsalign-itemsкак элементы стоят внутри своей строки
align-contentкак строки целиком распределены в контейнере
Второе имеет смысл, только когда строк больше одной, то есть при
flex-wrap: wrap. Без переноса свойство просто ничего не
делает — ещё один повод «не работает, а почему, непонятно».
По отдельности их пишут редко. Почти всегда пишут сокращение.
flex: 1 — самая частая строка.item { flex: 1; }
/* разворачивается в: */
.item {
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%; /* ← вот это важно */
}
basis: 0 означает «забудь про содержимое, считай с нуля».
Отсюда следующий вопрос.
.item { flex: 1; }
<div class="item">Да</div>
<div class="item">Довольно длинная надпись в три слова</div>
<div class="item">Нет</div>
flex-basis: 0 обнуляет вклад
содержимого, и всё доступное место делится на три равные доли.
А вот flex: auto — это basis: auto, и тогда
ширины были бы разными, по содержимому.
/* и внутри длинная ссылка без пробелов */
.sidebar { flex: 1; }
Флекс-элемент по умолчанию не сжимается уже своего
содержимого: у него min-width: auto. Длинное слово,
широкая картинка или таблица внутри — и блок распирает макет за границы
экрана.
Снаружи это выглядит как «появился горизонтальный скролл, а виноватого не видно».
.sidebar {
flex: 1;
/* разрешаем сжиматься ниже содержимого */
min-width: 0;
}
Для flex-direction: column то же самое, но
min-height: 0.
Человек, который слышал про это один раз, экономит себе вечер.
.special {
/* этот — по-своему, остальные как сказал родитель */
align-self: flex-end;
/* и первым, хотя в разметке он третий */
order: -1;
}
align-self перебивает align-items родителя,
order — визуальный порядок.
order меняет только картинкуПорядок табуляции, порядок чтения скринридером и порядок в исходнике остаются прежними. Пользователь, который ходит по странице с клавиатуры, будет прыгать по ней в непонятном ему порядке.
Тот же принцип, что :focus в прошлой лекции:
внешний вид не должен расходиться со смыслом.
Flexbox Froggy Томаса Парка: двадцать четыре уровня, от
justify-content до align-content. Лучший
способ закрепить эту часть, и он занимает двадцать минут.
Есть русская версия. Наша игра на прошлом слайде — короткий разогрев по мотивам, оригинал полнее.
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
Четыре строки. В эпоху float это была задача на полчаса.
.cards { display: flex; flex-wrap: wrap; gap: 20px; }
/* не уже 240, дальше тянемся и переносимся */
.card { flex: 1 1 240px; }
Карточки автоматически одинаковой высоты — бесплатно, из-за
align-items: stretch по умолчанию. Раньше ради этого
писали скрипт, который мерил высоты и выравнивал их вручную.
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* забирает весь остаток — футер уезжает вниз */
main { flex: 1; }
Футер прижат к низу на короткой странице и идёт следом за содержимым на длинной. Раньше у этой задачи было пять конкурирующих решений.
Когда осей две
одна ось: элементы выстраиваются в линию, а переносы — это следствие, а не замысел
две оси сразу: строки и колонки задаются заранее, элементы садятся в готовую сетку
Разница не в мощности, а в направлении мысли.
Меню, панель кнопок, ряд тегов — флекс: сколько элементов пришло, так и встали. Каркас страницы, галерея, календарь — грид: сетка есть до того, как в неё что-то положили.
И они не конкуренты: грид снаружи, флексы внутри ячеек — обычное дело.
2017
Первый черновик подала Microsoft в 2011-м, реализация вышла в IE10 с префиксами. Спецификацию довели до ума Рейчел Эндрю и Джен Симмонс.
В марте 2017 Grid въехал в Chrome, Firefox и Safari практически одновременно — и пользоваться им можно было в тот же месяц. Такого в вебе почти не бывает.
Рейчел Эндрю — «The New CSS Layout».grid {
display: grid;
grid-template-columns: 200px 1fr 200px;
gap: 20px;
}
fr — доля свободного места
Единица, которой нет больше нигде в CSS. Сначала браузер вычитает всё
фиксированное и все gap, а остаток делит между
fr пропорционально.
/* 25% / 50% / 25% остатка */
grid-template-columns: 1fr 2fr 1fr;
В отличие от процентов, fr учитывает промежутки сам —
и сетка не вылезает за край от того, что 33 % × 3 плюс два
gap больше ста.
/* вместо 1fr 1fr 1fr 1fr */
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 120px);
gap: 20px; /* сразу по обеим осям */
gap: 20px 40px; /* между строками / между колонками */
gap здесь тот же самый, что во флексе, — свойство одно на
обе раскладки.
.gallery {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
Читается как фраза: повторяй колонки, сколько влезет, каждая — не уже двухсот пикселей и не шире равной доли.
.parent {
display: grid;
place-items: center;
}
Две строки, обе оси. place-items — это
align-items и justify-items разом.
grid-template-areas:
"hd hd hd"
"nav main aside"
"ft ft ft";
.header { grid-area: hd; }
.nav { grid-area: nav; }
Самый наглядный синтаксис во всём CSS: макет рисуется прямо в стилях, и схему видно, не запуская браузер.
Рядом с грид-контейнером в DevTools — значок grid. Он
рисует сетку поверх страницы вместе с номерами линий и именами
областей.
Это заодно и ответ на то, чего мы сегодня не проходили: размещение по
линиям через grid-column проще всего понять, когда эти
линии пронумерованы прямо на макете.
Шапка, футер, три колонки, средняя тянется, боковые фиксированы, все одинаковой высоты. У этой задачи есть собственное имя — «святой Грааль вёрстки».
Ей посвящали статьи в A List Apart. У неё было не меньше десяти решений, и каждое со своей оговоркой.
.wrap { padding: 0 200px; overflow: hidden; }
.col { position: relative; float: left; }
.main { width: 100%; }
.left { width: 200px; margin-left: -100%; right: 200px; }
.right{ width: 200px; margin-right: -200px; }
.wrap::after { content: ""; display: table; clear: both; }
/* ...и ещё тридцать строк, чтобы починить высоты и IE6 */
Отрицательные отступы, которые никто не мог объяснить.
.wrap {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"hd hd hd"
"nav main aside"
"ft ft ft";
min-height: 100vh;
}
Комментировать нечего. Слайды говорят сами.
Четыре макета, голосуем руками
Если сомневаетесь — берите флекс. Он прощает больше.
grid-column, grid-row — размещение по линиямgrid-auto-flow: densesubgrid — сетка, наследующая родительскуюВсё это есть и всё это пригодится. Цель части скромнее: понимать, зачем грид, и читать чужой грид-код без паники.
Grid Garden того же автора: двадцать восемь уровней, поливаем морковь, задавая колонки и строки.
Там как раз подробно про линии и grid-column, которые мы
сегодня пропустили.
Долг с прошлой лекции
| 1 | margin: 0 auto |
X |
| 2 | text-align: center + inline-block |
X |
| 3 | display: table-cell + vertical-align |
X Y |
| 4 | absolute + translate(-50%, -50%) |
X Y |
| 5 | absolute + inset: 0 + margin: auto |
X Y |
| 6 | flex + justify-content + align-items |
X Y |
| 7 | grid + place-items: center |
X Y |
Остальные три — для чтения чужого кода.
place-items: center
Честный ответ на вопрос. Две строки, обе оси, работает везде
В демо был тумблер «содержимое стало больше». Способ с фиксированными
width и height сразу разваливается —
и ровно поэтому способов семь, а не один.
Заодно это ответ на вопрос «почему в CSS всё так сложно». Не сложно — просто язык тащит на себе двадцать пять лет совместимости.
А тем, кто до сих пор не нашёл все пасхалки на стартовой странице
курса: там есть команда center a div.
Теперь вы хотя бы знаете, что искать.
И лабораторная
position: absolute,
потом воюет числами в z-index
По итогам лекций 2 и 3. Берём свою страницу из ЛР 1 — ту самую, с валидной разметкой и видом документа из девяносто шестого, — и верстаем её по-настоящему.
Требования и срок — в задании. Разметку из ЛР 1 менять можно, но семантика должна остаться семантикой.
transition, transform, анимацииclamp(), dvh, container queriesСетка уже умеет перестраиваться сама. Дальше научим её делать это осмысленно.
Слайды, демо и обе игры — в репозитории курса