How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

How-to Images

Resize an image from JavaScript using URL parameters

Every template transformation is also a query parameter on the image URL: w, h, s, f, q. Hand the script a base path from the template, round to a few fixed widths, and never put visitor input into the parameters.

Context

Context. A script that builds a gallery, swaps an image on hover or picks a size for a viewport has a path but no template object. The path takes the same transformations as query parameters, and the file is generated on the first request for that URL and cached. There is no limit on sizes and no signing, so a script that requests arbitrary widths makes the platform generate arbitrary files.

Steps

  1. In the template, put the image's path (and its dimensions, if the script needs them) in data attributes. Let the object build any base resize.
  2. In the script, append w, h and s from a fixed set of sizes, and .webp?f=jpg style conversion if you want WebP.
  3. Never feed a query parameter, a viewport width or anything visitor-controlled straight into the URL; round it to the nearest of your fixed widths.
  4. Prefer the automatic picture tag for "a size per device"; use scripts for behaviour, not for responsive images.

Code

Template code
<figure class="zoom" data-src="{$image->getPath()}" data-width="{$image->getWidth()}">
  {$image->getResized(800, 600, 'fit')->getHtml(['alt' => $image->getName()|escape])}
</figure>
<figure class="zoom" data-src="{{ image.getPath() }}" data-width="{{ image.getWidth() }}">
  {{ image.getResized(800, 600, 'fit').getHtml({'alt': image.getName()|e}) }}
</figure>
JavaScript
// on click, load a larger version: a fixed set of widths, never the raw viewport width
var WIDTHS = [800, 1200, 1600, 2000];
function sized(path, wanted) {
  var w = WIDTHS.find(function (x) { return x >= wanted; }) || WIDTHS[WIDTHS.length - 1];
  return path.replace(/\.(jpe?g|png)$/i, '.webp?f=$1&') .replace(/&$/, '&w=' + w + '&s=auto');
}
document.querySelectorAll('.zoom').forEach(function (fig) {
  fig.addEventListener('click', function () {
    var img = fig.querySelector('img');
    img.src = sized(fig.dataset.src, Math.min(window.innerWidth, fig.dataset.width));
  });
});

Why this and not that

Not a hand-built URL inside the template: the object knows the real dimensions, adds width and height attributes, skips a no-op resize and falls back safely; the URL form does none of that. Not the exact container width as w: every distinct value is another file generated and stored, and a visitor resizing a window generates dozens. The parameters are w, h, s (auto, fit, crop, exact, landscape, portrait, square), x and y for a crop origin, f for the original format when the extension is the target, q and l for compression.

Go deeper

Was this page helpful?

Your answer goes to the docs team.