Веб-разработка
May 5, 2022

Продвинутый HTML и CSS. Часть 2 — Тренажёр HTML Academy

Продвигаемся дальше по джунглям вёрстки. Прошлая часть была почти полностью про HTML, а в этой наоборот — один только CSS. Разберемся с ворохом селекторов, разложим с их помощью пасьянс, подробно поговорим о каскадировании и наследовании и изучим как работают фоны.

Здесь будет много испытаний и даже парочка практических заданий, которые «Вызовы». Испытания, кстати, сложные. Особенно в частях про селекторы. В общем, есть чем заняться и в этот раз действительно интересно.

Порядок будет иной — я склею две части про «Селекторы» в одну и, наверное, приступим к ним в последнюю очередь, начнем с лёгкого и постепенно дойдем до сложностей.

Фоны

Начнем с самого лёгкого в плане теории и, наверное, самого долгого в плане испытаний. Мы будем разбираться с группой свойств background и парочкой хитростей в работе с ними.

Не буду ничего придумывать — пойдем в порядке указанном в тренажёре:

  • background-color — задаёт цвет фона, полностью заливает фон указанным цветом, про способы указания цвета мы разбирались в свойстве color в одной из прошлых частей;
  • background-image — задаёт фоновое изображение с помощью значения url("img.png"), на первый взгляд тривиальное свойство, но на самом деле скрывает в себе много возможностей, о чем нам обещают рассказать в более поздних частях тренажёра;
  • background-repeat — задаёт повторяемость фонового изображения, по умолчанию стоит значение repeat — фон повторяется и горизонтально и вертикально, занимая всё пространство, другие значение repeat-x и repeat-y — повторять только по горизонтали или вертикали соответственно, и no-repeat — не повторять, то бишь будет одно изображение;
  • background-position — управляет положением фонового изображения, принимает два значения x y — где x позиция по горизонтали, а y по вертикали, за значения принимаются ключевые слова — top, left, center и так далее, проценты % и пиксели px, причем значения можно комбинировать — -20px 50%;
  • background-attachment — управляет прикреплением фонового изображения, значение по умолчанию scroll прокручивает изображение вместе содержимым блока, а с помощью значения fixed можно зафиксировать изображение относительно экрана.

У всего этого безобразия есть универсальный вариант записи с помощью сокращённого свойства background. Как и в других подобных свойствах, например, в font — в свойстве background важен порядок компонентов.

Порядок именно тот, что указан выше — сверху вниз от -color до -attachment, при этом если какой-то из компонентов не указан — берётся значение по умолчанию:

background: #e74c3c; /* просто цвет, остальное по умолчанию */
background: url("img.png") no-repeat; /* картинка без повторений */
background: url("img.png") 10px 20px; /* с повтором, но смещена */

По поводу форматов — им уделено несколько уроков, но на мой взгляд оно того не стоило. Мы уже разбирали форматы изображений в одной из прошлых частей и в этот раз добавился только PNG-8.

Хитрости

Перейдем к хитростям — для моего дилетантского ума было сюрпризом использование спрайтов для иконок. Насколько я понял, это не очень распространённый способ, но весьма интересный.

Спрайт — это одно изображение в котором отрисовано несколько, например, иконок. Однако, впервые я со спрайтами столкнулся давно в RPGM — простеньком игровом движке, там именно через спрайты задаётся анимация — несколько кадров отрисовано в одном изображении. Как интересно, в вебе тоже надо попробовать.

Вернемся к иконкам — они расположены в виде такой таблички, и достаточно просто каждую можно найти по координатам:

Таким образом снижается нагрузка на сервер и даже потенциально уменьшается вес страницы, так как одна картинка — один запрос на сервер.


Вторая хитрость не указана в теории, зато написана в последнем испытании. Связана она с сокращенным свойство background — оказывается, в него можно вписывать сразу несколько фонов через запятую:

