javascripthtmlcssevent-listenerparallax

How do I make my Vertical Image Animation Smoother?


I'm trying to make a parallax effect similar to the hover-over effects on the images on this site: https://www.framer.com/gallery/categories/portfolio

and here is the code I have so far:

document.querySelectorAll('.image-container').forEach(item => {
    const img = item.querySelector('img');

    item.addEventListener('mousemove', (e) => {
        const rect = item.getBoundingClientRect();
        const mouseY = e.clientY - rect.top; // Mouse Y position relative to the item
        const halfHeight = rect.height / 2; // Midpoint of the element
        const imgHeight = img.height - rect.height
        const mousePercentage = mouseY/imgHeight*100

        img.style.transform = `translateY(-${mousePercentage}px)`; // Move up
        img.style.transition = "transform 0s ease-out"
        
    });

    item.addEventListener('mouseleave', () => {
        img.style.transform = 'translateY(0)'; // Reset position when mouse leaves
        img.style.transition = " transform 2s ease-out"
    });

    item.addEventListener('mouseenter', () =>{
        img.style.transition = "transform 2s ease-out"
    })
});
#display-grid{
    display: grid;
    width: 80%;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(24%, 1fr));

}

.cell{
    border: 1px solid #EEEEEE;
    border-radius: 1rem;
    width: 100%;
    overflow-y: hidden;
    position: relative;
  overflow: hidden;
  border-radius: 12px;
  aspect-ratio: 1 / 1.2;
}

.cell .image-container{
  width: 100%;
  height: 90%;
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: 1rem;
  overflow-y: hidden;

}

.cell img {
  width: 100%;
  height: auto;
  object-fit: cover;
  object-position: top;
  border-radius: 1rem;
}
<div id="display-grid">
    <div class="cell">
      <div class="image-container">
        <img id="website-preview" src="https://framerusercontent.com/images/B7ykrtzOQa5hEXGFIhcq8gyaE.jpg?scale-down-to=1024">
      </div>
      <p>Image Title</p>
    </div>
    <div class="cell">
      <div class="image-container">
        <img id="website-preview" src="https://framerusercontent.com/images/B7ykrtzOQa5hEXGFIhcq8gyaE.jpg?scale-down-to=1024">
      </div>
      <p>InfoSwap</p>
    </div>
</div>

I have made it as far as having the image scroll within the frame up and down a variable % depending on where the mouse is in the image-container, but I find when the mouse enters the image-container, the image jumps to the coordinates. If I add a transition time to my stylesheet or to the "mousemove" Event Listener, I find the image waits for the mouse to stop moving before slowly moving to where the image needs to be.

What can I do so that the image moves smoothly and consistently on entry, moving, and leaving the container?


Solution

  • You're close to achieving the same effect seen on the provided site. You just need to change the "mouse move" state part, inside the JavaScript, from this:

    img.style.transition = "transform 0s ease-out"
    

    to this:

    img.style.transition = "transform 2s ease-out" 
    

    Like so:

    document.querySelectorAll('.image-container').forEach(item => {
        const img = item.querySelector('img');
    
        item.addEventListener('mousemove', (e) => {
            const rect = item.getBoundingClientRect();
            const mouseY = e.clientY - rect.top; // Mouse Y position relative to the item
            const halfHeight = rect.height / 2; // Midpoint of the element
            const imgHeight = img.height - rect.height
            const mousePercentage = mouseY/imgHeight*100
    
            img.style.transform = `translateY(-${mousePercentage}px)`; // Move up
            img.style.transition = "transform 2s ease-out" // this is the changed part
            
        });
    
        item.addEventListener('mouseleave', () => {
            img.style.transform = 'translateY(0)'; // Reset position when mouse leaves
            img.style.transition = " transform 2s ease-out"
        });
    
        item.addEventListener('mouseenter', () =>{
            img.style.transition = "transform 2s ease-out"
        })
    });
    #display-grid {
      display: grid;
      width: 80%;
      gap: 1rem;
      grid-template-columns: repeat(auto-fit, minmax(24%, 1fr));
    
    }
    
    .cell {
      border: 1px solid #EEEEEE;
      border-radius: 1rem;
      width: 100%;
      overflow-y: hidden;
      position: relative;
      overflow: hidden;
      border-radius: 12px;
      aspect-ratio: 1 / 1.2;
    }
    
    .cell .image-container {
      width: 100%;
      height: 90%;
      position: relative;
      aspect-ratio: 1 / 1;
      border-radius: 1rem;
      overflow-y: hidden;
    
    }
    
    .cell img {
      width: 100%;
      height: auto;
      object-fit: cover;
      object-position: top;
      border-radius: 1rem;
    }
    <div id="display-grid">
      <div class="cell">
        <div class="image-container">
          <img id="website-preview" src="https://framerusercontent.com/images/B7ykrtzOQa5hEXGFIhcq8gyaE.jpg?scale-down-to=1024">
        </div>
        <p>Image Title</p>
      </div>
      <div class="cell">
        <div class="image-container">
          <img id="website-preview" src="https://framerusercontent.com/images/B7ykrtzOQa5hEXGFIhcq8gyaE.jpg?scale-down-to=1024">
        </div>
        <p>InfoSwap</p>
      </div>
    </div>

    Known Issue:

    Sometimes, when the browser's DevTools is open, the performance throttling kicks in to limit how frequently the mousemove events are fired, especially if the browser detects excessive rendering work.