Serving pages faster with images variations
Oct 19, 2021
--
Getting images in the right resolution 1200x628

Serving pages faster with images variations

A sensible requirement for any web app is delivering different-size images depending on a visitor’s device. You can save a lot of bandwidth by providing images in a smaller resolution when a mobile device or tablet accesses your site, making it load faster.

This blog article will show you what image resolution options Magnolia brings to the table and how to use them.

Defining image variations

To provide images in different resolutions in Magnolia you first have to define image variations in a theme. The definition is straightforward and consists of

  • The name of the variation, for example, its width for simplicity

  • The class SimpleResizeVariation

  • The image’s width

Java
  [...]
  imaging:
    class: info.magnolia.templating.imaging.VariationAwareImagingSupport
    variations:
      "1600":
        class: info.magnolia.templating.imaging.variation.SimpleResizeVariation
        width: 1600
      "1366":
        class: info.magnolia.templating.imaging.variation.SimpleResizeVariation
        width: 1366
      [...]

If you have not yet assigned a theme to your site, please follow our documentation to do so.

Generating the image URL

In Freemarker

We offer templating functions to provide easy access to digital assets such as images. These functions generate the URL of an image and its requested variation for you. I will show you how to use them later.

In headless mode

Our delivery API supports image variations in headless mode, too. Our asset resolver automatically generates the URL of the image variation. Then it’s the responsibility of the front end to place the image correctly.

An example from our Travel Demo

I would like to share the implementation of image variations in our travel demo to show you how to define variations and use our templating functions efficiently.

Example: Defining the theme

This is how we define variations in a theme.

Excerpt from travel-demo-theme.yaml:

Java
  cssFiles:
    [...]
  jsFiles:
    [...]
    lazysizes:
      addFingerPrint: true
      link: /.resources/travel-demo-theme/libs/lazysizes/lazysizes.min.js
    [...]
  imaging:
    class: info.magnolia.templating.imaging.VariationAwareImagingSupport
    variations:
      "1600":
        class: info.magnolia.templating.imaging.variation.SimpleResizeVariation
        width: 1600
      "1366":
        class: info.magnolia.templating.imaging.variation.SimpleResizeVariation
        width: 1366
      "960":
        class: info.magnolia.templating.imaging.variation.SimpleResizeVariation
        width: 960
      "480":
        class: info.magnolia.templating.imaging.variation.SimpleResizeVariation
        width: 480
      [...]

Besides the image variations themselves, the JavaScript library lazysizes is worth mentioning. It later ensures that Magnolia serves the best image variation depending on a visitor’s screen size. For this to work, we have to generate the correct HTML code of the image, as I will show you next.

Building a Custom Integration for an External DAM in Magnolia

Do you want to easily use images, videos, and other documents across your apps and digital channels? Read our blog to learn how to connect any external Digital Asset Management (DAM) platform to Magnolia.

Example: Generating the image HTML

We use a macro called responsiveImageTravel in our demo. It determines which image variations to consider and which variation to use as a fallback. We will use this macro in our component template.

Excerpt from imageResponsive.ftl:

Java
  [...]
 
 [#-- Macro to render a responsive image with the variations configured in the theme. --]
  [#macro responsiveImageTravel asset  alt="" title="" cssClass="" additional="" constrainAspectRatio=false]
    [#if constrainAspectRatio ]
      [#assign srcs = [
        {"name":"480x360", "width":"480"},
        {"name":"960x720", "width":"960"},
        {"name":"1366x1024","width":"1366"},
        {"name":"1600x1200", "width":"1600"}]]
      [#assign fallback="960x720"]
      [@responsiveImageLazySizes asset alt title cssClass srcs fallback additional /]
    [#else]
      [#assign srcs = [
        {"name":"480", "width":"480"},
        {"name":"960", "width":"960"},
        {"name":"1366","width":"1366"},
        {"name":"1600","width":"1600"}]]
      [#assign fallback="960"]
      [@responsiveImageLazySizes asset alt title cssClass srcs fallback additional /]
    [/#if]
  [/#macro]
 
[...]

This macro calls a second macro called responsiveImageLazySizes which uses the getRenditions templating function for each variation and the fallback.

This way, we generate the image HTML as described in the example "responsive image with srcset and automatic sizes attribute" in the lazysizes documentation.

Excerpt from imageResponsive.ftl:

Java
   
[#-- Macro to render responsive image using lazysizes javascript library. Use data-srcset attribute to only load the size of image that the current image width requires --]
  [#macro responsiveImageLazySizes asset  alt="" title="" cssClass="" srcs="" fallbackName="" additional="" ]
    [#if asset?exists]
      [#assign cssClass = cssClass + " lazyload"]
      [#assign rendition = damfn.getRendition(asset, fallbackName)!]
      <noscript>
        <img class="${cssClass}" src="${rendition.link}" alt="${alt}" title="${title}" ${additional} />
      </noscript>
      <img data-sizes="auto" class="${cssClass} lazyload" ${additional} src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="${alt}" title="${title}"
            data-srcset="[#compress]
               [#list srcs as src]
                   [#assign rendition = damfn.getRendition(asset, src.name)!]
                   [#if rendition?exists && rendition?has_content]
                       ${rendition.link} ${src.width}w,
                   [/#if]
               [/#list]
           [/#compress]" />
    [/#if]
  [/#macro]

We can now use the responsiveImageTravel macro anywhere. So, let’s look at an example: the tour teaser macro. This macro uses the previously macro to generate the image HTML.

Excerpt from tourTeaser.ft:

Java
  [#macro tourTeaser tour additionalWrapperClass="col-md-6"]
 
    [#include "/travel-demo/templates/macros/imageResponsive.ftl"]
    [#include "/tours/templates/macros/tourTypeIcon.ftl" /]
    [#assign imageHtml][@responsiveImageTravel tour.image "" "" "tour-card-image" "data-ratio='1.33'" true /][/#assign]
 
    <!-- Tour Teaser -->
    <div class="${additionalWrapperClass} tour-card card">
      <div class="tour-card-background">
        ${imageHtml}
      </div>
      <a class="tour-card-anchor" href="${tour.link!}">
        <div class="tour-card-content-shader"></div>
        <div class="tour-card-content">
          <h3>${tour.name!}</h3>
          <div class="category-icons">
            [#list tour.tourTypes as tourType]
              <div class="category-icon absolute-center-container">
                [@tourTypeIcon tourType.icon tourType.name "" /]
              </div>
            [/#list]
          </div>
          <div class="card-button">
            <div class="btn btn-primary">${i18n['tour.view']}</div>
          </div>
        </div>
      </a>
    </div>
  [/#macro]

Example: Using the macro in a component

Now, let's see how to use the macros in an actual component.

Their inclusion makes them available in the component template. While iterating through the list of tours, we create a “tourTeaser” component for each tour using the “tourTeaser” macro.

Java
  [#-------------- ASSIGNMENTS --------------]
  [#include "/tours/templates/macros/tourTeaser.ftl"]
  [#include "/travel-demo/templates/macros/editorAlert.ftl" /]
 
  [#if def.parameters.tourType??]
    [#assign category = model.getCategoryByName(def.parameters.tourType)]
  [#else]
    [#assign category = model.getCategoryByUrl()!]
  [/#if]
 
  [#assign tours = model.getToursByCategory(category.identifier)]
  [#assign title = content.title!i18n.get('tour.all.tours', [category.name!""])!]
 
  [#-------------- RENDERING --------------]
  <!-- Tour List -->
  <div class="container tour-list">
    <h2>${title}</h2>
    <div class="row">
      [#list tours as tour]
        [@tourTeaser tour /]
      [/#list]
    </div>
   [@editorAlert i18n.get('note.for.editors.assign.category', [category.name!""]) /]
  </div>
  <script>
    jQuery(".tour-card-image").objectFitCoverSimple();
  </script>

At the end of this component, we run the jQuery function objectFitCoverSimple using the CSS class tour-card-image we passed in the macro tourTeaser.ftl when creating the HTML.

Conclusion

With Magnolia it’s easy to deliver different image resolutions depending on the user’s screen size. This functionality is not limited to Freemarker but works for modern headless implementations, too. This is another example of Magnolia’s flexibility, delivering real-life szenarios.

About the author

Tobias Kerschbaum

Solution Architect, Magnolia

As a solution architect, Tobias works closely with customers and partners, sharing his knowledge and expertise. He helps organizations evaluate and understand how Magnolia can meet project requirements. He contributes to the project plan and ensures the right modules and technologies are chosen. Besides delivering tailored workshops, Tobias also gets involved when customers and partners need to implement new functionality or custom requirements.