1

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?

asked Mar 13, 2025 at 2:19
1
  • 2
    Why do you call this shift effect "parallax"? Even though it has something in common (a very small similarity) and if this is a kind of jargon, this is not parallax. Also, what is the purpose of this shift on hover? What's the point? You can always peek CSS of that size, but... why?! Commented Mar 13, 2025 at 3:02

1 Answer 1

3

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.

answered Mar 13, 2025 at 8:45
Sign up to request clarification or add additional context in comments.

3 Comments

I thought setting it to 0s would make the scroll more responsive. This is pretty much what I'm looking for and I'm surprised how easy this answer was! I've tried copying and pasting the code and couldn't get it to work even though it works on the snippet here. I found that this works on Microsoft Edge and not my default browser Brave where the issue is the image does not move until the mouse stops moving within the image-container. Is there a way to solve this? Thank you!
@Skiddswarmik I can't seem to replicate the issue you're experiencing. I'm using version: 1.76.74 Chromium: 134.0.6998.89 (Brave latest).
I found the reason, it's because my F12 DevTools screen was up; Weird. If the DevTools are open, even on another screen, the mousemove doesn't seem to work. As soon as I close it, the movement works as intended. Thank you again!

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.