background:
  url("img1.png") no-repeat 0 0,
  url("img2.png") repeat-x 50% 50%,
  url("img3.png");

Таким образом, даже к одному блоку можно прикрутить какой-нибудь красивый параллакс-эффект. Судя по всему, к этому мы вернёмся по позже, в блоках продвинутого уровня, но сейчас в испытании это было крайне полезно. Кстати, испытания.

Испытания

Их два и оба они просто до безобразия ужасны. И даже не в сложности дело — в них нужно подбирать позицию изображений на глаз. То есть всё сверстать дело двух минут, а потом минут 20 ты сидишь и меняешь background-position у нескольких изображений, чтобы попасть в исходник.

Первое испытание «Котокомикс» еще ладно, но тоже заметьте позицию 41px, блин, по горизонтали для селектора .block8

Я специально заскринил результат — если кому надо списывайте, так как это действительно не вопрос мастерства, а вопрос подбора циферок.

Второе испытание «CAT Academy» это жесть в кубе.

Я сначала задавал все в процентах, что убавило мне количество нервных клеток. Потом решил пойти от фона — звёздочки и надпись, в них попал почти сразу. А дальше минут 10-15 выравнивание котов. Feel free списывать координаты.

Короче, испытание не то чтобы плохое, но ленивое. Зато запоминающиеся, у меня даже в статистике по выдаче моего блога в Google эти испытания есть.

Наследование и каскадирование

Перейдем к сложным для понимания вещам. Сразу скажу — эта часть была между двух частей про Селекторы, которые далее. Поэтому, возможно, здесь будут селекторы которые мы еще раз повторим в следующей части.

Про наследование и каскадирование свойств в CSS мы уже немножечко знаем из части «Знакомства», теперь разберёмся подробно.

Наследование

HTML документ имеет иерархическую структуру — каждый элемент кроме первого, корневого <html>, имеет одного родителя и располагается внутри него. В тренажёре это назвали Иерархическое дерево:

В <html> вложены <head> и <body>, в которые, в свою очередь, вложены их дочерние элементы и так далее, «глубже» по структуре.

Наследование в CSS — это способность родителя-элемента передавать свои значения свойств всем его дочерним элементам.

Если задать <body> свойство color: red;, то текст станет красного цвета во всем документе, если только какому-то конкретному дочернему элементу не задан конкретный цвет.

Однако, наследуются далеко не все свойства. Например, наследуются практически все свойства относящиеся к параметрам текста — color, font, text-align, line-height и другие. Также, например, visabilty — скрывая родительский элемент, скроются и все дочерние.

Перечислять все наследуемые и не наследуемые свойства смысла нет, чаще всего наследственность работает логично, например, фон background не наследуется по понятным причинам. Полный список можно и периодически нужно сверять со спецификацией.

Однако в CSS есть способ заставить элемент унаследовать какое-либо свойство родителя, например, тот же background, с помощью значения inherit:

background: inherit;

Каскадирование и специфичность

Каскадность в CSS означает, что к одному элементу в итоге применяется набор CSS-правил из разных источников. По разным причинам — стили браузера по умолчанию, наследуемость, несколько разных селекторов могут назначать одному и тому же элементу несколько свойств.

Отсюда выходит, что к одному элементу может применяться несколько раз одно и то же свойство, но с разными значениями. Какое значение в итоге будет?

В CSS существуют три основных фактора, определяющих порядок применения свойства к элементу — порядок исходного кода, специфичность селектора, важность. Причем, следующий отменяет предыдущий, то есть специфичность важнее порядка кода.

Сразу разберёмся с порядком кода, это самое простое:

p {color: red;}
p {color: green;}

В данном случае текст будет зелёный green, так как указан позже и таким образом переопределил значение свойства color.

Переходим к сложному — специфичность. Возьмем объяснение из тренажёра, оно хорошо описывает термин:

Чем меньшее количество элементов потенциально может выбрать селектор, тем он специфичнее.

