Skip to content

Instantly share code, notes, and snippets.

@doiftrue
Last active November 18, 2025 02:42
Show Gist options
  • Select an option

  • Save doiftrue/67b6f488e16ea989be5175678c69536d to your computer and use it in GitHub Desktop.

Select an option

Save doiftrue/67b6f488e16ea989be5175678c69536d to your computer and use it in GitHub Desktop.
<?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, '.', '' );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment