Вёрстка для дизайнеров и фронтендеров
Макет не перестраивается. Он масштабируется.
Внутри брейкпоинта страница на любой ширине выглядит как макет в Figma: те же пропорции, переносы строк и композиция. Меняется только масштаб.
Двигайте ползунок или смотрите анимацию
01 · Суть
Чем Pantograph отличается от обычной адаптивной вёрстки
Возьмём мобильный макет на 375 px и откроем страницу на телефоне шириной 480 px.
375 px
480 px
480 px
Адаптивная вёрстка
Размеры в px фиксированы. Между брейкпоинтами блоки растягиваются и сжимаются, поэтому каждую промежуточную ширину приходится проверять отдельно.
Pantograph layout
Размеры в rem, а 1rem зависит от ширины окна. Любая ширина внутри брейкпоинта — это макет в масштабе.
02 · Алгоритм
Один CSS и одна формула
CSS задаёт брейкпоинты и считает font-size для html. Вся вёрстка пишется в rem, поэтому растёт и уменьшается вслед за ним.
Брейкпоинты: три макета — три диапазона
Для каждого диапазона ширин задаются три числа. --balance-point — точка баланса: ширина макета в Figma, на которой 1rem ровно 1px. --min-vw и --max-vw — края диапазона. Числа записаны без единиц, чтобы их можно было делить друг на друга.
Первый медиа-запрос начинается с 0 px, но расчёт идёт от 320: на более узких экранах 1rem не опускается ниже 0,853 px.
/* Breakpoints */
@media (min-width: 0px) {
:root {
--min-vw: 320;
--max-vw: 500;
--balance-point: 375;
}
}
@media (min-width: 500px) {
:root {
--min-vw: 500;
--max-vw: 1152;
--balance-point: 768;
}
}
@media (min-width: 1152px) {
:root {
--min-vw: 1152;
--max-vw: 1600;
--balance-point: 1440;
}
}
Считаем размер 1rem
CSS строит прямую: при ширине окна min-vw размер 1rem минимальный, при max-vw — максимальный, между ними растёт равномерно. Разберём по частям. Числа в примерах — для планшетного диапазона.
:root {
--min-font-size: calc(var(--min-vw) / var(--balance-point));
--max-font-size: calc(var(--max-vw) / var(--balance-point));
Размер 1rem на краях диапазона
На ширине макета 1rem = 1px. На краю диапазона он во столько раз меньше или больше, во сколько край меньше или больше макета.
500 / 768 = 0,651 px
1152 / 768 = 1,5 px
--diff-font-size: calc(var(--max-font-size) - var(--min-font-size));
--diff-vw: calc(var(--max-vw) - var(--min-vw));
--slope: calc(var(--diff-font-size) / var(--diff-vw));
Наклон прямой
На сколько px вырастает 1rem с каждым пикселем ширины окна.
(1,5 − 0,651) / (1152 − 500)
= 0,001302 = 1 / 768
--max-font-size-px: calc(var(--max-font-size) * 1px);
--min-font-size-px: calc(var(--min-font-size) * 1px);
--min-vw-px: calc(var(--min-vw) * 1px);
--fluid-font-size-px: calc(
var(--slope) * (100vw - var(--min-vw-px)) + var(--min-font-size-px)
);
Уравнение прямой
Прямая через точку (min-vw; min-font-size) с найденным наклоном. 100vw — текущая ширина окна. Умножение на 1px переводит безразмерные числа в пиксели.
Окно 900 px:
0,001302 × (900 − 500) + 0,651 = 1,172 px
--clamp-scaling-fs: clamp(
var(--min-font-size-px),
var(--fluid-font-size-px),
var(--max-font-size-px)
);
}
html {
font-size: var(--clamp-scaling-fs);
}
Ограничение и применение
clamp не выпускает размер за края диапазона. Внутри диапазона он ничего не меняет, а срабатывает шире 1600 px и уже 320 px. Результат становится font-size у html, и от него считается каждый rem на странице.
Если сократить формулу
slope = (max-font-size − min-font-size)
/ (max-vw − min-vw)
= 1 / balance-point
fluid = slope × (100vw − min-vw)
+ min-vw / balance-point
= 100vw / balance-point
slope = (max-font-size − min-font-size) / (max-vw − min-vw)
= 1 / balance-point
fluid = slope × (100vw − min-vw) + min-vw / balance-point
= 100vw / balance-point
1rem = ширина окна ÷ ширина макета
Это всё, что делает алгоритм. Остальные переменные нужны, чтобы CSS мог посчитать формулу, а clamp — чтобы значение не вышло за края диапазона.
- 375 px → 1 px
- 412 px → 1,099 px
- 480 px → 1,28 px
- 1024 px → 1,333 px
Посчитайте на своей ширине
Значения считаются для ширины из демо наверху. Выберите ширину здесь или вернитесь к ползунку.
- Переменные
- min-vw 320 · max-vw 500 · balance-point 375
- min-font-size
- 320 / 375 = 0,853 px
- max-font-size
- 500 / 375 = 1,333 px
- slope
- (1,333 − 0,853) / (500 − 320) = 0,002667
- fluid-font-size
- 0,002667 × (412 − 320) + 0,853 = 1,099 px
- clamp
- clamp(0,853, 1,099, 1,333) = 1,099 pxОкно шире 1600 px: сработало ограничение сверху
- Итог
- 1rem = 1,099 px · 24rem = 26,4 px
Что происходит на границах
Смена брейкпоинта
На 1151 px планшетный макет увеличен в 1,5 раза. На 1152 px включается десктопный в масштабе 0,8. Раскладка сменилась, и новый макет начинает с меньшего масштаба. Это ожидаемый скачок.
Шире 1600 px
clamp останавливает рост: 1rem держится на 1,111 px. Шрифты и отступы дальше не увеличиваются, лишняя ширина уходит в поля.
Уже 320 px
1rem не опускается ниже 0,853 px. Мобильный макет перестаёт уменьшаться.
03 · Дизайнерам
Что это значит для дизайнера
Рисуйте три макета: 375, 768 и 1440 px
Каждый макет покрывает свой диапазон ширин. Промежуточные ширины рисовать не нужно: это тот же макет в масштабе.
Проверяйте крайние ширины
Сильнее всего макет уменьшается на 500 px: планшетный макет там в масштабе 0,65. Мелкий текст планшетного макета проверяйте на этой ширине.
Если макет нарисован не на ширине брейкпоинта
Другая ширина макета
Десктоп нарисован на 1200, мобильный на 360? Пересчитывать числа руками не нужно. Укажите ширину своего макета в --balance-point, и 1rem снова равен 1px вашего макета. Края диапазонов и медиа-запросы не меняются.
/* after the CSS above */
@media (min-width: 0px) {
:root {
--balance-point: 360;
}
}
@media (min-width: 1152px) {
:root {
--balance-point: 1200;
}
}
Побочный эффект: на ширине брейкпоинта 1rem уже не равен 1px (на 1440 это 1,2 px). Точкой отсчёта становится макет.
Нет планшетного макета
Возьмите десктопную раскладку в масштабе около 0,75: для планшетного диапазона --balance-point = 768 / 0,75 = 1024. Честные 1200 дают на 768 px масштаб 0,64 и текст около 15 px — мелко.
Чтобы контент не вылезал за 768 px, ширины контейнеров и колонок задайте в процентах. В rem оставьте шрифты, отступы, высоты и радиусы. Крупные иллюстрации уменьшите или сдвиньте отдельным правилом.
@media (min-width: 500px) {
:root {
--balance-point: 1024; /* 768 / 0.75 */
}
}
Запишите принятые масштабы в README проекта, чтобы правило не потерялось.
04 · Фронтендерам
Как верстать
Добавьте CSS
Один раз, глобально, до стилей страницы. Он задаёт font-size для html, поэтому влияет на каждый rem. Если так свёрстаны не все страницы, подключайте CSS только на нужных.
/* Breakpoints */
@media (min-width: 0px) {
:root {
--min-vw: 320;
--max-vw: 500;
--balance-point: 375;
}
}
@media (min-width: 500px) {
:root {
--min-vw: 500;
--max-vw: 1152;
--balance-point: 768;
}
}
@media (min-width: 1152px) {
:root {
--min-vw: 1152;
--max-vw: 1600;
--balance-point: 1440;
}
}
/* Scaling */
:root {
--min-font-size: calc(var(--min-vw) / var(--balance-point));
--max-font-size: calc(var(--max-vw) / var(--balance-point));
--diff-font-size: calc(var(--max-font-size) - var(--min-font-size));
--diff-vw: calc(var(--max-vw) - var(--min-vw));
--slope: calc(var(--diff-font-size) / var(--diff-vw));
--max-font-size-px: calc(var(--max-font-size) * 1px);
--min-font-size-px: calc(var(--min-font-size) * 1px);
--min-vw-px: calc(var(--min-vw) * 1px);
--fluid-font-size-px: calc(
var(--slope) * (100vw - var(--min-vw-px)) + var(--min-font-size-px)
);
--clamp-scaling-fs: clamp(
var(--min-font-size-px),
var(--fluid-font-size-px),
var(--max-font-size-px)
);
}
html {
font-size: var(--clamp-scaling-fs);
}
Числа из Figma — в rem
В макете 24px — в коде 24rem. Пересчитывать под каждую ширину не нужно, масштаб меняется сам.
Медиа-запросы — для смены раскладки
Они нужны, если меняется сетка или размеры в макетах разные. Границы — те же 500 и 1152 px, что в CSS.
Пример
/* mobile mockup, 375 */
.hotels {
display: grid;
gap: 12rem;
padding: 0 20rem;
}
.hotels__title {
font-size: 21rem;
}
/* tablet mockup, 768 */
@media (min-width: 500px) {
.hotels {
grid-template-columns: repeat(2, 1fr);
gap: 20rem;
padding: 0 32rem;
}
.hotels__title {
font-size: 28rem;
}
}
/* desktop mockup, 1440 */
@media (min-width: 1152px) {
.hotels {
grid-template-columns: repeat(4, 1fr);
gap: 24rem;
padding: 0 80rem;
}
.hotels__title {
font-size: 36rem;
}
}
Mobile first
Сначала мобильная версия без медиа-запросов, потом планшет и десктоп.
Числа — прямо из макетов
Заголовок списка отелей в демо-макетах: 21 px на мобильном, 28 px на планшете, 36 px на десктопе. В коде те же числа, только в rem.
Между брейкпоинтами кода нет
На 412 px заголовок будет 21 × 1,099 = 23,1 px, на 900 px — 28 × 1,172 = 32,8 px. Это делает CSS из первого шага.
05 · Скилл для AI
Научите AI-агента верстать так же
Скилл — файл SKILL.md на английском: инструкция, которую AI-агент читает перед работой. Положите его в папку скиллов агента, и он будет верстать по этим правилам.
Что внутри
- Правило 1rem = 1px макета
- Брейкпоинты 375 / 768 / 1440 и границы 500 и 1152 px
- CSS целиком и разбор формулы
- Что делать с макетами не на ширине брейкпоинта
- Подводные камни и чек-лист крайних ширин
---
name: pantograph-layout
description: Pantograph layout: scale a page with
the viewport so it matches the Figma mockup at any
width inside a breakpoint (1rem = 1px of mockup, …
---
# Pantograph layout
## The idea in one line
html font-size = viewport width / mockup width.
At the mockup width 1rem is exactly 1px of the mockup.
## Breakpoints
| up to 499px | 375 | 0.853px → 1.333px |
| 500–1151px | 768 | 0.651px → 1.5px |
| 1152px+ | 1440 | 0.8px → 1.111px |
## The CSS
## How it works
## Writing styles
## Mockup not at the breakpoint width
## Pitfalls
## Review checklist
06 · Ограничения
Где Pantograph ломается
Анимации в px
JS-библиотеки анимаций, например GSAP, выставляют координаты и размеры в px напрямую. Такие значения не масштабируются вместе с окном. После анимации пересчитайте итоговые позиции и размеры в rem.
Свои границы брейкпоинтов
Если сетка переключается не на 500 и 1152 px, а масштаб — на них, в промежутке планшетная сетка встретится с мобильным масштабом. Берите границы из CSS.
Чужой код в rem
1rem на странице около 1 px, а не 16. Сторонние виджеты и стили, где rem рассчитан на 16 px, станут крошечными. Их размеры переводите в px или в масштаб макета.