То есть, селектор p {} применятся ко всем <p> в документе — он менее специфичен, чем селектор .text {}, который применяется только к элементам с классом text. В свою очередь селектор .text {} менее специфичен чем селектор p.text {}, который применяется только к <p calss="text">.

Селектор по id #text {} еще более специфичен чем селекторы по классу, так как может применятся только к одному элементу на странице. И как следствие, селектор p.text {} менее специфичен чем #text {}.

Еще большим приоритетом обладают стили в атрибуте style="", так как применяются к одному конкретному элементу в вёрстке.

Наибольший или «усиленный» приоритет, даже больший чем стиль в атрибуте, имеет важное свойство, которое определяется ключевым словом !important в конце этого свойства. Применять его стоит только в критичных ситуациях.

Чтобы как-то структурировать показатели специфичности умные разработчики придумали своего рода индексацию. Селекторы разделены на четыре группы — A, B, C и D — каждая группа это порядок специфичности, от самого специфичного A до наименее специфичного D.

  1. Группа A отвечает за встроенные стили, то есть определенные в атрибуте style="", A=1 если стиль встроен в вёрстку, и A=0 если нет;
  2. Группа B отвечает за количество идентификаторов #id в селекторе;
  3. Группа C отвечает за количество классов .class, псевдоклассов :hover и атрибутных [attr] селекторов (про них скоро узнаем);
  4. Группа D отвечает за количество селекторов по тегу a и псевдоэлементов ::after.

Я рехнусь описывать все здесь в текстовом виде, поэтому в табличке на картинке:

Великий, могучий и ужасный Excel

При этом надо понимать, что это не «баллы» специфичности. Например, селектор с идентификатором #id и классом .class имеет индекс специфичности 0.1.1.0, его часто можно увидеть без точек — 0110, что как раз может сбивать с толку.

Очевидно, что 0.1.1.0 специфичнее чем, например, 0.0.1.1. Однако, если представить себе, что у вас монструозный селектор из более десяти классов 0.0.10.0, то он все равно менее специфичен чем селектор из одного идентификатора 0.1.0.0, хотя если писать без точек будет 00100 и 0100.

Поэтому я рекомендую и сам в дальнейшем если буду прибегать к этой индексации, писать с точками или как-то иначе отделять группы.


Учитывая наследуемость, каскадирование и специфичность, в современной разработке часто используется приём перекрёстного наследования. Он заключается в том, что бы задать базовый стиль для какого-то типа элементов, например для кнопок .button, а затем определить вспомогательные стили для разных нужд, например .button-open, .button-clear и т. д.

Таким образом у всех кнопок на сайте будет определен базовый стиль, а специфичным кнопкам будет добавляться класс, переопределяющий или добавляющий свойства.

Испытание и практика

Разбираемся со взломом! В испытании напортачил кот-конкурент, указав стили в разметке, а наша задача переопределить их применяя полученные знания.

В вызове задачка примерно такая же, но по сложнее — здесь взломан профиль и нужно использовать псевдоклассы из части про селекторы.

Селекторы

Перейдём к самому интересному! В тренажёрах им уделено две части — в первой основные селекторы и базовые псевдоклассы, а во второй уже почти полный набор комплексных селекторов, псевдоклассов и даже парочка псевдоэлементов сюда попали заодно.

Все уроки это знакомство с новым селектором, я же напишу в стиле, что был у меня в части про поля форм в прошлой статье. Иначе, если размусоливать каждый селектор, то статья выпадет из экрана и убежит.

Типы селекторов

Селектор по тегу a {} — содержит стили для всех элементов с указанным тегом, например, все изображения img {}, все параграфы p {} и так далее.

Одно правило может содержать несколько селекторов, в этом случае они записываются через запятую — a, p {} содержит стили общие для ссылок и параграфов. Это относится ко всем селекторам, не только по тегу.

Селектор по классу .class {} — содержит стили для всех элементов с указанным классом в соответствующем атрибуте class="". Основной инструмент при работе со стилями.

