javascripthtmljquerycssparallax

The parallax animation delays when the image is scrolled into position


I have 3 images that have a parallax animated effect. The problem is that the parallax effect has some delay*

*as @Roko C. Buljan pointed out, correctly.

How to fix?

Here's my code - the effect is only visible under 670px (as can be seen without putting on the full screen mode, here.)

document.addEventListener("DOMContentLoaded", function() {
  const parallaxContainer = document.querySelector(".parallax-container");
  const windowWidth = window.innerWidth;
  let isScrolling = false; // Flag to track scrolling activity
  let isScrollingStopped = false; // Flag to track when scrolling has stopped

  // Define the Intersection Observer options
  const observerOptions = {
    root: null,
    rootMargin: "0px",
    threshold: 1 // Trigger when the entire image is not in the viewport
  };

  // Create the Intersection Observer
  const observer = new IntersectionObserver(function(entries) {
    entries.forEach((entry) => {
      const image = entry.target;
      if (windowWidth < 670) {
        if (entry.isIntersecting) {
          // Image is in viewport, remove grayscale filter
          image.style.filter = "grayscale(0)";
          // Pause the parallax animation by removing the animation CSS property
          image.style.animation = "none";
        } else {
          // Image is not in viewport, apply grayscale filter
          image.style.filter = "grayscale(1)";
          // Resume the parallax animation by restoring the animation CSS property
          image.style.animation = "";
        }
      }
    });
  }, observerOptions);

  // Get all the parallax images and observe each one
  const parallaxImages = document.querySelectorAll(".parallax-image");

  parallaxImages.forEach((item) => {
    observer.observe(item);
  });

  // Function to update the parallax effect
  function updateParallax() {
    const scrollPosition = parallaxContainer.scrollLeft;

    // Apply the parallax effect to each item with a background image
    parallaxImages.forEach((item) => {
      const coefficient = parseFloat(item.dataset.parallaxCoefficient) || 1.0;
      const xOffset = -scrollPosition * coefficient;
      item.style.backgroundPositionX = `${xOffset}px`;
    });

    // Continue updating the parallax effect while scrolling
    if (isScrolling && !isScrollingStopped) {
      requestAnimationFrame(updateParallax);
    }
  }

  // Function to handle scrolling end
  function handleScrollEnd() {
    isScrollingStopped = true;
  }

  // Start the parallax effect when scrolling begins
  parallaxContainer.addEventListener("scroll", function() {
    isScrollingStopped = false;
    // Set the flag to true when scrolling starts
    if (!isScrolling) {
      isScrolling = true;
      // Request the first frame of the parallax animation
      requestAnimationFrame(updateParallax);
    }

    // Clear the flag after a short delay (adjust the delay time as needed)
    clearTimeout(parallaxContainer.dataScrollTimer);
    parallaxContainer.dataScrollTimer = setTimeout(function() {
      isScrolling = false;
      handleScrollEnd(); // Call the function to handle scrolling end
    }, 100); // Adjust the delay (in milliseconds) to control the duration of parallax after scroll ends
  });
});
.what-we-do {
  padding: 88px 60px 40px 60px;
}


.what-we-do .upper-div h2 {
  margin: 0;
}

.what-we-do h2 {
  font-family: "lato";
  font-style: normal;
  font-weight: 300;
  font-size: 40px;
  line-height: 48px;
}

.what-we-do-container {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.pics-div {
  display: flex;
  flex-direction: row;
  gap: 8px;
  height: 576px;
  overflow: hidden;
}



.pics-div p {
  font-family: Lato;
  font-size: 20px;
  font-weight: 400;
  line-height: 28px;
  letter-spacing: 0em;
  text-align: left;
}

.pic1,
.pic2,
.pic3 {
  cursor: pointer;
}

.pic1 {
  filter: grayscale(1);
  background-image: url(https://picsum.photos/200/300);
  background-size: cover;
  width: 30%;
  padding: 32px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  color: white;
  transition-duration: 1s;
}

.pic2 {
  filter: grayscale(1);
  background-image: url(https://picsum.photos/200/300);
  background-size: cover;
  width: 30%;
  padding: 32px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  color: white;
  transition-duration: 1s;
}

.pic3 {
  filter: grayscale(1);
  background-image: url(https://picsum.photos/200/300);
  background-size: cover;
  width: 30%;
  padding: 32px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  color: white;
  transition-duration: 1s;
}

.pic-upper-text {
  display: flex;
  gap: 32px;
  align-items: center;
  transition-duration: 1s;
  position: relative;
}

.pic1:hover {
  width: 50%;
  filter: grayscale(0);
  transition-duration: 1s;
  .pic-upper-text {
    gap: 150px;
  }
}

.pic2:hover {
  width: 50%;
  filter: grayscale(0);
  transition-duration: 1s;
  .pic-upper-text {
    gap: 150px;
  }
}

.pic3:hover {
  width: 50%;
  filter: grayscale(0);
  transition-duration: 1s;
  .pic-upper-text {
    gap: 150px;
  }
}

section {
  display: block;
}

.what-we-do-container {
  box-sizing: border-box;
  height: auto;
}

.what-we-do-container {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.pics-div {
  height: 100%;
  width: 100%;
  overflow-x: scroll;
  margin-bottom: 74px;
}

.pics-div {
  display: flex;
  flex-direction: row;
  gap: 8px;
  height: 576px;
  overflow: hidden;
  overflow-x: hidden;
}

.parallax-container {
  display: flex;
  overflow-x: scroll;
  scroll-behavior: auto;
  -webkit-overflow-scrolling: auto;
  scroll-snap-type: x mandatory;
}

.parallax-image {
  scroll-snap-align: center;
  flex: auto;
}

.pic1:hover,
.pic2:hover,
.pic3:hover {
  width: 50%;
  filter: grayscale(0);
  transition-duration: 0.5s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="what-we-do">
  <div class="what-we-do-container">
    <div class="upper-div">
      <h2>Main title</h2>
    </div>
    <div class="pics-div parallax-container">
      <div ontouchstart="changeBullet1()" onclick="window.location.href = 'lorem-ipsum';" class="pic1 parallax-image" data-parallax-coefficient="0.2">
        <div class="pic-upper-text">
          <h3>Title1</h3>
          <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M8.33398 20H31.6673" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
            <path d="M20 8.33325L31.6667 19.9999L20 31.6666" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
            </svg>
        </div>
        <p>
          Lorem ipsum
        </p>
      </div>
      <div ontouchstart="changeBullet2()" onclick="window.location.href = 'lorem-ipsum';" class="pic2 parallax-image" data-parallax-coefficient="0.2">
        <div class="pic-upper-text">
          <h3>Title 2</h3>
          <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M8.33398 20H31.6673" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
            <path d="M20 8.33325L31.6667 19.9999L20 31.6666" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
            </svg>
        </div>
        <p>
          Lorem ipsum sit dolor amet
        </p>
      </div>
      <div ontouchstart="changeBullet3()" onclick="window.location.href = 'lorem-ipsum';" class="pic3 parallax-image" data-parallax-coefficient="0.2">
        <div class="pic-upper-text">
          <h3>Title 3</h3>
          <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M8.33398 20H31.6673" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
            <path d="M20 8.33325L31.6667 19.9999L20 31.6666" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
            </svg>
        </div>

        <p>
          Lorem ipsum sit dolor amet
        </p>
      </div>
    </div>
    <div class="bullet-container">
      <div class="bullet-box">
        <div id="bullet1" class="bullet"></div>
        <div id="bullet2" class="bullet"></div>
        <div id="bullet3" class="bullet"></div>
      </div>
    </div>
  </div>
</section>

Here it can be seen live: [I removed the link for privacy issues] at the "What we do" section.


Solution

  • Every .pic1,2,3 's transition-duration: 1s; is interfering for what you see as the parallax for the backgroundPosition. Use only the duration for the properties that actually need one, otherwise the default is all. And you don't want that for the background's position. So use only the one for filter:

    also, don't copy/paste code in CSS!
    Use a single rule like

    /* no need for .pic1, pic2, pic3 { as well */
    
    .pic {
      cursor: pointer;
      background-size: cover;
      width: 30%;
      padding: 32px;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      color: white;
      transition: filter 1s; /* add transition only to filter! */
    }
    
    .pic1 {
      background-image: url(https://www.rgu.hu/CMF/jpg/laser-sci1.jpg);
    }
    
    .pic2 {
      background-image: url(https://www.rgu.hu/CMF/jpg/life-sci1.jpg);
    }
    
    .pic3 {
      background-image: url(https://www.rgu.hu/CMF/jpg/data-sci1.jpg);
    }
    

    and obviously add that class to all your HTML pic elements:

    class="pic pic1 parallax-image"