Skip to content

Instantly share code, notes, and snippets.

@vnphanquang
Last active April 29, 2024 04:39
Show Gist options
  • Save vnphanquang/6a73786eecfbfedb6c4be64a09517fb5 to your computer and use it in GitHub Desktop.
Save vnphanquang/6a73786eecfbfedb6c4be64a09517fb5 to your computer and use it in GitHub Desktop.
Fluid Calculator (for Typography)
/**
* @public
* @typedef {'rem' | 'px'} Unit
*/
/**
* @public
* @typedef {`${number}${Unit}`} Length
*/
/**
* @public
* @typedef ClampFluidConfig
* @property {[Length, Length]} screen_range
* @property {[Length, Length]} value_range
* @property {number} root_font_size_px
*/
/**
* @public
* generate a {@link https://developer.mozilla.org/en-US/docs/Web/CSS/clamp | CSS clamp} expression for scaling value between a screen range
*
* @param {ClampFluidConfig} config
*/
export function clamp_fluid(config) {
const {
screen_range: [screen_min, screen_max],
value_range: [value_min, value_max],
root_font_size_px,
} = config;
let screen_min_px = resolve_length_to_px(screen_min, root_font_size_px);
let screen_max_px = resolve_length_to_px(screen_max, root_font_size_px);
let screen_delta_px = screen_max_px - screen_min_px;
let value_min_px = resolve_length_to_px(value_min, root_font_size_px);
let value_max_px = resolve_length_to_px(value_max, root_font_size_px);
let value_delta_px = value_max_px - value_min_px;
const vw = value_delta_px * 100 / screen_delta_px;
const rem = (value_min_px - (screen_min_px * value_delta_px / screen_delta_px)) / root_font_size_px;
if (Math.abs(rem) < 1) {
console.warn(`The calculated rem value "${rem}" has its absolute value less than 1. It should be at least 1rem so that users can scale it effectively. Try changing the input config`);
}
const min = format_length(value_min_px / root_font_size_px) + 'rem';
const max = format_length(value_max_px / root_font_size_px) + 'rem';
const ideal = rem > 0
? `${format_length(vw)}vw + ${format_length(rem)}rem`
: `${format_length(vw)}vw - ${format_length(Math.abs(rem))}rem`
return `clamp(${min}, ${ideal}, ${max})`;
}
/**
* @internal
* @param {Length} value
* @param {number} root_font_size_px
*/
function resolve_length_to_px(length, root_font_size_px) {
if (length.includes('rem')) {
return parseFloat(length) * root_font_size_px;
}
return parseFloat(length);
}
/**
* @param {number} length
*/
function format_length(length) {
if (Math.round(length) !== length) {
return length.toFixed(3);
}
return length.toString();
}
// test
const config = {
screen_range: ['400px', '1400px'],
value_range: ['2rem', '4.5rem'],
root_font_size_px: 16,
};
const clamped = clamp_fluid(config);
// assert clamped = clamp(2rem, 4vw + 1rem, 4.5rem)
console.log(clamped);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment