diff --git a/src/pages/css/index.md b/src/pages/css/index.md index acf747a..1f2aba8 100644 --- a/src/pages/css/index.md +++ b/src/pages/css/index.md @@ -2309,9 +2309,82 @@ Sass/Less дают nesting, mixins, functions, modules и удобства дл **Полный ответ** -Sass/Less дают nesting, mixins, functions, modules и удобства для дизайн-систем. Минусы: дополнительная сборка, риск -глубокой вложенности, абстракций поверх CSS и расхождения с runtime-возможностями браузера. Многие задачи сегодня -закрывают native CSS custom properties, nesting, cascade layers и modern selectors. +CSS preprocessor принимает source вроде Sass/Less и **до браузера** компилирует его в обычный CSS. Поэтому его +возможности делятся на две группы: удобный authoring syntax и compile-time programming. + +Например, Sass может дать variables, mixins, functions, loops и modules: + +```scss +$space: 8px; + +@mixin focus-ring { + outline: 2px solid currentColor; + outline-offset: 2px; +} + +.button { + padding: $space $space * 2; + + &:focus-visible { + @include focus-ring; + } +} +``` + +После build browser видит только сгенерированный CSS. Sass variable `$space` уже не существует в runtime, поэтому +JavaScript, cascade или media query не могут изменить ее значение после загрузки страницы. + +Это важное отличие от CSS custom properties: + +```css +:root { + --space: 0.5rem; +} + +.card { + padding: var(--space); +} +``` + +`--space` остается частью CSSOM, участвует в cascade/inheritance и может меняться в runtime. Поэтому Sass variables и +custom properties решают пересекающиеся, но не одинаковые задачи. + +Плюсы preprocessors: + +- compile-time functions/mixins помогают генерировать повторяющиеся patterns; +- modules позволяют организовать большую style codebase; +- зрелые ecosystems дают utilities для color/math и design-token generation; +- legacy projects могут иметь большой объем готовых Sass/Less abstractions. + +Минусы: + +- дополнительная build dependency и время compilation; +- source map/debugging сложнее прямого CSS; +- легко создать слишком глубокую nesting и огромный generated output; +- compile-time abstractions иногда скрывают реальную cascade/specificity; +- часть старых причин использовать preprocessor теперь закрывает native CSS. + +Например, browser уже поддерживает CSS nesting: + +```css +.card { + padding: 1rem; + + & > .title { + font-weight: 600; + } +} +``` + +Но native nesting **не является Sass один-в-один**. Например, Sass-паттерн `&__title` конкатенирует selector name, а CSS +nesting так делать не умеет. Поэтому миграция с Sass не всегда сводится к удалению build step. + +Современный выбор обычно такой: если проекту нужны в основном variables, nesting и cascade organization, стоит сначала +проверить native CSS custom properties, nesting и `@layer`. Если реально нужны compile-time loops, reusable functions +или существующая Sass ecosystem, preprocessor по-прежнему оправдан. + +На интервью: **preprocessor — compile-time layer над CSS. Он полезен там, где нужна генерация/абстракция до browser, но +runtime theming и cascade лучше решать native CSS primitives**. @@ -2329,9 +2402,55 @@ CSS postprocessors обрабатывают уже написанный CSS: д **Полный ответ** -CSS postprocessors обрабатывают уже написанный CSS: добавляют vendor prefixes, оптимизируют output, раскрывают -современный синтаксис или проверяют правила. Типичный пример — PostCSS с Autoprefixer. Это снижает ручную работу, но -должно опираться на реальную browser support policy, а не на настройки на всякий случай. +CSS postprocessor работает с CSS как с входными данными и преобразует его в build pipeline. На практике чаще всего речь +идет об инструментах на базе PostCSS, которые разбирают CSS в AST и запускают plugins. + +Классический пример — Autoprefixer: + +```css +.example { + user-select: none; +} +``` + +В зависимости от target browsers pipeline может добавить только реально необходимые vendor-prefixed declarations. Важно, +что решение принимается по browser support policy, обычно через Browserslist, а не по памяти разработчика. + +Другие типичные задачи postprocessing: + +- преобразование части нового CSS syntax для выбранных targets; +- minification и объединение безопасных rules; +- удаление comments/dead artifacts; +- linting или custom AST checks; +- нормализация output сторонних generators. + +Например, `postcss-preset-env` может позволить писать часть современного CSS и преобразовать ее в форму, понятную target +browsers. Но такое преобразование не означает, что **любую** новую browser feature можно polyfill-ить +CSS-трансформацией. Если feature зависит от runtime layout algorithm или platform API, build tool может быть бессилен. + +Postprocessor также не должен превращаться в безусловный набор «всех prefix на свете»: + +```css +/* плохо поддерживать вручную */ +-webkit-something: value; +-moz-something: value; +something: value; +``` + +Ручные prefixes устаревают вместе с browser matrix. Лучше иметь единую target policy и воспроизводимый pipeline. + +Trade-offs тоже есть: + +- больше plugins — больше complexity и update surface; +- transform может изменить semantics нового syntax или затруднить debugging; +- minifier должен сохранять observable behavior; +- source maps нужны, чтобы DevTools указывал на исходный source, а не только generated CSS. + +Термин «postprocessor» исторический: современный PostCSS pipeline может стоять на разных этапах build и работать не +только «после всего CSS». Важнее понимать функцию — **AST transformation готового CSS syntax**. + +На интервью: **postprocessing автоматизирует compatibility/optimization policy; Autoprefixer должен следовать target +browsers, а не заменять понимание browser support**. @@ -2349,13 +2468,70 @@ preload только для критичных начертаний и fallback **Полный ответ** -Шрифты подключают через `@font-face`, задают `font-family`, `src`, `font-weight`, `font-style` и `font-display`. -Используют WOFF2, preload только для критичных начертаний и fallback stack с похожими метриками, чтобы снизить CLS. -Слишком много начертаний ухудшает LCP и first render. +Web font обычно объявляют через `@font-face`, после чего используют его обычным `font-family`. + +Минимальный пример: + +```css +@font-face { + font-family: 'Product Sans'; + src: url('/fonts/product-sans-regular.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +body { + font-family: 'Product Sans', system-ui, sans-serif; +} +``` + +Для web обычно предпочитают WOFF2: он сжат специально для доставки fonts. Каждое реально используемое начертание должно +быть корректно описано через `font-weight`/`font-style`, иначе browser может синтезировать bold/italic или загрузить не +тот face. + +Variable font способен покрыть диапазон weights одним resource: -Командные правила для fonts должны описывать источник файлов, разрешенные weights, fallback stack, `font-display`, -preload strategy и связь с design tokens. Это защищает проект от ситуации, когда каждая feature случайно подключает -новое тяжелое начертание. +```css +@font-face { + font-family: 'Product Sans Variable'; + src: url('/fonts/product-sans.woff2') format('woff2'); + font-weight: 100 900; + font-style: normal; + font-display: swap; +} +``` + +Но variable file не автоматически меньше любого набора static fonts: нужно сравнивать реальные assets/subsets. + +`font-display` задает стратегию между invisible text, fallback font и поздней заменой. Часто выбирают `swap`, `fallback` +или `optional` в зависимости от важности brand font и допустимого layout shift. + +Для performance важны не только CSS declarations: + +- preload нужен только для действительно critical font resource, который понадобится на первом render; +- URL в preload должен совпадать с URL в `@font-face`; +- cross-origin правила должны быть настроены корректно; +- subset/`unicode-range` может не загружать glyphs, которые странице не нужны; +- не стоит загружать пять weights, если интерфейс использует два. + +Web font способен вызвать layout shift, если fallback сильно отличается по metrics. Помимо выбора похожего system +fallback, CSS Fonts дает descriptors вроде `size-adjust`, `ascent-override`, `descent-override` и `line-gap-override`, +которыми можно приблизить fallback metrics к web font. Их поддержку нужно учитывать в target browsers. + +```css +@font-face { + font-family: 'Product Fallback'; + src: local('Arial'); + size-adjust: 102%; +} +``` + +Preload не нужно использовать «на всякий случай»: каждый preload конкурирует за network priority с CSS, images и другими +critical resources. + +На интервью: **правильное подключение font — это не только `@font-face`: нужно описать faces, выбрать loading strategy, +минимизировать bytes и контролировать fallback metrics/CLS**. @@ -2373,9 +2549,52 @@ FOUT означает, что браузер сначала показывает **Полный ответ** -FOUT означает, что браузер сначала показывает fallback font, а потом заменяет его на custom font. FOIT означает, что -текст временно невидим, пока custom font не загрузится. Обычно этим управляют через `font-display`, preload только -критичных fonts, subset и fallback с близкими метриками. +FOUT и FOIT описывают, что пользователь видит, пока downloadable web font еще не готов. + +**FOIT — Flash of Invisible Text**: browser некоторое время скрывает glyphs, ожидая web font. Текст занимает место через +invisible fallback, но визуально пользователь его не видит. + +**FOUT — Flash of Unstyled Text**: browser сразу показывает fallback font, а после загрузки заменяет его web font. +Контент доступен раньше, но при заметно разных metrics возможен layout shift. + +Этим поведением управляет `font-display` внутри `@font-face`: + +```css +@font-face { + font-family: 'Brand'; + src: url('/brand.woff2') format('woff2'); + font-display: swap; +} +``` + +У font loading есть block period, swap period и затем failure behavior. Конкретная продолжительность зависит от user +agent, поэтому не стоит заучивать одно универсальное число миллисекунд. + +Основные стратегии: + +- `block` допускает короткий период invisible text, затем длительный swap period; +- `swap` почти сразу показывает fallback и разрешает заменить его позже; +- `fallback` дает небольшой шанс font загрузиться быстро, но ограничивает поздний swap; +- `optional` минимизирует блокировку и может оставить fallback до следующей navigation/session, если font не пришел + достаточно быстро; +- `auto` оставляет стратегию browser. + +`swap` часто улучшает perceived availability текста, но не гарантирует лучший UX автоматически. Если brand font сильно +шире fallback, поздняя замена может двигать строки и элементы. + +Для снижения проблемы комбинируют: + +- небольшой WOFF2/subset; +- preload только critical face; +- хороший fallback stack; +- metric matching через `size-adjust` и font metric overrides там, где это подходит support policy; +- отказ от ненужных weights/styles. + +FOIT/FOUT — не отдельные CSS bugs, а trade-off между **скоростью появления текста, визуальной стабильностью и brand +typography**. + +На интервью: **`font-display` управляет timeline загрузки; FOUT показывает fallback раньше, FOIT временно скрывает text, +а оптимизация должна учитывать и readability, и CLS**. @@ -2393,9 +2612,61 @@ Pseudo-element создает стилизуемую часть элемента **Полный ответ** -Pseudo-element создает стилизуемую часть элемента, которой нет как отдельного DOM-узла: `::before`, `::after`, -`::marker`, `::placeholder`, `::selection`. Его используют для декоративного контента, markers и визуальных деталей. -Смысловой текст лучше хранить в HTML, чтобы он был доступен assistive technologies и копированию. +Pseudo-element позволяет выбрать и стилизовать **часть/абстрактный элемент render tree**, которой не обязательно +соответствует отдельный DOM node. + +Синтаксис использует двойное двоеточие: + +```css +selector::pseudo-element { + /* declarations */ +} +``` + +Примеры решают разные задачи: + +```css +li::marker { + color: tomato; +} + +input::placeholder { + color: gray; +} + +p::first-line { + font-weight: 600; +} +``` + +`::marker` адресует marker list item, `::placeholder` — placeholder form control, `::first-line` — первую formatted +line. Это показывает, почему pseudo-element нельзя сводить только к «виртуальному `div`». + +`::before` и `::after` создают generated boxes, когда `content` приводит к их генерации: + +```css +.external-link::after { + content: ' ↗'; +} +``` + +Но meaningful content лучше не хранить только в CSS. Generated content может по-разному попадать в accessibility tree, +не всегда удобно копируется и исчезает вместе со styles. Для обязательной подписи/инструкции правильнее HTML. + +Pseudo-element также не является обычным DOM `Element`: + +```js +document.querySelector('.external-link::after'); // не возвращает pseudo-element +``` + +Его existence и допустимые properties определяются конкретной CSS specification. Например, набор свойств для +`::first-line` ограничен сильнее, чем для обычного element. + +Ранние pseudo-elements исторически поддерживали syntax с одним colon (`:before`), но современная запись — `::before`, +чтобы визуально отличать pseudo-elements от pseudo-classes. + +На интервью: **pseudo-element адресует часть formatting/render structure без отдельного DOM node; `::before`/`::after` — +только два частных примера**. @@ -2413,9 +2684,67 @@ Pseudo-class выбирает элемент по состоянию или от **Полный ответ** -Pseudo-class выбирает элемент по состоянию или отношению: `:hover`, `:focus-visible`, `:checked`, `:disabled`, -`:first-child`, `:has()`. Она не создает новый box, а уточняет selector. Для accessibility особенно важны -`:focus-visible`, disabled states и состояния form controls. +Pseudo-class добавляет к selector условие, которое определяется state, structure или relation элемента, а не отдельным +class attribute в markup. + +Примеры state: + +```css +button:hover { +} +button:focus-visible { +} +input:checked { +} +input:disabled { +} +``` + +Structural pseudo-classes описывают положение среди siblings: + +```css +li:first-child { +} +li:nth-child(odd) { +} +``` + +Functional pseudo-classes могут выражать более сложные relations: + +```css +.card:has(> img) { +} +:is(h1, h2, h3) > a { +} +button:not(:disabled) { +} +``` + +В отличие от pseudo-element, pseudo-class **не создает и не адресует отдельную часть render tree** — она уточняет, когда +сам element совпадает с selector. + +Важно помнить specificity. Большинство pseudo-classes дают вес class selector. Но есть специальные rules: + +- `:where(...)` всегда имеет нулевую specificity; +- `:is(...)`, `:not(...)` и `:has(...)` сами не добавляют обычный class-weight — итоговый вклад определяется наиболее + специфичным selector из их аргументов. + +Например: + +```css +:where(.dialog, .popover) button { + font: inherit; +} +``` + +удобен для low-specificity base rules, которые потом легко переопределять. + +Accessibility edge case: `:hover` нельзя считать единственным способом открыть важное управление, потому что +touch/keyboard users могут не иметь hover. Для keyboard focus обычно нужен `:focus-visible`/`:focus-within` или явное +application state. + +На интервью: **pseudo-class выбирает существующий element по состоянию/структуре/отношению; pseudo-element выбирает +часть formatting/render structure**. @@ -2433,9 +2762,59 @@ p:nth-child(2) выберет p, только если он второй child **Полный ответ** -`:nth-child()` считает элемент среди всех siblings, а `:nth-of-type()` — только среди siblings того же tag name. -Например, `p:nth-child(2)` выберет `p`, только если он второй child вообще, а `p:nth-of-type(2)` выберет второй `p`. -Разница важна, когда структура содержит смешанные элементы. +Обе pseudo-classes используют формулу `An+B`, но считают разные наборы siblings. + +`p:nth-child(2)` означает: **element должен быть `p` и одновременно вторым child среди всех element siblings**. + +```html +
+

Title

+

First paragraph

+

Second paragraph

+
+``` + +```css +p:nth-child(2) { + color: red; +} +``` + +Выберет `First paragraph`, потому что этот `p` — второй child вообще. + +`p:nth-of-type(2)` сначала рассматривает siblings с тем же type selector `p` и выбирает второй из них: + +```css +p:nth-of-type(2) { + color: blue; +} +``` + +Здесь будет выбран `Second paragraph`. + +Поэтому эти selectors легко перепутать, если между одинаковыми tags добавляется другой element: `:nth-child()` реагирует +на общую sibling structure, `:nth-of-type()` — на позицию среди того же element type. + +У современного `:nth-child()` есть дополнительная форма `of `: + +```css +tr:nth-child(even of :not([hidden])) { + background: var(--stripe); +} +``` + +Она сначала фильтрует siblings по selector list, а затем применяет `An+B`. Это полезно для zebra striping, когда часть +rows скрыта: counting идет только по видимому subset. + +Это не делает `:nth-of-type()` ненужным. `:nth-of-type()` лаконично выражает именно counting одного tag type; +`:nth-child(... of S)` умеет считать произвольный filtered set, например `.item:not(.disabled)`. + +При динамическом DOM position пересчитывается автоматически, поэтому selector может начать matching другой element после +insert/remove sibling. Если выбор должен отражать business identity, positional pseudo-class не заменяет semantic +class/data attribute. + +На интервью: **`nth-child` считает общий или явно отфильтрованный sibling set, `nth-of-type` — siblings того же element +type; сначала определите, какой набор вы вообще хотите нумеровать**.