Если нужно обратиться к определённым элементам, например <p>, с определённым классом, то в их селектор записывается тег с классом без пробелов — p.class {}.

Если у нужного элемента несколько классов и нам нужно задать стиль конкретному набору элементов с этими классами, то в селектор записываются эти классы без пробелов — .first.second {}. Работает, на самом деле, не только с классами, но и с другими селекторами.

Селектор по идентификатору #id {} — содержит стили для конкретного элемента с указанным атрибутом id="". Стилизация через селекторы по ID считается плохой практикой, хотя иногда может спасти ситуацию.

Селектор по атрибуту input[type="text"] — содержит стили для указанных элементов с указанным атрибутом и значением. В самом начале прохождения тренажёров я подметил, что мы можем меняем стиль через lang="", как оказалось все еще интереснее — можно выбирать по сути любой атрибут и стилизовать через него элемент.

Контекстный или вложенный селектор p a.link {} — содержит стили для указанных элементов, внутри указанного родительского элемента. В нашем примере p a.link {} содержит стили всех ссылок <a> с классом link внутри параграфов <p>. Вложенность задаётся именно через пробел между селекторами. Это одна из самых распространённых конструкций.

Дочерний селектор ul > li {} — содержит стиль указанного дочернего элемента, то есть только ближайшего потомка, указанного родительского элемента. Отличие от контекстного селектора в том, что он влияет на всех потомков, то есть контекстный селектор ul li {} будет влиять как на свои пункты списка <li>, так и на вложенные внутрь него списки, как бы рекурсивно. Дочерний же селектор влияет только на ближайших потомков.

Соседний селектор .first + .second {} — содержит стиль второго селектора, в нашем случае .second, применимые только если этот элемент находится сразу после элемента с первым селектором, в нашем случае .first. Супер странная конструкция, могу примерно представить где её применять, но с натяжкой.

Селектор последующих элементов .first ~ .second {} — похож на соседний селектор, но второй элемент может находится не сразу за первым, а главное после него, то есть между ними могут быть одноуровневые элементы с другими селекторами.

Это всё касаемо селекторов без псевдоклассов в тренажёрах. Беглый гуглинг показал, что их больше, включая комплексные селекторы по атрибутам. Но сейчас не будем на этом заострять внимание, я уверен мы еще вернёмся к ним.

Псевдоклассы

Псевдоклассы дополняют селектор, указывая на его состояние или положение. Они работают со всеми селекторами и конструкциями указанными выше, то есть и контекстные, и дочерние, и объединённые, и так далее конструкции можно дополнять псевдоклассами.

Первый дочерний элемент :first-child — позволяет задать стиль первого указанного дочернего элемента. Тут важно понять логику — псевдокласс задаётся дочернему элементу, а не родителю. Селектор родителя не столь важен.

Последний дочерний элемент :last-child — аналогично, задаёт стиль последнему указанному дочернему элементу. Логика такая же как и с первым.

Определённый дочерний элемент :nth-child(выражение) — позволяет задать стиль указанному или даже группе указанных дочерних элементов. В одном этом псевдоклассе таиться огромное количество возможностей. Долго не мог понять что за «нтх», потом как-то само дошло, что читается «N-th», то бишь «Энное», и это помогло понять логику этого псевдокласса. N — number, мы задаем в скобочках.

В скобочках мы задаем либо просто число, например li:nth-child(3) — это третий дочерний элемент списка, либо ключевое слово или целое выражение. Например, :nth-child(2n) выберет все чётные элементы, что можно записать через ключевое слово :nth-child(even). А нечётные можно задать с помощью :nth-child(2n+1) или :nth-child(odd).

Вычисления в скобочках работают по формуле an+b, где n — это счетчик итерации начинающийся с нуля, а a и b — целые числа, своего рода модификаторы. В случае с нечётными числами можно посчитать, почему именно формула 2n+1 выделяет нечётные элементы:

  1. 2*0+1 = 1 — первый элемент;
  2. 2*1+1 = 3 — третий элемент;
  3. 2*2+1 = 5 — мультипаспорт!
  4. и так далее

Аналогично можно выбрать, например, каждый 4-й элемент или элементы с определённым шагом. Это самая сложная часть, правда. Дальше все :nth-псевдоклассы работают аналогично.

Определённый дочерний элемент с конца :nth-last-child(выражение) — отличие от псевдокласса :nth-child в том, что счет ведётся не с начала, а с конца списка дочерних элементов.

Единственный дочерний элемент :only-child — сработает только если элемент единственный внутри родительского элемента.

Первый дочерний элемент по типу :first-of-type — отличие от первого дочернего элемента :first-child в том, что он ищет первый встречаемый по типу элемент. В то время как :first-child обращается к самому первому элементу не проверяя его тип и если селекторы не совпадают, например указан li:first-child, а первый дочерний элемент <div>, то стили не применяются.

Последний дочерний элемент по типу :last-of-type — аналогично и для последнего элемента, псевдокласс ищет не самый последний дочерний элемент, а последний по указанному селектору.

Определённый дочерний элемент по типу :nth-of-type() — работает также как и :nth-child(), но ищет только элементы указанного типа, игнорируя остальные.

Определённый дочерний элемент по типу с конца :nth-last-of-type() — ищет указанный элемент по указанному типу с конца списка дочерних элементов.

Единственный дочерний элемент по типу :only-of-type — работает так же как и :only-child, но учитывает типа элемента. То есть если внутри родителя есть только один <p> и неважно сколько других элементов, то p:only-of-type сработает. Более дипломатичный вариант.

Пустой элемент :empty — выбирает только полностью пустые элементы, то есть в которых нет ни одного символа, даже переноса строки.


Можно сказать разобрались с дочерними элементами и их набором псевдоклассов. Далее один специфичный псевдокласс и более знакомые мне псевдоклассы состояния.

Отрицающий селектор :not(селектор) — с помощью этого псевдокласса можно выбрать элемент не имеющий указанный в скобочках селектор. Например, li:not(:last-child) {} — выберет все пункты списка кроме последнего.

В скобочки можно написать только единичный селектор или псевдокласс, а также нельзя использовать двойное отрицание :not(:not()). Но можно использовать его последовательно, например:

li:not(:first-child):not(:last-child) {} // не первый и не последний

Далее знакомые многим псевдоклассы, без которых не обходиться стилизаций ни одной ссылки даже на самом простом сайте.

При наведение курсора :hover — позволяет задать стиль элементу при наведении на него курсора мыши. Крайне полезный псевдокласс, с помощью которого можно оживить интерфейс даже без участия JavaScript.

Не посещённая ссылка :link — специальный псевдокласс для ссылок, позволяет настроить стиль ссылки по которой пользователь еще не переходил.

Посещённая ссылка :visited — напротив, позволяет настроить уже посещённую ссылку.

Активная ссылка :active — задаёт стиль ссылки в момент нажатия или при выборе её с помощью клавиши tab.

Выделенный элемент :focus — позволяет настроить стиль выделенного элемента, например, выделяются поля формы <input> если кликнуть на них мышью.

На этом с псевдоклассами всё! Изучение псевдоклассов сложный, но интересный опыт, на них в основном построены испытания, но про них чуть позже, а пока псевдоэлементы.

Псевдоэлементы

В этой части тренажёра упоминаются не все, а только четыре — два самых основных и ещё два на затравку.

Псевдоэлемент «до» ::before — добавляет «псевдотег» внутрь указанного элемента до всего внутреннего содержимого элемента. Работает этот элемент как обычный <span>, при этом он не отображается в разметке, а существует «виртуально» в памяти браузера.

Содержимое псевдоэлемента задается свойством content, причем, чтобы псевдоэлемент появился достаточно даже пустой строки content: "";. В остальном элемент работает как строчный и принимает любые не специфичные CSS-свойства.

