Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

230
Views
Problem with using IntersectionObserver to trigger CSS animation

I am trying to use IntersectionObserver to observe the 3 container so that the wipe-enter animation will start when they are inside the viewport one by one.

If I scroll to a container (that is outside the viewport) slowly so that only part of it is inside the viewport, the container keeps flickering until it is fully inside the viewport.

I tried to inspect the container when it is flickering and it seems that the container-animation class is being added and removed constantly until the container is fully inside the viewport.

This is the first time I use IntersectionObserver so I am not sure how the code should be changed to stop them from flickering.

Any help will be appreciated. Thank you.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Animation</title>
    <style>
        .container {
            width: 300px;
            height: 300px;
            background: green;
            margin: 500px auto;
        }

        @keyframes wipe-enter {
            0% { transform: scale(0, .025); }
            50% { transform: scale(1, .025); }
        }

        .container-animation {
            animation: wipe-enter 1s 1;
        }
    </style>
</head>

<body>
    <div class="container"></div>    
    <div class="container"></div>    
    <div class="container"></div>    
</body>

<script>
    // Register IntersectionObserver
    const io = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        // Add 'container-animation' class if observation target is inside viewport
        if (entry.intersectionRatio > 0) {
            entry.target.classList.add('container-animation');
        }
        else {
            // Remove 'container-animation' class
            entry.target.classList.remove('container-animation');
        }
    })
    })

    // Declares what to observe, and observes its properties.
    const containers = document.querySelectorAll('.container');
    containers.forEach((el) => {
        io.observe(el);
    })
</script>
</html>

about 4 years ago · Juan Pablo Isaza
2 answers
Answer question

0

When an element is scaled it still 'takes up' the same space in the page - in the sense that other items are not affected. However, scaling takes place by default from the central point of the element.

So, when an element gets into the viewport your code immediately scales it right down and then gradually increases its height but from the center which will at that time be nearly 150px below (or above) the viewport bottom/top.

So you get told it's gone out of the viewport and you remove the animation. The element goes back to 300px high and so enters the viewport and so on. Hence 'flashing'.

One way to prevent this is to not remove the animation when the item goes out of the viewport but when the animation has finished - then it doesn't matter that it's shrunk through the scaling and isn't in the viewport for part of a second.

But, in order to prevent other elements moving we can't just do this by changing the height of the element, that needs to remain constant. This code scales a before pseudo element on each of the containers.

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Animation</title>
    <style>
        .container {
            width: 300px;
            height: 300px;
            margin: 500px auto;
            position: relative;
        }

        @keyframes wipe-enter {
            0% { transform: scale(0, .025); }
            50% { transform: scale(1, .025); }
            100% { transform: scale(1, 1); }
        }
        .container::before {
          content: '';
          position: absolute;
          width: 100%;
          height: 100%;
          left: 0;
          top: 0;
          background: green;
        }

        .container.container-animation::before {
            animation: wipe-enter 1s 1;
        }
    </style>
</head>

<body>
    <div class="container"></div>    
    <div class="container"></div>    
    <div class="container"></div>    
</body>

<script>
    // Register IntersectionObserver
    const io = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        // Add 'container-animation' class if observation target is inside viewport
        if (entry.intersectionRatio > 0) {
            entry.target.classList.add('container-animation');
        }
    })
    })

    // Declares what to observe, and observes its properties.
    const containers = document.querySelectorAll('.container');
    containers.forEach((el) => {
        io.observe(el);
        el.addEventListener('animationend', function () {
          el.classList.remove('container-animation');
        });
    })
</script>
</html>
about 4 years ago · Juan Pablo Isaza Report

0

Here is a solution after reading A Haworth's answer. I also changed the opacity so that the container does not show up initially.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Animation</title>
    <style>
        .container {
            width: 300px;
            height: 300px;
            background: green;
            margin: 500px auto;
            opacity: 0;
        }

        @keyframes wipe-enter {
            0% {
                transform: scale(0, .025);
            }
            50% {
                transform: scale(1, .025); 
            }
            100% {
                opacity: 1; 
            }
        }

        .container-animation {
            animation: wipe-enter 1s 1;
            animation-fill-mode: forwards;
        }
    </style>
</head>

<body>
    <div class="container-wrapper">
        <div class="container"></div>    
    </div>
    <div class="container-wrapper">
        <div class="container"></div>    
    </div>
    <div class="container-wrapper">
        <div class="container"></div>    
    </div>
</body>

<script>
    // Register IntersectionObserver
    const io = new IntersectionObserver(entries => {
    entries.forEach(entry => {

        const container = entry.target.querySelector('.container');
        
        console.log(entry)
        // Add 'container-animation' class if observation target is inside viewport
        if (entry.intersectionRatio > 0) {
            container.classList.add('container-animation');
        }
        else {
            // Remove 'container-animation' class
            container.classList.remove('container-animation');
        }
    })
    })

    // Declares what to observe, and observes its properties.
    const containers = document.querySelectorAll('.container-wrapper');
    containers.forEach((el) => {
        io.observe(el);
    })
</script>
</html>

about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!