Last active
March 23, 2023 18:18
-
-
Save 13twelve/4cf542d6b92080d691a3e8f7b77a4698 to your computer and use it in GitHub Desktop.
Create responsive image sizes attribute with PHP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Generate responsive image sizes | |
* Takes a sizes object: | |
* | |
* responsiveImageSizes({ | |
* "sm": "100vw", | |
* "md": 4, | |
* "lg": 7, | |
* "xl": "80%", | |
* "xxl": "300px" | |
* }); | |
* | |
* And generates an image `sizes` attribute string, | |
* complete with breakpoints | |
* | |
*/ | |
function responsiveImageSizes($sizes, $feConfig = [], $relativeUnits = true) { | |
// Doc: https://github.com/area17/a17-behaviors/wiki/responsiveImageSizes | |
if (!$feConfig['structure'] || !$feConfig['structure']['columns'] || !$feConfig['structure']['container'] || !$feConfig['structure']['gutters'] || !$feConfig['structure']['gutters']['inner']) { | |
return '100vw'; | |
} | |
// convert to rems | |
$remCalc = function($px) { | |
$rem = floatval($px) / 16; | |
return "{$rem}rem"; | |
}; | |
// | |
$getUnitValue = function($val) { | |
$result = []; | |
if (gettype($val) === 'integer') { | |
$result['value'] = $val; | |
} | |
if (gettype($val) === 'string') { | |
if (strpos($val, 'calc') > -1) { | |
$result['calc'] = $val; | |
} else { | |
$result['value'] = floatval($val); | |
$result['unit'] = trim(substr($val, strlen(''.$result['value']))); | |
$result['unit'] = isset($result['unit']) ? $result['unit'] : null; | |
} | |
} | |
return $result; | |
}; | |
// parse CSS type data from config | |
$cssColumns = $feConfig['structure']['columns']; | |
$cssContainerWidths = $feConfig['structure']['container']; | |
$cssInnerGutters = $feConfig['structure']['gutters']['inner']; | |
// $sizesSet is going to be a complete list of breakpoints with null values | |
// that we'll later update to fill in size at bp | |
$sizesSet = []; | |
// size media query prefixes (except the smallest breakpoint) | |
$breakpointsArr = array_map(fn($val) => floatval($val), $feConfig['structure']['breakpoints']); | |
asort($breakpointsArr); | |
$mqPrefixes = []; | |
foreach ($breakpointsArr as $name=>$size) { | |
$size = $relativeUnits ? $remCalc($size) : "{$size}px"; | |
$mqPrefixes[$name] = (floatval($size) > 0) ? "(min-width: {$size})" : ''; | |
$sizesSet[$name] = null; | |
}; | |
// generate $sizes | |
if (isset($sizes)) { | |
// if a string for $sizes is passed through | |
if (gettype($sizes) === 'string') { | |
return $sizes; | |
} | |
// if an object of $sizes is passed through, convert to an array | |
if (is_array($sizes)) { | |
// merge the objects | |
$sizesSet = array_merge((array) $sizesSet, (array) $sizes); | |
// set up to fill in ALL values for ALL bps | |
$sizesSetKeys = array_keys($sizesSet); | |
$lastKnownSize = ''; | |
$sizesArr = []; | |
// fill in any missing BP values | |
// if user sends { "lg": 3 } or { "sm": 2, "lg": 3 } | |
// this fills out the missing "sm", "md", "xl" values | |
// incase the amount of columns changes per breakpoint | |
// but the column spanning doesn't | |
foreach($sizesSetKeys as $index=>$bp) { | |
if (gettype($sizesSet[$bp]) === 'NULL') { | |
if ($index === 0) { | |
$sizesSet[$bp] = '100vw'; | |
$lastKnownSize = '100vw'; | |
} else { | |
$sizesSet[$bp] = $lastKnownSize; | |
} | |
} else { | |
$lastKnownSize = $sizesSet[$bp]; | |
} | |
} | |
foreach($sizesSetKeys as $index=>$bp) { | |
// calculate size string for bp | |
$bpSizeStr = ''; | |
$sizeAtBreakpoint = $getUnitValue($sizesSet[$bp]); | |
$cssColumnsAtBreakpoint = $cssColumns[$bp]; | |
$colWidth = $cssContainerWidths[$bp] === 'auto' ? 'auto' : floatval($cssContainerWidths[$bp]); | |
if (isset($sizeAtBreakpoint['calc'])) { | |
// if a calc has been passed | |
$bpSizeStr = $sizeAtBreakpoint['calc']; | |
} else if (isset($sizeAtBreakpoint['value']) && gettype($sizeAtBreakpoint['value']) !== 'integer' && gettype($sizeAtBreakpoint['value']) !== 'double') { | |
// no number found, perhaps a "calc()" or something else was passed | |
$bpSizeStr = $sizeAtBreakpoint['value'] || '100vw'; | |
} else if (isset($sizeAtBreakpoint['unit'])) { | |
// has some other unit | |
$bpSizeStr = "{$sizeAtBreakpoint['value']}{$sizeAtBreakpoint['unit']}"; | |
// px values will be converted to rem later | |
} else if ($colWidth !== 'auto') { | |
// calculate based on how much of main col width wide | |
$innerGutter = floatval($cssInnerGutters[$bp]); | |
$px = ((($colWidth - ($innerGutter * ($cssColumnsAtBreakpoint - 1))) / $cssColumnsAtBreakpoint) * $sizeAtBreakpoint['value']) + (($sizeAtBreakpoint['value'] - 1) * $innerGutter); | |
$px = (int) round($px * 100) / 100; | |
$bpSizeStr = "{$px}px"; // will be converted to rem later | |
} else { | |
// else calculate one based on %/vw | |
$percent = ($sizeAtBreakpoint['value'] / $cssColumnsAtBreakpoint) * 100; | |
$percent = (int) round($percent * 100) / 100; | |
$bpSizeStr = $percent.'vw'; | |
} | |
$sizesSet[$bp] = $bpSizeStr; | |
}; | |
// don't add sequential duplicate $sizes so we have the most minimal output possible | |
$lastSize = -1; | |
foreach ($sizesSet as $bp => $size) { | |
if ($size != $lastSize) { | |
$sizesArr[$bp] = $size; | |
} | |
$lastSize = $size; | |
} | |
// set $sizes to the newly made $sizes array, so that it can be converted to a string for output below | |
$sizes = $sizesArr; | |
// convert array to string and return | |
// NB: if an object was passed, its been converted to an array for final output | |
// make final size string for output | |
$sizesStr = ''; | |
$index = 0; | |
foreach(array_reverse($sizes) as $bp=>$size) { | |
if ($relativeUnits && isset($getUnitValue($size)['unit']) && $getUnitValue($size)['unit'] === 'px') { | |
$size = $remCalc($size); | |
} | |
$sizesStr .= $index > 0 ? ', ' : ''; | |
$sizesStr .= strlen($mqPrefixes[$bp]) ? $mqPrefixes[$bp].' ' : ''; | |
$sizesStr .= $size; | |
$index++; | |
}; | |
// return generated string | |
return $sizesStr; | |
} | |
// catch other entries and do something sensible | |
return json_encode($sizes); | |
} | |
// default return if all else fails | |
return '100vw'; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// load me via PHP file_get_contents etc. | |
$feConfig = [ | |
'structure' => [ | |
'breakpoints' => [ | |
'sm' => '0', | |
'md' => '600px', | |
'lg' => '900px', | |
'xl' => '1200px', | |
'xxl' => '1500px', | |
'xxxl' => '1944px', | |
], | |
'columns' => [ | |
'sm' => '4', | |
'md' => '8', | |
'lg' => '12', | |
'xl' => '12', | |
'xxl' => '12', | |
'xxxl' => '12', | |
], | |
'container' => [ | |
'sm' => 'auto', | |
'md' => 'auto', | |
'lg' => 'auto', | |
'xl' => 'auto', | |
'xxl' => 'auto', | |
'xxxl' => '1800px', | |
], | |
'gutters' => [ | |
'inner' => [ | |
'sm' => '20px', | |
'md' => '36px', | |
'lg' => '36px', | |
'xl' => '48px', | |
'xxl' => '60px', | |
'xxxl' => '60px', | |
], | |
'outer' => [ | |
'sm' => '24px', | |
'md' => '28px', | |
'lg' => '48px', | |
'xl' => '60px', | |
'xxl' => '72px', | |
'xxxl' => '0px', | |
], | |
], | |
], | |
// would also contain colors, typography etc. | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
echo "\n"; | |
$sizes = responsiveImageSizes([ | |
"sm" => "100vw", | |
"md" => 4, | |
"lg" => 7, | |
"xl" => "80%", | |
"xxl" => "300px" | |
], $feConfig); | |
$expected = '(min-width: 93.75rem) 18.75rem, (min-width: 75rem) 80%, (min-width: 56.25rem) 58.33vw, (min-width: 37.5rem) 50vw, 100vw'; | |
echo "generates a sizes string with rem units (1) ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; | |
$sizes = responsiveImageSizes([ | |
"sm" => "100vw", | |
"md" => 4, | |
"lg" => "calc(100vw - 20px)", | |
"xl" => "80%", | |
"xxl" => "300px" | |
], $feConfig); | |
$expected = '(min-width: 93.75rem) 18.75rem, (min-width: 75rem) 80%, (min-width: 56.25rem) calc(100vw - 20px), (min-width: 37.5rem) 50vw, 100vw'; | |
echo "generates a sizes string with rem units (2) ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; | |
$sizes = responsiveImageSizes([ | |
"sm" => 2, | |
], $feConfig); | |
$expected = '(min-width: 121.5rem) 15.625rem, (min-width: 56.25rem) 16.67vw, (min-width: 37.5rem) 25vw, 50vw'; | |
echo "generates a sizes string with rem units (3) ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; | |
// -- | |
$sizes = responsiveImageSizes([ | |
"sm" => "100vw", | |
"md" => 4, | |
"lg" => 7, | |
"xl" => "80%", | |
"xxl" => "300px" | |
], $feConfig, false); | |
$expected = '(min-width: 1500px) 300px, (min-width: 1200px) 80%, (min-width: 900px) 58.33vw, (min-width: 600px) 50vw, 100vw'; | |
echo "generates a sizes string with pixel units (1) ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; | |
$sizes = responsiveImageSizes([ | |
"sm" => 2, | |
], $feConfig, false); | |
$expected = '(min-width: 1944px) 250px, (min-width: 900px) 16.67vw, (min-width: 600px) 25vw, 50vw'; | |
echo "generates a sizes string with pixel units (2) ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; | |
// -- | |
$sizes = responsiveImageSizes([ | |
"lg" => 2, | |
], $feConfig); | |
$expected = '(min-width: 121.5rem) 15.625rem, (min-width: 56.25rem) 16.67vw, 100vw'; | |
echo "if no bp passed, defaults to 100vw (1) ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; | |
$sizes = responsiveImageSizes([], $feConfig); | |
$expected = '100vw'; | |
echo "if no bp passed, defaults to 100vw (2) ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; | |
// --- | |
$sizes = responsiveImageSizes('100vw', $feConfig); | |
$expected = '100vw'; | |
echo "returns a passed string (1) ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; | |
$sizes = responsiveImageSizes('calc(100vw - 20px)', $feConfig); | |
$expected = 'calc(100vw - 20px)'; | |
echo "returns a passed string (2) ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; | |
//-- | |
$sizes = responsiveImageSizes([ | |
'sm' => '100vw', | |
'md' => '50vw', | |
'lg' => '58.33vw', | |
'xl' => '80%', | |
'xxl' => '300px' | |
], $feConfig); | |
$expected = '(min-width: 93.75rem) 18.75rem, (min-width: 75rem) 80%, (min-width: 56.25rem) 58.33vw, (min-width: 37.5rem) 50vw, 100vw'; | |
echo "converts a passed array ".($sizes === $expected ? "✅" : "❌"); | |
echo "\n"; | |
echo "EXPECTED: ".$expected; | |
echo "\n"; | |
echo "RECEIVED: ".$sizes; | |
echo "\n\n"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment