Pantograph Layout

Вёрстка для дизайнеров и фронтендеров

Макет не перестраивается. Он масштабируется.

Внутри брейкпоинта страница на любой ширине выглядит как макет в Figma: те же пропорции, переносы строк и композиция. Меняется только масштаб.

Двигайте ползунок или смотрите анимацию

Ширина окна412 px
БрейкпоинтМобильный · 375
1rem1rem = 1,099 px
Шрифт 24px из макета26,4 px

01 · Суть

Чем Pantograph отличается от обычной адаптивной вёрстки

Возьмём мобильный макет на 375 px и откроем страницу на телефоне шириной 480 px.

375 px

Макет в FigmaТочка отсчёта.

480 px

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

480 px

PantographВсё увеличено в 480 / 375 = 1,28 раза. Это тот же макет, только крупнее.

Адаптивная вёрстка

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

Pantograph layout

Размеры в rem, а 1rem зависит от ширины окна. Любая ширина внутри брейкпоинта — это макет в масштабе.

02 · Алгоритм

Один CSS и одна формула

CSS задаёт брейкпоинты и считает font-size для html. Вся вёрстка пишется в rem, поэтому растёт и уменьшается вслед за ним.

1

Брейкпоинты: три макета — три диапазона

Для каждого диапазона ширин задаются три числа. --balance-point — точка баланса: ширина макета в Figma, на которой 1rem ровно 1px. --min-vw и --max-vw — края диапазона. Числа записаны без единиц, чтобы их можно было делить друг на друга.

Ширина окнаМакет1rem на краях
до 499 px3750,853 → 1,333 px
500–1151 px7680,651 → 1,5 px
от 1152 px14400,8 → 1,111 px

Первый медиа-запрос начинается с 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;
  }
}
2

Считаем размер 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 на странице.

3

Если сократить формулу

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

Посчитайте на своей ширине

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

412 px Мобильный · макет 375 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
Итог
1rem = 1,099 px · 24rem = 26,4 px
4

Что происходит на границах

Смена брейкпоинта

На 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. Мелкий текст планшетного макета проверяйте на этой ширине.

ОкноМакетМасштабТекст 14px
320375× 0,85311,9 px
499375× 1,33118,6 px
500768× 0,6519,1 px
1151768× 1,49921,0 px
11521440× 0,811,2 px
1600+1440× 1,11115,6 px

Если макет нарисован не на ширине брейкпоинта

Другая ширина макета

Десктоп нарисован на 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 · Фронтендерам

Как верстать

1

Добавьте 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);
}
2

Числа из Figma — в rem

В макете 24px — в коде 24rem. Пересчитывать под каждую ширину не нужно, масштаб меняется сам.

3

Медиа-запросы — для смены раскладки

Они нужны, если меняется сетка или размеры в макетах разные. Границы — те же 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 целиком и разбор формулы
  • Что делать с макетами не на ширине брейкпоинта
  • Подводные камни и чек-лист крайних ширин
Скачать SKILL.md
SKILL.md
---
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 или в масштаб макета.