Псевдоэлемент «после» ::after — аналогично добавляет псевдотег внутрь элемента, но уже после всего внутреннего содержимого. Работает абсолютно также как и псевдоэлемент ::before.

Эти двое, наверное, самый распространённые и лично мне по началу, когда я ни черта не разбирался в вёрстке, ломали мозг. Смотришь — на сайте контент есть, а в F12 ничего нет.

Далее два псевдоэлемента, которые работают как модификаторы уже существующего текстового контента.

Первая строка текста ::first-line — позволяет задать стиль первой строки текста указанного элемента. Строка определяется до первого переноса. В правилах можно использовать только свойства относящиеся к текстовому оформлению.

Первый символ текста ::first-letter — позволяет задать стиль первого символа, таким образом можно сделать «буквицу». Тоже принимает только текстовые свойства.

Испытания и Практика

Пожалуй, самые сложные испытания были именно в этих частях. Первая часть «Селекторы. Знакомство» проходила с темой биатлона. Здесь одно Испытание и один Вызов.

В Вызове нужно подобрать к CSS-правилам нужные селекторы. Основная сложность в том, что полоски целей две и они по разному стилизованы, первая базово закрыта, а вторая наоборот открыта (или наоборот, уже точно не помню), а стили уже модифицируют отдельные мишени.

При этом контейнеры мишеней — флексы, и если что-то поначалу неправильно ввести оно может даже не сразу это показать.

В испытании у нас внезапно таблица. В этот раз наоборот, CSS закрыт и нам надо подставить нужным ячейкам правильные теги и классы и поменять местами кое-где строки.

Далее часть «Селекторы. Погружение», где всё строится на игральных картах. Первое испытание, на мой взгляд, оказалось самым сложным. Нам впервые дают самостоятельно разбираться с псевдоклассами. Менять разметку нельзя, базовые стили тоже, всё только через уже существующие элементы.

Нужно выделить 9-ки, то бишь четвёртый элемент, и раскрасить определённые масти, определённые позиции карт по номеру и строчке. Здесь прям полные комбинации нескольких псевдоклассов. Очень крутая задачка.

Базово все карты большие как 9-ки

Следующее испытание на псевдоэлементы, не такое сложное, просто детально разбираемся как они работают. Немножечко знакомимся с transform.

И заключительное испытание — это опять раскладка. Она уже сложнее чем первая, больше логических задач. Здесь прям максимум знаний про :nth-вычисления надо применить.

Предыдущая хардкорная раскладка подготовила, поэтому эта показалась может не легче, но не сложнее. Но это всё еще очень круто, обожаю когда сложность в понимании, а не в попадании или угадывании.


Неужели осилил? Это самый большой блок по частям — восемь штук, с большим количеством уроков, испытаний и даже практикой. Я его делал еще второпях в рамках интенсива. Следующий блок будет про построение сеток, он меньше.

Спасибо за внимание! Очень рад всем кто это прочитал и еще больше рад если кому-то это было интересно или как-то помогло.

Ссылки на предыдущие статьи по HTML Academy:

Знакомство с Веб-разработкой
Знакомство с HTML и CSS
Знакомство с JavaScript
Знакомство с PHP
Таблицы и подробно о формах
Наследование, каскады и селекторы <- Вы здесь
Блочная модель, поток и сетка на float
Гибкие флексбоксы display: flex
Удобные сетки на гридах display: grid
Пропуск блока «Погружение»
Позиционирование и двумерные трансформации
Теневое искусство и линейные градиенты
CSS-фильтры и Кекстаграм
Мастерские
Продвинутые Мастерские
...

Остальные статьи можно посмотреть у меня на главной странице блога.

Также мои соц. сетки, которые я продолжаю вести:

Мой Twitter
Мой Telegram
Мой Паблик ВК

Заходите куда удобно вам и подписывайтесь! Еще раз спасибо за внимание!