Lazy Image Loading Reveal

How to Create a Lazy Image Loading Reveal with Vanilla JavaScript

html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./app.js" defer></script>
    <link rel="stylesheet" href="./style.css">
    <title>Document</title>
</head>

<body>
    <div id="img-container">

        <div class="img-wrapper">
            <img src="./img/blur-img/imagen-low-01.png" class="preview" width="300" height="200">
            <img data-src="https://images.unsplash.com/photo-1748701821466-0b9f8bf839ac?q=80&w=1451&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
                class="real" loading="lazy" width="300" height="200">
        </div>

        <div class="img-wrapper">
            <img src="./img/blur-img/imagen-low-02.png" class="preview" width="300" height="200">
            <img data-src="https://images.unsplash.com/photo-1596833504535-ca1a5a4b5c57?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDF8fHxlbnwwfHx8fHw%3D"
                class="real" loading="lazy" width="300" height="200">
        </div>

        <div class="img-wrapper">
            <img src="./img/blur-img/imagen-low-03.png" class="preview" width="300" height="200">
            <img data-src="https://images.unsplash.com/photo-1621825347931-41fe02769c29?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDV8fHxlbnwwfHx8fHw%3D"
                class="real" loading="lazy" width="300" height="200">
        </div>

        <div class="img-wrapper">
            <img src="./img/blur-img/imagen-low-04.png" class="preview" width="300" height="200">
            <img data-src="https://images.unsplash.com/photo-1593194634277-ba2834f5dcbb?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDZ8fHxlbnwwfHx8fHw%3D"
                class="real" loading="lazy" width="300" height="200">
        </div>

        <div class="img-wrapper">
            <img src="./img/blur-img/imagen-low-05.png" class="preview" width="300" height="200">
            <img data-src="https://images.unsplash.com/photo-1642046268189-c53f2903b9c9?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1yZWxhdGVkfDE1fHx8ZW58MHx8fHx8"
                class="real" loading="lazy" width="300" height="200">
        </div>
    </div>
</body>

</html>

css

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

#img-container {
    width: 100%;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    gap: 5px;
    padding: 5px;

    & .img-wrapper {
        width: 300px;
        height: 200px;
        position: relative;
        overflow: hidden;

        & img {
            width: 100%;
            display: block;
            height: auto;
            object-fit: cover;
        }

        & .preview {
            filter: blur(20px);
            position: absolute;
            top: 0;
            left: 0;
            z-index: 1;
            transition: opacity 0.3s ease;
        }

        & .real {
            position: relative;
            z-index: 2;
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        & .real.loaded {
            opacity: 1;
        }
    }
}

javascript

document.querySelectorAll('.real').forEach(img => {
    const realSrc = img.dataset.src;

    const fullImg = new Image();
    fullImg.src = realSrc;

    fullImg.onload = () => {
        img.src = realSrc;
        img.classList.add('loaded');

        const wrapper = img.closest('.img-wrapper');
        const preview = wrapper.querySelector('.preview');
        if (preview) {
            preview.style.opacity = '0';
            setTimeout(() => {
                preview.remove();
            }, 300);
        };
    };
});