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

Render a responsive image with an author-supplied crop

The author chooses the crop through an attached attribute; the template resizes with that strategy and lets the picture-tag setting offer the widths. Resize, then convert, then render.

Context

Context. One image, many slots: a wide banner, a square card, a portrait thumbnail. The author should choose how it is cropped and the template should do the rest. The crop is an attached attribute (a select whose keys are the resize strategies), the resize is getResized() with that strategy, and the responsive part comes from the site's Generate picture tag setting plus offeredSizes, which offers several widths and densities without hand-written markup.

Steps

  1. Attach an imageCrop select to the image area with options fit::Fill the frame, auto::Fit inside, crop::Cut a detail, default fit.
  2. Read the image and the crop; skip when blank.
  3. Resize with the crop, convert with toOptimizedFormat(), then render with offeredSizes, alt text and lazy loading, in that order.
  4. With picture tags off, the same call renders one <img> with width and height; nothing else changes.

Template code

Template code
{$image = $component->getAreaImage('image')}
{if !$image->isBlank()}
  {$crop = $component->getAreaText('imageCrop')|default:'fit'}
  {$image->getResized(1200, 675, $crop)->toOptimizedFormat()->getHtml([
    'offeredSizes' => ['1x', '2x', '600w', '1200w'],
    'alt'          => $component->getAreaText('imageAlt')|default:$image->getName()|escape,
    'loading'      => 'lazy'
  ])}
{/if}
{% set image = component.getAreaImage('image') %}
{% if not image.isBlank() %}
  {% set crop = component.getAreaText('imageCrop')|default('fit') %}
  {{ image.getResized(1200, 675, crop).toOptimizedFormat().getHtml({
    'offeredSizes': ['1x', '2x', '600w', '1200w'],
    'alt': component.getAreaText('imageAlt')|default(image.getName())|e,
    'loading': 'lazy'
  }) }}
{% endif %}

Why this and not that

Not a fixed crop in the template: the same component holds a landscape photo today and a logo tomorrow. Not converting before resizing: both orders give the same pixels, but resizing first is the order that keeps the URLs the cache expects, so it does not hold every image twice. Not a hand-written picture tag unless the crop must differ per breakpoint; that case is its own how-to.

Go deeper

Was this page helpful?

Your answer goes to the docs team.