Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

238
Visualizações
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 Respostas
Responde à pergunta

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 Relatório

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 Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda