DEV Community

Cover image for Hard time with image attribute loading="lazy", Alpinejs can help.
Andrew Zachary
Andrew Zachary

Posted on • Updated on

 

Hard time with image attribute loading="lazy", Alpinejs can help.

Modern browsers natively support lazy loading with <img loading=lazy> attribute. But if the page has layout shifts or no flexibility to specify the width and height for all images within the page, the browser will determine that a misplaced images are visible to the user and load them all, that will hurt the page's total performance.

Alpinejs is a powerful and simple front-end library which can be extended with a intersect plugin that will wrap Intersection Observer, simplifying how to react when an element enters the viewport.

Install

import Alpine from 'alpinejs'
import intersect from '@alpinejs/intersect'

window.Alpine = Alpine

Alpine.plugin(intersect)
Alpine.start()
Enter fullscreen mode Exit fullscreen mode

Alpine Component

Create a component by add x-data directive to div#lazy-loading-page, a lot of other useful directives can be used on it's children. Directive x-intersect can be added to any element within an Alpine component, and when that element enters the viewport (is scrolled into view), the provided expression to load images will execute.

<div x-data id="lazy-loading-page" class="h-full">

    <section x-intersect.threshold.50="$refs.img1.src = $refs.img1.dataset.img" class="border-b-4 border-black p-8 h-full flex items-center justify-center">
        <img
            x-ref="img1"
            class="opacity-0 transition-opacity duration-300 ease-linear" 
            src=""
            data-img="https://via.placeholder.com/650x450"
            onload="this.classList.add('opacity-100')"
        />
    </section>
    <section x-intersect.threshold.50="$refs.img2.src = $refs.img2.dataset.img" class="border-b-4 border-black p-8 h-full flex items-center justify-center">
        <img
            x-ref="img2"
            class="opacity-0 transition-opacity duration-300 ease-linear" 
            src=""
            data-img="https://via.placeholder.com/650x451"
            onload="this.classList.add('opacity-100')"
        />
    </section>
    <section x-intersect.threshold.50="$refs.img3.src = $refs.img3.dataset.img" class="border-b-4 border-black p-8 h-full flex items-center justify-center">
        <img
            x-ref="img3"
            class="opacity-0 transition-opacity duration-300 ease-linear" 
            src=""
            data-img="https://via.placeholder.com/650x452"
            onload="this.classList.add('opacity-100')"
        />
    </section>
    <section x-intersect.threshold.50="$refs.img4.src = $refs.img4.dataset.img" class="border-b-4 border-black p-8 h-full flex items-center justify-center">
        <img
            x-ref="img4"
            class="opacity-0 transition-opacity duration-300 ease-linear" 
            src=""
            data-img="https://via.placeholder.com/650x453"
            onload="this.classList.add('opacity-100')"
        />
    </section>

</div>
Enter fullscreen mode Exit fullscreen mode

Within the code above, x-intersect directive used with .threshold modifier to control the threshold property of the underlying Intersection Observer. For example to trigger an intersection after half of the element has entered the page, .threshold.50 can be used, and that will execute expression $refs.img1.src = $refs.img1.dataset.img to load the image.


Full HTML Code
Full JS Code
Working Example

Your feedback is much appreciated, thank you for reading.

Top comments (0)

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

⭐️🎀 JavaScript Visualized: Promises & Async/Await

async await