CODE

Responsive Image Utility

The Responsive Image Utility snippet provides an easier way to add production-ready responsive images to your site without writing complex <picture> elements with multiple image sources.

v1.0.0

Using a combination of modern image strategies, including WebinOne's Image Processor to automatically size and convert to WebP image format (with JPEG fallback), <picture> element with multiple srcset variations, and optional lazy-load support with blurred Low-Quality Image Placeholder (LQIP) proxies, and pulsing load effects. This utility takes the hard work out of modern responsive and performant images, helping your site load faster, provide users with a better perceived loading experience, avoid jarring layout shifts, and score better in performance and best-practice evaluations.

There are 3 components to get this setup:

  1. Snippet

    The Snippet contains most of the heavy-lifting and Liquid conditions, and you configure it by passing your image path and a few settings via parameters - which are all documented and examples provided in the Snippet code below.

    Create a new Snippet in your site’s admin with the name “Responsive Image Utility” and adjust the alias to “image_utility”. Then, copy and paste the code below into the Snippet contents via the ‘Code’ view.

    NOTE:
    Set your fallback image if no image path is provided, by changing this assign value in the code: {% assign _src = '/favicon.png' %}. The default /favicon.png is used as this is the most likely image to exist on all sites, and falling back to the site's logo or icon generally makes sense.

    {% comment %}<!-- Treehouse CODE v1.0.0 -->{% endcomment %}
    {% comment %}
      image_utility — Responsive <picture> with WebP + JPEG srcset via WebinOne Image Processor.
      When loading='lazy' is set, a Low-Quality Image Placeholder (LQIP) is shown until the full image loads.
    
      The <img> in the LQIP path has no src attribute — this prevents Chromium from
      race-fetching the JPEG fallback. loading='lazy' latches onto the <source> selection
      instead, so the browser correctly picks the WebP srcset candidate at load time.
    
      Parameters:
        src           Base image path. Default: /favicon.png
        alt           Alt text. Default: 'Image for {page name}'
        class         CSS classes.
        size          Layout hint: 'full' (default) or 'half'
                        full = spans the full container width (max 1320px)
                        half = approx. two-column layout, e.g. col-lg-6
        sizes         Power-user override: explicit sizes string. Takes precedence over size. e.g. '(max-width: 1399px) calc(100vw - 48px), 1320px'
        ratio         Aspect ratio preset — sets aspect-ratio CSS + <img> width/height for CLS prevention.
                        'landscape' = 3:2  (standard photography, product shots)
                        'wide'      = 16:9 (blog headers, editorial widescreen)
                        'square'    = 1:1  (social-style, product grid)
                        'portrait'  = 2:3  (team members, person photos)
        width         Exact intrinsic width — takes precedence over ratio. Power-user override.
        height        Exact intrinsic height — takes precedence over ratio. Power-user override.
        loading       'lazy' activates the LQIP placeholder. Omit for above-fold / LCP images.
        fetchpriority 'high' for LCP / hero images. Omit otherwise.
        quality       JPEG fallback quality 1-100 (default: 75) - WebP quality is automatic/fixed.
    
      NOTE: use params.* (not this.params.*) — this.params is cached at page scope across
      component calls; params is properly scoped per invocation.
    
      Styles for `.lazyload-blur` and `.lazyload-blur img` should be saved in your CSS file.
      Only dynamic values (`background-image` URL and `aspect-ratio/min-height`) are set inline.
      Fade-in is handled by JS function `initLazyBlur` which adds `.is-loaded` on load and should be saved in your JS file.
    
      Usage — above-fold / Largest Contentful Paint (LCP) without LQIP:
        {% component type: 'snippet', alias: 'image_utility',
          src: '/media/hero.jpg',
          alt: 'Your Image Description',
          class: 'img-fluid rounded',
          size: 'half',
          ratio: 'landscape',
          fetchpriority: 'high' %}
    
      Usage — below-fold with LQIP:
        {% component type: 'snippet', alias: 'image_utility',
          src: '/media/image.jpg',
          alt: 'Your Image Description',
          class: 'img-fluid rounded',
          size: 'full',
          ratio: 'wide',
          loading: 'lazy' %}
    
      Usage — exact dimensions override (for power-users):
        {% component type: 'snippet', alias: 'image_utility',
          src: '/media/image.jpg',
          alt: 'Your Image Description',
          width: '1200', height: '400',
          loading: 'lazy' %}
    {% endcomment %}
    {% assign _src = params.src | replace: ' ', '%20' %}
    {% assign _srcMode = '' %}
    {% if _src == blank %}
      {% assign _src = '/favicon.png' %}
      {% assign _srcMode = '&mode=boxpad' %}
    {% endif %}{% assign _alt = params.alt %}
    {% if _alt == blank %}{% assign _alt = 'Image for ' | append: this.name %}{% endif %}
    {% assign _class = params.class %}
    {% assign _size = params.size %}
    {% assign _sizes = params.sizes %}
    {% assign _quality = params.quality %}
    {% assign _ratio = params.ratio %}
    {% assign _width = params.width %}
    {% assign _height = params.height %}
    {% assign _loading = params.loading %}
    {% assign _fp = params.fetchpriority %}
    {% if _quality == blank %}{% assign _quality = '75' %}{% endif %}
    {% if _sizes == blank %}
      {% if _size == 'half' %}
        {% assign _sizes = '(min-width: 1400px) 636px, (min-width: 992px) 456px, (min-width: 768px) 696px, calc(100vw - 24px)' %}
      {% else %}
        {% assign _sizes = '(min-width: 1400px) 1296px, (min-width: 992px) 936px, (min-width: 768px) 696px, calc(100vw - 24px)' %}
      {% endif %}
    {% endif %}
    {% assign _img_w = '' %}
    {% assign _img_h = '' %}
    {% assign _css_ratio = '' %}
    {% assign _has_ratio = false %}
    {% if _width != blank %}
      {% if _height != blank %}
        {% assign _img_w = _width %}
        {% assign _img_h = _height %}
        {% assign _css_ratio = _width | append: '/' | append: _height %}
        {% assign _has_ratio = true %}
      {% endif %}
    {% endif %}
    {% unless _has_ratio %}
      {% if _ratio == 'wide' %}
        {% assign _img_w = '1200' %}
        {% assign _img_h = '675' %}
        {% assign _css_ratio = '16/9' %}
        {% assign _has_ratio = true %}
      {% elsif _ratio == 'square' %}
        {% assign _img_w = '800' %}
        {% assign _img_h = '800' %}
        {% assign _css_ratio = '1/1' %}
        {% assign _has_ratio = true %}
      {% elsif _ratio == 'portrait' %}
        {% assign _img_w = '534' %}
        {% assign _img_h = '800' %}
        {% assign _css_ratio = '2/3' %}
        {% assign _has_ratio = true %}
      {% elsif _ratio == 'landscape' %}
        {% assign _img_w = '1200' %}
        {% assign _img_h = '800' %}
        {% assign _css_ratio = '3/2' %}
        {% assign _has_ratio = true %}
      {% endif %}
    {% endunless %}
    {% if _loading == 'lazy' %}
      {% if _size == 'half' %}
        {% assign _lqip_url = _src | append: '?width=60&blur=50&format=webp' %}
      {% else %}
        {% assign _lqip_url = _src | append: '?width=160&blur=50&format=webp' %}
      {% endif %}
    <span class="lazyload-blur {{ _class }}" style="background-image:url('{{ _lqip_url }}'); {% if _has_ratio %}aspect-ratio:{{ _css_ratio }};{% else %}min-height:200px;{% endif %}">
      <picture class="riu">
        <source
          type="image/webp"
          srcset="{{ _src }}?format=webp&width=480{{ _srcMode }} 480w, {{ _src }}?format=webp&width=800{{ _srcMode }} 800w, {{ _src }}?format=webp&width=1200{{ _srcMode }} 1200w, {{ _src }}?format=webp&width=1500{{ _srcMode }} 1500w"
          sizes="{{ _sizes }}"
        >
        <source
          type="image/jpeg"
          srcset="{{ _src }}?width=480&quality={{ _quality }}{{ _srcMode }} 480w, {{ _src }}?width=800&quality={{ _quality }}{{ _srcMode }} 800w, {{ _src }}?width=1200&quality={{ _quality }}{{ _srcMode }} 1200w, {{ _src }}?width=1500&quality={{ _quality }}{{ _srcMode }} 1500w"
          sizes="{{ _sizes }}"
        >
        <img
          alt="{{ _alt }}"
          {% if _img_w != blank %}width="{{ _img_w }}"{% endif %}
          {% if _img_h != blank %}height="{{ _img_h }}"{% endif %}
          loading="lazy"
          decoding="async"
        >
      </picture>
    </span>
    {% else %}
    <picture class="riu">
      <source
        type="image/webp"
        srcset="{{ _src }}?format=webp&width=480{{ _srcMode }} 480w, {{ _src }}?format=webp&width=800{{ _srcMode }} 800w, {{ _src }}?format=webp&width=1200{{ _srcMode }} 1200w, {{ _src }}?format=webp&width=1500{{ _srcMode }} 1500w"
        sizes="{{ _sizes }}"
      >
      <source
        type="image/jpeg"
        srcset="{{ _src }}?width=480&quality={{ _quality }}{{ _srcMode }} 480w, {{ _src }}?width=800&quality={{ _quality }}{{ _srcMode }} 800w, {{ _src }}?width=1200&quality={{ _quality }}{{ _srcMode }} 1200w, {{ _src }}?width=1500&quality={{ _quality }}{{ _srcMode }} 1500w"
        sizes="{{ _sizes }}"
      >
      <img
        src="{{ _src }}?width=800&quality={{ _quality }}{{ _srcMode }}"
        alt="{{ _alt }}"
        class="{{ _class }}"
        {% if _img_w != blank %}width="{{ _img_w }}"{% endif %}
        {% if _img_h != blank %}height="{{ _img_h }}"{% endif %}
        {% if _fp != blank %}fetchpriority="{{ _fp }}"{% endif %}
        decoding="async"
      >
    </picture>
    {% endif %}
  2. Javascript

    The Javascript function checks when lazy-loaded images have loaded and removes the loading animation on the blurred proxy images.

    Copy and paste the script into your existing global JS file.

    //## Treehouse CODE v1.0.0 ##
    /* LQIP fade-in — adds .is-loaded to images inside .lazyload-blur once loaded.
       Opacity and transition are defined in evanlite.css; no inline styles used. */
    (function () {
      function revealImg(img) {
        img.classList.add('is-loaded');
      }
    
      function initLazyBlur() {
        document.querySelectorAll('.lazyload-blur img').forEach(function (img) {
          if (img.complete && img.naturalWidth > 0) {
            /* Already loaded (cached) — reveal immediately */
            revealImg(img);
          } else {
            img.addEventListener('load', function () { revealImg(img); });
          }
        });
      }
    
      if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initLazyBlur);
      } else {
        initLazyBlur();
      }
    }());
  3. CSS

    The CSS sets up a few key styles for the lazy-loading effects..

    Copy and paste the styles into your existing global CSS file.

    /* Treehouse CODE v1.0.0 */
    .lazyload-blur {
        position: relative;
        display:block;
        overflow:hidden; 
        background-repeat: no-repeat;
        background-size: cover;
    }
    .lazyload-blur:has( .is-loaded) {
        background-image: none !important;
        aspect-ratio: auto !important;
    }
    .lazyload-blur img {
        display:block;
        width:100%;
        opacity:0;
        transition:opacity 0.4s ease;
    }
    .lazyload-blur img.is-loaded {
        opacity: 1;
    }
    .lazyload-blur::before {
        content: '';
        position: absolute;
        inset: 0;
        background-color: rgb(255 255 255 / 30%);
        backdrop-filter: blur(10px);
        z-index: 0;
        animation: pulse 2.5s infinite;
    }
    .lazyload-blur picture {
        position: relative;
        z-index: 1;
    }
    picture.riu img {
        margin: 0;
        padding: 0;
    }
    
    @keyframes pulse {
      50% {
        background-color: rgb(255 255 255 / 10%);
      }
    }

Usage

Wherever you have images on your page or in your layouts, you can replace the regular <img> element and instead, call in the Snippet and add parameters to it to set up your image preferences.

Below are some examples:

Usage — above-fold images / Largest Contentful Paint (LCP) without LQIP:

{% component type: 'snippet', alias: 'image_utility',
    src: '/media/hero.jpg',
    alt: 'Your Image Description',
    class: 'img-fluid rounded',
    size: 'half',
    ratio: 'landscape',
    fetchpriority: 'high' %}

Usage — below-fold images with LQIP:

{% component type: 'snippet', alias: 'image_utility',
    src: '/media/image.jpg',
    alt: 'Your Image Description',
    class: 'img-fluid rounded',
    size: 'full',
    ratio: 'wide',
    loading: 'lazy' %}

Usage — exact dimensions override (for power-users):

{% component type: 'snippet', alias: 'image_utility',
    src: '/media/image.jpg',
    alt: 'Your Image Description',
    width: '1200',
    height: '400',
    loading: 'lazy' %}

Usage — within a module layout, wired up for Liquid data:

{% component type: 'snippet', alias: 'image_utility',
    src: '{{this.MainImage}}',
    alt: 'Image for {{this.Name}}',
    class: 'img-fluid rounded',
    size: 'full',
    ratio: 'wide',
    loading: 'lazy' %}

The Snippet will output markup similar to the following:

<span class="lazyload-blur img-fluid rounded" style="background-image:url('/images/image.jpg?width=160&amp;blur=50&amp;format=webp'); aspect-ratio:3/2;">
  <picture class="riu">
    <source type="image/webp" srcset="/images/image.jpg?format=webp&amp;width=480 480w, /images/image.jpg?format=webp&amp;width=800 800w, /images/image.jpg?format=webp&amp;width=1200 1200w, /images/image.jpg?format=webp&amp;width=1500 1500w" sizes="(min-width: 1400px) 1296px, (min-width: 992px) 936px, (min-width: 768px) 696px, calc(100vw - 24px)">
    <source type="image/jpeg" srcset="/images/image.jpg?width=480&amp;quality=75 480w, /images/image.jpg?width=800&amp;quality=75 800w, /images/image.jpg?width=1200&amp;quality=75 1200w, /images/image.jpg?width=1500&amp;quality=75 1500w" sizes="(min-width: 1400px) 1296px, (min-width: 992px) 936px, (min-width: 768px) 696px, calc(100vw - 24px)">
    <img alt="Your Image Description" width="1200" height="800" loading="lazy" decoding="async">
  </picture>
</span>

How it Works

Format Selection

  • WebP is served to browsers that support it (Chrome, Edge, Firefox, Safari 16+)
  • JPEG fallback for older browsers (Safari 15 and below, IE11)

Responsive Sizing

  • Browser downloads the smallest image that fits its viewport (high-density displays will use larger images)
  • 480px, 800px, 1200px, and 1500px variants are generated on-demand by WebinOne's Image Processor
  • sizes attribute tells the browser which variant to load at each breakpoint

Lazy Loading & LQIP

  • When loading: 'lazy' is set, a very samll and blurred placeholder renders immediately
  • Full image loads when it scrolls into or near view (depending on the browser)
  • Fade-in animation handled by JS function

CLS Prevention

  • Aspect ratio presets or explicit width/height reserve space before the image loads, prevent layout shifts
  • Browser recalculates layout once — no jank or reflow

Performance Tips

  • Use fetchpriority: 'high' on hero / above-fold images only (1–2 per page max)
  • Use loading: 'lazy' on below-fold images to defer load
  • Set an aspect ratio preset or use exact width / height to prevent cumulative layout shift (CLS)

Comments or questions? Head over to the WebinOne forum to discuss with the community.