Skip to content

Instantly share code, notes, and snippets.

@tylermercer
Last active June 9, 2024 03:24
Show Gist options
  • Save tylermercer/8bce964a5ca830efb5754aefb73a40d4 to your computer and use it in GitHub Desktop.
Save tylermercer/8bce964a5ca830efb5754aefb73a40d4 to your computer and use it in GitHub Desktop.
Responsive Image Sizes Calculator
/*
* This function can be applied to the results in the above's `computeSizes` to output the results as a `widths` and `sizes` attribute
* to be used in an Astro project
*/
const convertToAstroCode = (obj) => {
const result = {};
Object.keys(obj).forEach((key) => {
const measurements = obj[key];
measurements.sort((a, b) => a.window - b.window);
const widths = measurements.map(
(measurement) => measurement.measurement,
);
// Generate the sizes string
const sizes = measurements
.map((measurement, index) => {
if (index === measurements.length - 1) {
return `${measurement.measurement}px`;
} else {
return `(max-width: ${measurement.window}px) ${measurement.measurement}px`;
}
})
.join(", ");
// Create the final string for the key
result[key] =
`widths={${JSON.stringify(widths)}} sizes={\`${sizes}\`}`;
});
return result;
};
/*
This script records the sizes of images on the page as the window resizes. Images with a data-img-measure-id attr are measured.
You can then run imgMeasure.printMeasurements() to see the data (in table form so you can copy it to Excel for graphing).
imgMeasure.computeSizes(k) runs a naive algorithm to compute the necessary image sizes and breakpoints to avoid serving image files
that are more than k times as wide as the element they are rendered in
Note: I noticed that resizing the window slowly from largest to smallest is the best way to get good data. Moving too quickly or back
and forth repeatedly can cause sufficient error in measurement that the computeSize algorithm fails (i.e. generates way too many sizes)
*/
const images = Array.from(
document.querySelectorAll("[data-img-measure-id]"),
);
const measurements = [];
window.addEventListener("resize", () => {
measurements.push(
Object.fromEntries(
images
.map((i) => [i.dataset.imgMeasureId, i.clientWidth])
.concat([["window", window.innerWidth]]),
),
);
});
const readData = () => measurements.sort((a, b) => b.window - a.window);
const printMeasurements = () => console.table(readData());
const computeSizes = (k = 2) => {
if (typeof k !== "number" || k < 1) {
console.error("k should be a number greater than 1");
return;
}
const data = readData();
if (!data.length) {
console.error("no data yet");
return;
}
const ids = Object.keys(data[0]).filter((key) => key !== "window");
const results = Object.fromEntries(
ids.map((id) => [
id,
data.map((d) => ({
measurement: d[id],
window: d.window,
})),
])
.map(([id, dataset]) => [
id,
dataset.reduce(
(r, { measurement, window }) => {
if (
r.currentSize > k * measurement ||
r.currentSize < measurement
)
r.sizes.push({ measurement, window });
return r;
},
{
sizes: [dataset[0]],
get currentSize() {
return this.sizes[this.sizes.length - 1]
.measurement;
},
},
).sizes,
]),
);
console.log(results);
};
window.imgMeasure = {
printMeasurements,
computeSizes,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment