Генерация CSS fluid-значений на базе clamp()
Один и тот же компонент сегодня открывают мобильном, завтра - на огромном мониторе. Если размеры задавать голыми пикселями и медиазапросами, получается каша из магических чисел: шрифты, отступы и кнопки растут по разным формулам, поддерживать это больно.
В фронтенд‑сообществе уже давно прижился подход с fluid‑логикой, когда размеры (шрифтов, отступом и т.д.) указываются не жесткими значениями а через CSS‑функцию clamp(), которая позволяет задать минимальный, максимальный размер и формулу роста между ними. Примерно так: clamp(1rem, 0.875rem + 1.5vw, 2rem). Такой подход позволяет избавиться от множества медиазапросов.
Для вычисления таких значение можно использовать онлайн калькуляторы, но это не всегда удобно и не централизовано. Поэтому я написал небольшой PHP‑класс, который генерирует готовые CSS‑переменные с такими fluid‑значениями на основе понятных параметров.
В WordPress есть похожая функция для вычисления fluid‑значений типографики на основе настроек темы: wp_get_computed_fluid_typography_value()
Класс
GitHub<?php
/**
* Generates ready-to-use CSS variables with fluid values via `clamp()`, so that sizes in `rem`
* smoothly change between two viewport widths and do not exceed specified minimums and maximums.
*
* Usage example:
*
* $formatter = new Fluid_CSSVar_Generator(
* 375, // minimum viewport width in pixels
* 1280, // maximum viewport width in pixels
* 150, // scaling factor in percent
* 0 // minimum size in pixels
* );
* echo $formatter->generate( [12, 48], 4 );
* // or get single variable:
* echo $formatter->get_css_var( 24 );
*
* @ver 1.0.1
*/
class Fluid_CSSVar_Generator {
public int $root_size_px = 16;
public string $var_pattern = '--fluid{scale}-{px}px';
/**
* @param int $vw_min_width The minimum viewport width in pixels.
* @param int $vw_max_width The maximum viewport width in pixels.
* @param float $scale_percent The scaling factor between minimum and maximum size.
* @param int $min_size_px The minimum size in pixels. Less than this size will not be used.
*/
public function __construct(
private readonly int $vw_min_width = 375,
private readonly int $vw_max_width = 1280,
private readonly int $scale_percent = 150,
private readonly int $min_size_px = 0,
){
( $this->scale_percent <= 100 ) && throw new InvalidArgumentException( '$scale_percent must be greater than 1.' );
( $this->min_size_px < 0 ) && throw new InvalidArgumentException( '$min_size_px cannot be negative.' );
}
/**
* @param array $px_range Minimum and maximum size in pixels at the maximum viewport width.
* @param int $step The step between sizes in pixels.
*/
public function generate( array $px_range, int $step = 1 ): string {
$result = [];
for ( $px = $px_range[0]; $px <= $px_range[1]; $px += $step ) {
$result[] = $this->get_css_var( $px );
}
return implode( "\n", $result );
}
/**
* @param int $px The maximum size in pixels at the maximum viewport width.
*/
public function get_css_var( int $px ): string {
$vw_min_rem = ( $this->vw_min_width / 100 ) / $this->root_size_px;
$vw_max_rem = ( $this->vw_max_width / 100 ) / $this->root_size_px;
$vw_range = $vw_max_rem - $vw_min_rem;
$scale_factor = $this->scale_percent / 100;
$font_max_rem = $px / $this->root_size_px;
$font_min_rem = max( $font_max_rem / $scale_factor, $this->min_size_px / $this->root_size_px );
if ( $font_max_rem <= $font_min_rem ) {
$min = self::format_value( $font_min_rem );
return strtr(
$this->var_pattern . ': {min}rem;',
[
'{scale}' => $this->scale_percent,
'{px}' => $px,
'{min}' => $min,
]
);
}
$font_range = $font_max_rem - $font_min_rem;
$slope = $font_range / $vw_range;
$intercept = $font_min_rem - ( $slope * $vw_min_rem );
return strtr(
$this->var_pattern . ': clamp({min}rem, {intercept}rem + {slope}vw, {max}rem);',
[
'{scale}' => $this->scale_percent,
'{px}' => $px,
'{min}' => self::format_value( $font_min_rem ),
'{intercept}' => self::format_value( $intercept ),
'{slope}' => self::format_value( $slope ),
'{max}' => self::format_value( $font_max_rem ),
]
);
}
private static function format_value( float $value ): string {
return number_format( $value, 3, '.', '' );
}
}
Использование
Параметры конструктора позволяют задать минимальную и максимальную ширину вьюпорта в пикселях,
коэффициент масштабирования (в процентах) и минимальный размер в пикселях (меньше которого значение не опустится).
Метод get_css_var() принимает целевой размер в пикселях (максимальный размер при максимальной ширине вьюпорта)
и возвращает строку с готовым определением CSS‑переменной с использованием функции clamp().
$gen = new Fluid_CSSVar_Generator( 375, // минимальная ширина вьюпорта в пикселях 1280, // максимальная ширина вьюпорта в пикселях 150, // коэффициент масштабирования в процентах 0 // минимальный размер в пикселях ); echo $gen->get_css_var( 24 ); // --fluid150-24px: clamp(1.000rem, 0.793rem + 0.884vw, 1.500rem); $gen = new Fluid_CSSVar_Generator( 375, // минимальная ширина вьюпорта в пикселях 1280, // максимальная ширина вьюпорта в пикселях 200, // коэффициент масштабирования в процентах 12 // минимальный размер в пикселях ); echo $gen->get_css_var( 16 ); // --fluid200-16px: clamp(0.750rem, 0.646rem + 0.442vw, 1.000rem);
Пример с указанием параметров конструктора:
$gen = new Fluid_CSSVar_Generator( 375, 1440, 200 ); echo $gen->get_css_var( 32 ); // --fluid200-32px: clamp(1.000rem, 0.648rem + 1.502vw, 2.000rem);
Пример изменения названия CSS‑переменной:
$gen = new Fluid_CSSVar_Generator(
375, // минимальная ширина вьюпорта в пикселях
1280, // максимальная ширина вьюпорта в пикселях
150, // коэффициент масштабирования
);
$gen->var_pattern = '--my-fluid{scale}-{px}';
echo $gen->get_css_var( 20 ); // --my-fluid150-20: clamp(0.833rem, 0.661rem + 0.737vw, 1.250rem);
Пример генерации группы переменных:
$scale = 130;
$gen = new Fluid_CSSVar_Generator( 375, 1280, $scale, 12 );
$gen->var_pattern = '--fluid{scale}-min12-{px}px';
$gen = new Fluid_CSSVar_Generator( 375, 1280, 150, 2 );
echo "/** Scale $scale%, min 12px */\n";
echo $gen->generate( [10, 30], 2 );
Получим:
/** Scale 130%, min 12px */ --fluid150-10px: clamp(0.417rem, 0.330rem + 0.368vw, 0.625rem); --fluid150-12px: clamp(0.500rem, 0.396rem + 0.442vw, 0.750rem); --fluid150-14px: clamp(0.583rem, 0.462rem + 0.516vw, 0.875rem); --fluid150-16px: clamp(0.667rem, 0.529rem + 0.589vw, 1.000rem); --fluid150-18px: clamp(0.750rem, 0.595rem + 0.663vw, 1.125rem); --fluid150-20px: clamp(0.833rem, 0.661rem + 0.737vw, 1.250rem); --fluid150-22px: clamp(0.917rem, 0.727rem + 0.810vw, 1.375rem); --fluid150-24px: clamp(1.000rem, 0.793rem + 0.884vw, 1.500rem); --fluid150-26px: clamp(1.083rem, 0.859rem + 0.958vw, 1.625rem); --fluid150-28px: clamp(1.167rem, 0.925rem + 1.031vw, 1.750rem); --fluid150-30px: clamp(1.250rem, 0.991rem + 1.105vw, 1.875rem);