How to set lazy loading in Twig template

Hello, i want to try loading images with ‘lazy’ in my twig template.

In my gallery.html.twig i have the following code:

{% extends 'partials/base.html.twig' %}

{% block content %}
    {{ page.content|raw }}
    <ul>
       {% for image in page.media.images|sort|reverse %}
        {{ image.lightbox(2000,1100).cropResize(400,200).html('Test Image')|raw }}
    {% endfor %}
    </ul>

{% endblock %}

Where i have to put the .loading(‘lazy’) in as decribed on documentation page:

{# Using explicit value #}
{{ page.media['sample-image.jpg'].loading('lazy').html('Sample Image')|raw }}

Thanks for help

@pilot23, When asking questions on forums it is often a good habit to show what one has tried already including the results that were obtained.

This demonstrates that you’ve taken the time to try to help yourself.

Hello @pamtbaau, sorry for shure you are right.
I have tried adding the “lazy load” tags on different ways.

I’m using the quark theme, therefor i have add it on: /user/themes/quark/templates/gallery.html.twig

{% for image in page.media.images .loading(‘lazy’)|sort|reverse %}

{% extends 'partials/base.html.twig' %}

{% block content %}
    {{ page.content|raw }}
    <ul>
       {% for image in page.media.images.loading('lazy')|sort|reverse %}
        {{ image.lightbox(2000,1100).cropResize(400,200).html('Test Image')|raw }}
    {% endfor %}
    </ul>

{% endblock %}

But this gives me a RunetimeError:
Twig \ Error \ RuntimeError
The sort filter only works with arrays or “Traversable”, got “NULL”.

When i try it like that:

{{ image .loading(‘lazy’).lightbox(2000,1100).cropResize(400,200).html(‘Test Image’)|raw }}

{% extends 'partials/base.html.twig' %}

{% block content %}
    {{ page.content|raw }}
    <ul>
       {% for image in page.media.images|sort|reverse %}
        {{ image.loading('lazy').lightbox(2000,1100).cropResize(400,200).html('Test Image')|raw }}
    {% endfor %}
    </ul>

{% endblock %}

the site compile but without effect.

My gallery.md looks like this:

access:
    site.login: true
cache_enable: false
process:
    twig: true
    markdown: false
---

Do i have to adjust the partials/base.html.twig because my gallery.html.twig is extended and maybe settings overwritten by base.html.twig?

@pilot23, Adding ** (bold) doesn’t really work in block-quotes… :wink:

Your first try is really off as you’ve noticed. The second try is close and I can see why it has “no effect”.

In you current code, the loading='lazy' is added to the wrapping lighbox anchor.

<a 
  rel="lightbox" 
  data-width="2000" 
  data-height="1100" 
  href="/images/d/b/9/1/6/db916...-image.jpg" 
  data-loading="lazy"
>
  <img title="Heimenegg Image" alt="" src="/images/5/d/6/a/4/5d6a4aa...-image.jpg">
</a>

If you change the order of options like:

{% for image in page.media.images|sort|reverse %}
    {{ image.lightbox(2000,1100).loading('lazy').cropResize(400,200).html('Heimenegg Image')|raw }}
{% endfor %}

the result will be:

<a 
  rel="lightbox" 
  data-width="2000" 
  data-height="1100" 
  href="/images/d/b/9/1/6/db9160a0...-image.jpg"
>
  <img loading="lazy" title="Heimenegg Image" alt="" src="/images/5/d/6/a/4/5d6a4a...-image.jpg">
</a>

Note:

  • You still might see no effect depending on the size of the page and the position of the images.

Thanks for your time and assist @pamtbaau
I have change the code as you written to:

{% extends 'partials/base.html.twig' %}

{% block content %}
    {{ page.content|raw }}
    <ul>
       {% for image in page.media.images|sort|reverse %}
        {{ image.lightbox(2000,1100).loading('lazy').cropResize(400,200).html('Test Image')|raw }}
    {% endfor %}
    </ul>

{% endblock %}

but there is no effect, the page has some time to load (300+) pictures or failed because of timeout.

Now i have tried without the lightbox and cropResize but also without lazyload effect.

{% extends 'partials/base.html.twig' %}

{% block content %}
    {{ page.content|raw }}
    <ul>
       {% for image in page.media.images|sort|reverse %}
        {{ image.loading('lazy').html('Test Image')|raw }}
    {% endfor %}
    </ul>

{% endblock %}

I have checked my settings for caching, they are “standard” and under Media → Imageloading is ‘lazy’ (but we fix it already in twig).
Are there any other options/settings or tags who i can try?

@pilot23 As said…

  • You still might see no effect depending on the size of the page and the position of the images.

Lazy loading is a browser feature. Once the attribute loading="lazy" has been set on the <img>, it is out of the hands of Grav and up to the browser to decide how to deal with lazy loading. Each browser has its own decision tree to decide when to load images. And that might not be what you would expect, or want…

It has nothing to do with cropResize or lightbox. Also changing Grav’s options does not effect how the browser decides to deal with lazy loading.

You said:

but there is no effect

  • What effect did you expect?
  • How did you check for the effect?
  • Page length/image position
    • How long is your page. Or, how much scrolling is needed to reach the images.
    • What happens if you make the text above the images 2, 3 or 10 times longer?

Hi @pilot23 ,

Have you tried change your code for this?

<ul>
  {% for image in page.media.images|sort|reverse %}
    <img src="{{image.cropResize(400,200).url|e}}" class="some classes" loading="lazy" alt="some alt text" /> 
  {% endfor %}
</ul>

@pmoreno Thanks for your assist, i have tried the code but no effect on lazy load. Now i have limited the images to 50 with

{% for image in page.media.images|sort|reverse|slice(0,50) %}

This is not exactly what i want but its a good solution.

@pilot23, So far, not much information is provided accept for “without effect” or “without lazyload effect”.

When I add 300 images of 1200x800px to the bottom of page Typography (fresh Grav 1.7.30 installation), none of the images is loaded until scrolling down the page near the end. Hence, lazy loading is working, albeit not yet as efficient as one would like.

default.html.twig:

{% extends 'partials/base.html.twig' %}

{% block content %}
  {{ page.content|raw }}

  {% for image in page.media.images %}
    {{ image.loading('lazy').html()|raw }}
  {% endfor %}
{% endblock %}

As said, the browser decides when and how many images to load depending on, for example, the image size, vertical position of images relative to page length, viewport dimensions, scrolling speed, etc.

Using above simple test, about 200+ images are being lazy loaded at once when scrolling down near the end of the page. Why so many? Before loading the images, the browser has no idea about the size of the images.

Do you apply height/width styling rules to the images in css?

  • Adding image dimensions in css (or adding height/width directly to the <img> tag) will give the browser more information to calculate/predict how many images should be loaded on scrolling.
  • Adding dimensions will also prevent Cumulative Layout Shift.

When adding the following css to above test, Chrome only loads 1-2 images at a time when scrolling down:

img {
  width: 1200px;
}

Notes:

  • You should probably add a class to the images using

    {{ image.loading('lazy').html('my-title', 'my-alt, 'my-class')|raw }}
    

    and use ‘.my-class’ as selector for the css instead of ‘img’.

  • You can have Grav add dimensions automatically to each image by setting the following in file /user/config/system.yaml:

    images:
      cls:      # stands for Cumulative Layout Shift
        auto_sizes: true
    
    • But often, you will need to create different image sizes (srcsets) depending on the size of the device and add different dimensions to css depending on the viewport of the device.
  • Also be aware that the gallery being used might interfere with lazy loading.