Перейти к основному содержимому

Функции в CSS

Разработчику Архитектору

Перед чтением: функции в коде — общая модель имени, аргументов и вызова; в CSS те же скобки (), но семантика делится на значения свойств и селекторы.


Две семьи «функций»

В CSS слово «функция» встречается в двух разных местах. Синтаксис похож (имя(аргументы)), роль разная.

СемьяГде пишетсяПримерыРезультат
Функции значенийВнутри значения свойстваcalc(), var(), rgb(), clamp()Число, цвет, длина, угол, градиент и т.д.
Функциональные псевдоклассыВ селекторе:is(), :where(), :has(), :not()Уточнение, какие элементы попадут под правило
/* функция значения — вычисляет ширину */
.card {
width: min(100%, 24rem);
padding: calc(var(--space) * 2);
color: color-mix(in oklch, var(--brand) 80%, white);
}

/* функция в селекторе — выбирает элементы */
.card:has(img) {
grid-template-columns: 120px 1fr;
}

:where(h1, h2, h3) {
line-height: 1.2;
}

Подробнее про :is(), :where(), :has() — в отдельной главе Селекторы :is, :where и :has. Ниже — обе семьи в одном обзоре с упором на вызов, встроенный набор и способы «своих» вычислений.

Полный перечень в табличном виде — в справочнике CSS.


Как объявлять и вызывать

Встроенные функции значений

Объявлять их в CSS нельзя — они заданы спецификацией. Вызывают прямо в значении свойства:

.sidebar {
width: clamp(12rem, 20vw, 18rem);
gap: calc(1rem + 2px);
background: linear-gradient(180deg, #1e293b, #0f172a);
filter: blur(0);
transition-timing-function: cubic-bezier(0.4, 0, 0.6, 1);
}

Общий синтаксис:

имя-функции( аргумент1 [, аргумент2 …] )
  • Имя без двоеточия в начале (calc, var, rgb).
  • Аргументы разделяются запятойcalc() для + и - вокруг операндов часто нужны пробелы).
  • Вложенность разрешена: calc(min(100%, 400px) - 1rem).

Функциональные псевдоклассы

Пишутся в селекторе, с двоеточием:

:where(.btn, button) {}
form:has(:invalid) {}

Их тоже нельзя «объявить» в таблице стилей — только вызывать с готовыми селекторами внутри скобок.


Свои вычисления — что доступно

ПодходЧто это«Своя функция»?
Custom properties --x + var()Именованное значение, подстановка в каскадеИменованный параметр, вызов через var(--x)
@propertyТип и начальное значение переменнойСтрогая переменная, анимация чисел/цветов
Препроцессор (Sass, Less)@function → компиляция в CSSДа, но только на этапе сборки
CSS Houdinipaint(), @function (черновик)Ограниченно, в основном Chromium
PostCSS-плагинПодстановка на сборкеЛюбая логика, которую напишет плагин

В чистом CSS нет аналога function myFn(a, b) { return a + b; } для произвольных значений (кроме экспериментов и Houdini). Паттерн «своя функция» — это композиция var() + calc() / color-mix() и дизайн-токены на :root.

См. Переменные в CSS и раздел про @property там же.


Математические функции

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


calc()

Складывает, вычитает, умножает и делит совместимые единицы (px, %, rem, vw, var() и др.).

.hero {
/* 50% минус половина gap в flex-сетке */
width: calc(50% - 0.5rem);
min-height: calc(100vh - var(--header-height));
}

Правила:

  • После + и - нужны пробелы с обеих сторон.
  • * и / могут обходиться без пробелов у числа: calc(100% / 3).
  • Внутри calc() можно вызывать min(), max(), clamp().

min(), max(), clamp()

.fluid-type {
/* не меньше 1rem, не больше 2.5rem, между ними — 4vw */
font-size: clamp(1rem, 2vw + 0.5rem, 2.5rem);
}

