Search code examples
javascriptcssonclickcss-animationsintersection-observer

Keyframe animations with Intersection Observer run unexpectedly when clicking on child elements with onclick


I want css keyframe animations to run 1 time when the bridge element is in the viewport. Intersect observer does this well, but the issue is the animations fire randomly when clicking on other parts of the element that have onClicks to show new text paragraphs.

The point was to call attention to the user that hey! you can click on these parts of the bridge element by showing a delayed box shadow when main element is in view. Maybe there is a better way to show users that an area is clickable, maybe there is a JS rule I am overlooking? Any suggestions appreciated - Thank you!

Example: https://codepen.io/robyngraha/pen/LYaZMVg

function firstBro() {
  document.getElementById("first-bro-text").style.display = "block";
  document.getElementById("second-bro-text").style.display = "none";
  document.getElementById("third-bro-text").style.display = "none";
  document.getElementById("first-brother").classList.add("bridge-green");
  document.getElementById("second-brother").classList.remove("bridge-green");
  document.getElementById("third-brother").classList.remove("bridge-green");
}

function secondBro() {
  document.getElementById("first-bro-text").style.display = "none";
  document.getElementById("second-bro-text").style.display = "block";
  document.getElementById("third-bro-text").style.display = "none";
  document.getElementById("first-brother").classList.remove("bridge-green");
  document.getElementById("second-brother").classList.add("bridge-green");
  document.getElementById("third-brother").classList.remove("bridge-green");
}

function thirdBro() {
  document.getElementById("first-bro-text").style.display = "none";
  document.getElementById("second-bro-text").style.display = "none";
  document.getElementById("third-bro-text").style.display = "block";
  document.getElementById("first-brother").classList.remove("bridge-green");
  document.getElementById("second-brother").classList.remove("bridge-green");
  document.getElementById("third-brother").classList.add("bridge-green");
}

// animation
const bridge = document.querySelector('.bridge');
const secondBroEl = document.querySelector('#second-brother');
const thirdBroEl = document.querySelector('#third-brother');
const observer = new IntersectionObserver(entries => {
  secondBroEl.classList.toggle('animation', entries[0].isIntersecting);
  thirdBroEl.classList.toggle('animation2', entries[0].isIntersecting);
  console.log(entries)
});

observer.observe(bridge);
.scroll-container {
  max-width: 480px;
}


/* Bridge CSS */

.bridge-container {
  height: 65px;
  color: white;
  border-radius: 8px;
  display: flex;
  gap: 2px;
  justify-content: space-between;
  font-size: 1.3rem;
  background: black;
  cursor: pointer;
  background: linear-gradient(180deg, #28411F 20.31%, #000 100%);
}


/* center div */

#second-brother {
  font-size: 1.8rem;
  width: 80%;
  margin: 0 auto;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  border-left: black solid 2px;
  border-right: black solid 2px;
}

.animation {
  animation-name: highlight;
  animation-duration: 1s;
  animation-iteration-count: 1;
}

@keyframes highlight {
  0% {
    box-shadow: none;
  }
  50% {
    box-shadow: 0 0px 10px 10px orange;
  }
  100% {
    box-shadow: none;
  }
}


/* center changing text  */

#second-brother::after {
  content: "2nd";
  position: relative;
}


/* outer left div */

#first-brother {
  border-radius: 8px 0px 0px 8px;
  width: 20%;
  height: 65px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 20px;
  font-size: 1.5rem;
}


/* outer left div text */

#first-brother::after {
  content: "1st";
  position: relative;
}


/* outer right div */

#third-brother {
  border-radius: 0px 8px 8px 0px;
  width: 20%;
  height: 65px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 20px;
  font-size: 1.5rem;
}

.animation2 {
  animation-name: highlight2;
  animation-delay: .5s;
  animation-duration: 1s;
  animation-iteration-count: 1;
}

@keyframes highlight2 {
  0% {
    box-shadow: none;
  }
  50% {
    box-shadow: 0 0px 10px 10px orange;
  }
  100% {
    box-shadow: none;
  }
}


/* outer right div text */

#third-brother::after {
  content: "3rd";
  position: relative;
}

@media (max-width: 625px) {
  #first-brother-left::after {
    margin-left: 12px;
  }
}

@media (max-width: 480px) {
  #countdown-right,
  #countdown-left,
  #center-battery {
    font-size: 1rem;
  }
}


/* Brothers text */

@keyframes fadeIn {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

#first-bro-text {
  display: block;
  animation: fadeIn .5s;
}

#second-bro-text {
  display: none;
  animation: fadeIn .5s;
}

#third-bro-text {
  display: none;
  animation: fadeIn .5s;
}

.bridge-green {
  background: linear-gradient(180deg, #4A7938 20.31%, #000 100%);
  animation: fadeIn 1s;
}
<div class="scroll-container">
  <!-- For Spacing -->
  <h1>Scroll down....<br />Lorem ipsum dolor sit amet, consectetur adipisicing elit. Temporibus architecto voluptatem numquam, nihil ullam suscipit, odit placeat labore natus maxime laborum aliquam voluptates animi, dicta facere debitis earum beatae nemo.
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Rem totam aut fugiat nostrum animi, sed illum ad minima sint sequi nemo, labore, suscipit et reiciendis fuga corrupti! Provident, tempore ab. Lorem ipsum dolor sit amet consectetur adipisicing
    elit. Reiciendis enim ad quos incidunt culpa necessitatibus eveniet aut earum quia, est iste debitis eos quidem facilis facere? Velit temporibus esse adipisci?</h1>

  <div class="bridge-container bridge">
    <div id="first-brother" class="bridge-green" onclick="firstBro()"></div>
    <div id="second-brother" onclick="secondBro()"></div>
    <div id="third-brother" onclick="thirdBro()"></div>
    <div id="rmg">HELLO</div>
  </div>

  <div id="first-bro-text">
    <p style="color:#225BBE;font-weight:900;">First Brother</p>
    <p>So, the oldest brother, who was a combative man, asked for a wand more powerful than any in existence. A wand that must always win battles for its owner. A wand worthy of a wizard who had conquered Death. So, Death had crossed to an Elder Tree on
      the banks of the river, fashioned a wand from a branch that had hung there, and gave it to the oldest brother</p>
  </div>
  <div id="second-bro-text">
    <p style="color:#225BBE;font-weight:900;">Second Brother</p>
    <p>Then the second brother, who was an arrogant man, decided that he wanted to humiliate Death still further, and asked for the power to recall others from Death. So, Death picked up a stone from the riverbank and gave it to the second brother, and told
      him that the stone would have the power to bring back the dead.</p>
  </div>
  <div id="third-bro-text">
    <p style="color:#225BBE;font-weight:900;">Third Brother</p>
    <p>Finally, Death turned to the third brother. A humble man, he asked for something that would enable him to go forth from that place without being followed by Death. And so it was that Death reluctantly handed over his own Cloak of Invisibility. </p>
  </div>

  <div id="click-me">Click Me</div>
  <!-- For Spacing -->
  <h1>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Temporibus architecto voluptatem numquam, nihil ullam suscipit, odit placeat labore natus maxime laborum aliquam voluptates animi, dicta facere debitis earum beatae nemo. Lorem ipsum dolor sit
    amet consectetur adipisicing elit. Rem totam aut fugiat nostrum animi, sed illum ad minima sint sequi nemo, labore, suscipit et reiciendis fuga corrupti! Provident, tempore ab. Lorem ipsum dolor sit amet consectetur adipisicing elit. Reiciendis enim
    ad quos incidunt culpa necessitatibus eveniet aut earum quia, est iste debitis eos quidem facilis facere? Velit temporibus esse adipisci?</h1>

</div>

I tried setting the animation-iteration-count to 1 in CSS but the animation starts over whenever clicking on 1st, 2nd or 3rd elements that have onClick functions.

I added an onClick function outside of the intersect observation element and the animations do not run, so it must be related to the element with the intersection observer.

I added an element with in the intersection observer without an onclick (Hello text) and the animations do not fire so the issue must be related to elements with an onclick.


Solution

  • The issue is caused by the animation and animation2 classes in combination with the bridge-green class, which all have animations. Whenever your elements come into view, the highlight animation plays. Then when you click you add the bridge-green class and overwrite the animation. Removing the bridge-green class again will trigger the highlight animation again.

    A solution is to expand the Intersection Observer logic only add the animation class once when the elements into view and then stop observering. This will prevent the animation class to be added more than once.

    Then use the animationend to remove the animation class after the animation finishes so that bridge-green can do it's thing without animation getting in the way.

    function firstBro() {
      document.getElementById("first-bro-text").style.display = "block";
      document.getElementById("second-bro-text").style.display = "none";
      document.getElementById("third-bro-text").style.display = "none";
      document.getElementById("first-brother").classList.add("bridge-green");
      document.getElementById("second-brother").classList.remove("bridge-green");
      document.getElementById("third-brother").classList.remove("bridge-green");
    }
    
    function secondBro() {
      document.getElementById("first-bro-text").style.display = "none";
      document.getElementById("second-bro-text").style.display = "block";
      document.getElementById("third-bro-text").style.display = "none";
      document.getElementById("first-brother").classList.remove("bridge-green");
      document.getElementById("second-brother").classList.add("bridge-green");
      document.getElementById("third-brother").classList.remove("bridge-green");
    }
    
    function thirdBro() {
      document.getElementById("first-bro-text").style.display = "none";
      document.getElementById("second-bro-text").style.display = "none";
      document.getElementById("third-bro-text").style.display = "block";
      document.getElementById("first-brother").classList.remove("bridge-green");
      document.getElementById("second-brother").classList.remove("bridge-green");
      document.getElementById("third-brother").classList.add("bridge-green");
    }
    
    // animation
    const bridge = document.querySelector('.bridge');
    const secondBroEl = document.querySelector('#second-brother');
    const thirdBroEl = document.querySelector('#third-brother');
    
    const observer = new IntersectionObserver((entries, observer) => {
      for (const { isIntersecting } of entries) {
        if (isIntersecting) {
          secondBroEl.classList.add('animation');
          thirdBroEl.classList.add('animation');
          
          observer.unobserve(bridge);
        }
      }
    });
    
    observer.observe(bridge);
    
    secondBroEl.addEventListener('animationend', removeAnimationAfterFinish);
    thirdBroEl.addEventListener('animationend', removeAnimationAfterFinish);
    
    function removeAnimationAfterFinish(event) {
      event.target.classList.remove('animation');
    }
    .scroll-container {
      max-width: 480px;
    }
    
    
    /* Bridge CSS */
    
    .bridge-container {
      height: 65px;
      color: white;
      border-radius: 8px;
      display: flex;
      gap: 2px;
      justify-content: space-between;
      font-size: 1.3rem;
      background: black;
      cursor: pointer;
      background: linear-gradient(180deg, #28411F 20.31%, #000 100%);
    }
    
    
    /* center div */
    
    #second-brother {
      font-size: 1.8rem;
      width: 80%;
      margin: 0 auto;
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      border-left: black solid 2px;
      border-right: black solid 2px;
    }
    
    #second-brother.animation {
      animation-name: highlight;
      animation-duration: 1s;
    }
    
    @keyframes highlight {
      0% {
        box-shadow: none;
      }
      50% {
        box-shadow: 0 0px 10px 10px orange;
      }
      100% {
        box-shadow: none;
      }
    }
    
    
    /* center changing text  */
    
    #second-brother::after {
      content: "2nd";
      position: relative;
    }
    
    
    /* outer left div */
    
    #first-brother {
      border-radius: 8px 0px 0px 8px;
      width: 20%;
      height: 65px;
      margin: 0 auto;
      display: flex;
      align-items: center;
      justify-content: center;
      line-height: 20px;
      font-size: 1.5rem;
    }
    
    
    /* outer left div text */
    
    #first-brother::after {
      content: "1st";
      position: relative;
    }
    
    
    /* outer right div */
    
    #third-brother {
      border-radius: 0px 8px 8px 0px;
      width: 20%;
      height: 65px;
      margin: 0 auto;
      display: flex;
      align-items: center;
      justify-content: center;
      line-height: 20px;
      font-size: 1.5rem;
    }
    
    #third-brother.animation {
      animation-name: highlight;
      animation-delay: .5s;
      animation-duration: 1s;
    }
    
    
    /* outer right div text */
    
    #third-brother::after {
      content: "3rd";
      position: relative;
    }
    
    @media (max-width: 625px) {
      #first-brother-left::after {
        margin-left: 12px;
      }
    }
    
    @media (max-width: 480px) {
      #countdown-right,
      #countdown-left,
      #center-battery {
        font-size: 1rem;
      }
    }
    
    
    /* Brothers text */
    
    @keyframes fadeIn {
      0% {
        opacity: 0;
      }
      100% {
        opacity: 1;
      }
    }
    
    #first-bro-text {
      display: block;
      animation: fadeIn .5s;
    }
    
    #second-bro-text {
      display: none;
      animation: fadeIn .5s;
    }
    
    #third-bro-text {
      display: none;
      animation: fadeIn .5s;
    }
    
    .bridge-green {
      background: linear-gradient(180deg, #4A7938 20.31%, #000 100%);
      animation: fadeIn 1s;
    }
    <div class="scroll-container">
      <!-- For Spacing -->
      <h1>Scroll down....<br />Lorem ipsum dolor sit amet, consectetur adipisicing elit. Temporibus architecto voluptatem numquam, nihil ullam suscipit, odit placeat labore natus maxime laborum aliquam voluptates animi, dicta facere debitis earum beatae nemo.
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Rem totam aut fugiat nostrum animi, sed illum ad minima sint sequi nemo, labore, suscipit et reiciendis fuga corrupti! Provident, tempore ab. Lorem ipsum dolor sit amet consectetur adipisicing
        elit. Reiciendis enim ad quos incidunt culpa necessitatibus eveniet aut earum quia, est iste debitis eos quidem facilis facere? Velit temporibus esse adipisci?</h1>
    
      <div class="bridge-container bridge">
        <div id="first-brother" class="bridge-green" onclick="firstBro()"></div>
        <div id="second-brother" onclick="secondBro()"></div>
        <div id="third-brother" onclick="thirdBro()"></div>
        <div id="rmg">HELLO</div>
      </div>
    
      <div id="first-bro-text">
        <p style="color:#225BBE;font-weight:900;">First Brother</p>
        <p>So, the oldest brother, who was a combative man, asked for a wand more powerful than any in existence. A wand that must always win battles for its owner. A wand worthy of a wizard who had conquered Death. So, Death had crossed to an Elder Tree on
          the banks of the river, fashioned a wand from a branch that had hung there, and gave it to the oldest brother</p>
      </div>
      <div id="second-bro-text">
        <p style="color:#225BBE;font-weight:900;">Second Brother</p>
        <p>Then the second brother, who was an arrogant man, decided that he wanted to humiliate Death still further, and asked for the power to recall others from Death. So, Death picked up a stone from the riverbank and gave it to the second brother, and told
          him that the stone would have the power to bring back the dead.</p>
      </div>
      <div id="third-bro-text">
        <p style="color:#225BBE;font-weight:900;">Third Brother</p>
        <p>Finally, Death turned to the third brother. A humble man, he asked for something that would enable him to go forth from that place without being followed by Death. And so it was that Death reluctantly handed over his own Cloak of Invisibility. </p>
      </div>
    
      <div id="click-me">Click Me</div>
      <!-- For Spacing -->
      <h1>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Temporibus architecto voluptatem numquam, nihil ullam suscipit, odit placeat labore natus maxime laborum aliquam voluptates animi, dicta facere debitis earum beatae nemo. Lorem ipsum dolor sit
        amet consectetur adipisicing elit. Rem totam aut fugiat nostrum animi, sed illum ad minima sint sequi nemo, labore, suscipit et reiciendis fuga corrupti! Provident, tempore ab. Lorem ipsum dolor sit amet consectetur adipisicing elit. Reiciendis enim
        ad quos incidunt culpa necessitatibus eveniet aut earum quia, est iste debitis eos quidem facilis facere? Velit temporibus esse adipisci?</h1>
    
    </div>