.dialog {
width: min(90vw, 32rem);
max-height: max(40vh, 20rem);
}

clamp(MIN, VAL, MAX) эквивалентно max(MIN, min(VAL, MAX)).


round(), mod(), rem() (CSS Values Level 4)

Округление и остаток от деления для сеток без «дробных пикселей»:

.tiles {
--cols: 3;
width: round(down, 100%, calc(100% / var(--cols)));
}

Поддержка в браузерах растёт; для продакшена проверяйте caniuse.


Тригонометрия и гипотенуза

sin(), cos(), tan(), asin(), acos(), atan(), atan2(), hypot(), pow(), sqrt(), log(), exp() — для анимаций по кругу, диаграмм, offset-path:

.orbit-dot {
offset-path: path("M 50 50");
offset-distance: calc(sin(45deg) * 100%);
}

Цветовые функции

ФункцияНазначение
rgb(), hsl(), hwb()Классические модели; современный синтаксис без запятых: rgb(255 0 0 / 0.5)
lab(), lch(), oklab(), oklch()Перцептивно равномерные пространства
color()Произвольное цветовое пространство, например display-p3
color-mix()Смесь цветов в заданном цветовом пространстве
light-dark()Светлая/тёмная пара в одном объявлении (где поддерживается)

color-mix() — смешивание без препроцессора

color-mix() позволяет смешивать два (и в новых браузерах — больше) цвета в любом цветовом пространстве. Оттенки, затемнения и динамические палитры можно строить прямо в CSS через var() — без Sass/Less и без ручного подбора hex.

Базовый синтаксис:

color-mix(in <colorspace>, <color1> [<percentage>], <color2> [<percentage>])

Если проценты не указаны, цвета смешиваются 50/50.

Полярные пространства (hsl, lch, oklch) — удобны, когда важен оттенок:

color-mix(in hsl, hsl(200 50% 80%), coral);
color-mix(in hsl, hsl(200 50% 80%) 20%, coral 80%);

Прямоугольные (srgb, lab, oklab) — предсказуемое смешение по каналам:

color-mix(in srgb, plum, #123456);
color-mix(in lab, plum 60%, #123456 50%);

Интерполяция оттенка — в lch/hsl можно задать, как «обходить» цветовой круг:

color-mix(in lch increasing hue, hsl(200deg 50% 80%), coral);
color-mix(in lch longer hue, hsl(200deg 50% 80%) 44%, coral 16%);

Несколько цветов (где поддерживается):

color-mix(in oklab, teal, olive, blue);
color-mix(in oklab, teal 20%, olive 30%, blue 50%);

Типичные задачи на токенах:

:root {
--brand: oklch(0.55 0.15 250);
--surface: color-mix(in oklch, var(--brand) 12%, white);
--text-on-brand: color-mix(in srgb, var(--brand) 90%, black);
--brand-hover: color-mix(in oklch, var(--brand) 85%, black);
}

.badge {
background: light-dark(#f1f5f9, #1e293b);
}
ЗадачаПример
Осветлить фонcolor-mix(in oklch, var(--brand) 15%, white)
Затемнить hovercolor-mix(in oklch, var(--brand) 80%, black)
Полупрозрачная обводкаcolor-mix(in srgb, var(--brand) 40%, transparent)
Палитра из одного --brandсерия --brand-100--brand-900 через разные доли white/black

Для UI чаще берут oklch или oklab — смесь выглядит равномернее, чем в srgb. Для точного совпадения с legacy-hex иногда достаточно srgb.

color-contrast() и color-adjust() — из черновиков Color Level 5; перед использованием смотрите поддержку.


Раскладка, формы и фигуры

Grid и Flex

.grid-auto {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr));
gap: fit-content(2rem);
}
ФункцияРоль
repeat()Повтор трека repeat(3, 1fr) или repeat(auto-fit, …)
minmax()Диапазон размера трека
fit-content()«По содержимому», но с потолком

aspect-ratio и размеры

.video-wrap {
aspect-ratio: 16 / 9;
width: min(100%, 960px);
}

Изображения, градиенты, маски

.banner {
background-image:
linear-gradient(135deg, transparent 40%, rgba(0, 0, 0, 0.4)),
url("/img/hero.webp");
mask-image: radial-gradient(circle at center, #000 60%, transparent 100%);
}

.icon {
background: conic-gradient(from 0deg, #3b82f6, #8b5cf6, #3b82f6);
}
ФункцияПримечание
url()Файл, data-URI, фрагмент SVG
image-set()Набор источников под плотность экрана
cross-fade()Плавная смена двух изображений
paint()Houdini — кастомная отрисовка фона

Фильтры, трансформации, анимация

.modal-backdrop {
backdrop-filter: blur(8px) brightness(0.9);
}

.card:hover {
transform: translateY(-4px) rotate(1deg);
filter: drop-shadow(0 8px 16px rgb(0 0 0 / 0.12));
}

.spinner {
animation: spin 1s linear infinite;
animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1);
}

@keyframes steps-demo {
from {
opacity: 1;
}
to {
opacity: 0.5;
}
}

.stepped {
animation-timing-function: steps(4, end);
}
ГруппаПримеры
Transformtranslate(), rotate(), scale(), matrix(), translate3d()
Filterblur(), brightness(), contrast(), grayscale()
Easingcubic-bezier(), steps(), linear() (набор точек в Level 2)

var(), env(), attr()

var() — вызов custom property

:root {
--space: 0.5rem;
--radius: calc(var(--space) * 2);
}

.button {
padding: var(--space) calc(var(--space) * 3);
border-radius: var(--radius, 6px); /* fallback */
}

Второй аргумент var(--name, fallback) обязателен, если переменная может отсутствовать. Fallback может быть другим var().


env() — системное окружение

.app-bar {
padding-top: env(safe-area-inset-top, 0px);
padding-bottom: env(keyboard-inset-height, 0px);
}

attr() — данные из HTML

Сегодня надёжно в content и счётчиках; в значениях длины/цвета — по мере внедрения Typed OM:

[data-label]::before {
content: attr(data-label);
}

/* будущее / ограниченная поддержка */
.thumb {
width: attr(data-width px, 64px);
}

Счётчики, шрифты, прочее

body {
counter-reset: section;
}

h2::before {
counter-increment: section;
content: counters(section, ".") ". ";
}

.code {
font-family: ui-monospace, monospace;
font-feature-settings: "liga" 0;
}
ФункцияГде
counter(), counters()Нумерация разделов, оглавление
symbols()Маркеры списков из символов
local()Локальный шрифт в @font-face

Функции в селекторах — кратко

Они не возвращают значение — фильтруют дерево DOM.

ПсевдоклассСпецифичностьТипичная задача
:is(a, b)Как у самого «тяжёлого» аргументаСократить длинный список селекторов
:where(a, b)0Базовые стили дизайн-системы, легко перебить классом
:has(S)Как у :has + внутренние селекторыСтили родителя по потомкам и соседям
:not(S)Как у :not + аргументИсключения

Комбинация из реального UI (индикатор «печатает» у последнего узла markdown):

@keyframes pulse-dot {
50% {
opacity: 0.5;
}
}

/* последний «лист» контента в блоке со статусом running */
.aui-md[data-status="running"]:empty::after,
.aui-md[data-status="running"] > :where(:not(ol):not(ul):not(pre)):last-child::after,
.aui-md[data-status="running"] > pre:last-child code::after,
.aui-md[data-status="running"] :where(ol, ul):last-child > li:last-child:not(:has(> li))::after {
content: "\25cf";
margin-left: 0.25rem;
animation: pulse-dot 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
font-family: ui-sans-serif, system-ui, sans-serif;
}

Здесь :where() снижает вес селектора, :has() отсекает вложенные списки, cubic-bezier() задаёт ритм анимации — три разных «функции», одна задача.

Разбор и осторожность с производительностью :has() — в 114.md.


Как сделать «свою» логику

1. Токены и композиция (основной путь)

:root {
--space-unit: 4px;
--space-md: calc(var(--space-unit) * 4);
--space-lg: calc(var(--space-unit) * 6);
--focus-ring: 0 0 0 3px color-mix(in srgb, var(--brand) 40%, transparent);
}

.focusable:focus-visible {
box-shadow: var(--focus-ring);
}

Меняете --space-unit — пересчитывается вся шкала.


2. @property — типизированная переменная

@property --progress {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}

.bar::before {
width: var(--progress);
transition: --progress 0.3s ease;
}

Позволяет анимировать custom properties там, где браузер понимает зарегистрированный тип.


3. Sass — настоящая @function

@function space($n) {
@return $n * 4px;
}

.card {
padding: space(3); /* → 12px после компиляции */
}

В браузер уходит уже обычный CSS без @function.


4. Houdini — paint() и черновик @function

// registerPaint в отдельном worklet-файле
CSS.paintWorklet.addModule("checkerboard.js");
.cell {
background: paint(checkerboard);
}

Модуль CSS Functions описывает @function для выражений в самом CSS; на момент написания статьи это эксперимент, а не база для продакшена.


Практические сценарии

Адаптивная типографика без десятка брейкпоинтов

html {
font-size: clamp(15px, 0.9rem + 0.35vw, 18px);
}

h1 {
font-size: clamp(1.75rem, 1.2rem + 2.5vw, 3rem);
}

Тема через color-mix и prefers-color-scheme

:root {
--bg: #f8fafc;
--fg: #0f172a;
}

@media (prefers-color-scheme: dark) {
:root {
--bg: #0f172a;
--fg: #e2e8f0;
}
}

.card {
background: color-mix(in oklch, var(--bg) 92%, var(--fg));
color: var(--fg);
}

Связка с доступностью и медиа-настройками.


Форма без JS-классов на обёртке

fieldset:has(:user-invalid) {
border-color: #b91c1c;
}

fieldset:has(:user-valid) legend {
color: #15803d;
}

Сброс типографики в @layer

@layer base {
:where(h1, h2, h3, p) {
margin-block: 0 0.5em;
}
}

Сочетание с каскадом и @layer.


Карточка с превью только при наличии картинки

.product:has(img) {
display: grid;
grid-template-columns: 96px 1fr;
gap: 1rem;
}

Шпаргалка по категориям

КатегорияВызов в значенииВызов в селекторе
Числа и размерыcalc, min, max, clamp, round
Цветrgb, hsl, oklch, color-mix
Сеткаrepeat, minmax, fit-content
Картинкиurl, linear-gradient, image-set
Движениеcubic-bezier, steps
Переменныеvar, env, attr
Логика выбора:is, :where, :has, :not

Частые ошибки

СимптомПричинаЧто сделать
calc() игнорируетсяНет пробелов у + / -calc(100% - 1rem)
var() пустойОпечатка в --name или область видимостиDevTools → Computed → custom properties
Перебить :where() не выходитКонфликт с !important или порядком слоёвПроверить @layer и источник
Тормоза при :has()Слишком широкий контекст (body:has(...))Сузить до компонента .list:has(.active)
«Функция» из Sass не в браузереЗабыли сборкуСмотреть скомпилированный CSS

Краткий итог

Функции значений вызываются в свойствах (calc(), var(), color-mix() и десятки других) и возвращают конкретное значение для движка стилей. Функциональные псевдоклассы (:is, :where, :has, :not) работают в селекторах и решают, к кому применить правило. Свои вычисления в стандартном CSS строят из custom properties, @property, иногда препроцессора или Houdini; произвольных myFunc() в чистом CSS пока нет. Для углубления — переменные, селекторы :is/:where/:has, справочник.


См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").

Освоение главы0%