Webflow Tutorial
Build an Interactive Parallax Mouse Hover Effect: A Step-by-Step Guide
In this post, we’ll explore how to create a dynamic parallax effect on hover using HTML, CSS, and JavaScript.
This effect will respond to mouse movements, making elements appear to follow the cursor with subtle shifts, enhancing depth and engagement
Step 1: HTML Structure for Parallax
The HTML structure forms the foundation of the parallax effect by organizing the main image, circle overlay, and other items that will respond to mouse movements.
Assigning specific classes to these elements prepares them for the CSS styling and JavaScript interactions that follow.
Wrapper and Main Image Setup
The hero_animation_wrapper
serves as the main container, holding all elements in the parallax animation.
<div class="hero_animation_wrapper">
<!-- Main Image -->
<img
src="<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/673397a7705e12a6a83fbaa3_Hero%20Image.png>"
loading="lazy"
sizes="(max-width: 479px) 100vw, (max-width: 767px) 92vw, (max-width: 991px) 599.984375px, 43vw"
srcset="
<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/673397a7705e12a6a83fbaa3_Hero%20Image-p-500.png> 500w,
<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/673397a7705e12a6a83fbaa3_Hero%20Image-p-800.png> 800w,
<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/673397a7705e12a6a83fbaa3_Hero%20Image.png> 818w
"
alt=""
class="hero_animation_main-image"
/>
This code creates the wrapper div
and sets the main image with responsive sizes for optimal performance.
Background Circle Layer
The circle overlay, which adds depth to the parallax effect, is positioned behind the main image.
<!-- Circle Image -->
<img
src="<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/67339861994cabecbc83df55_Circle.svg>"
loading="lazy"
alt=""
class="hero_animation_circle"
/>
This circle element, placed behind the main image, gives the design a layered look, enhancing the visual appeal of the parallax effect.
Parallax Items
The individual parallax items, assigned the .hero_animation_items
class, are set to move in response to mouse movements. Each item has an additional class (e.g., is-1
, is-2
) for custom styling or positioning.
<!-- Individual images that will move with parallax effect -->
<img
src="<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/6733950ec894756714d2315f_Roles-1.avif>"
loading="lazy"
alt=""
class="hero_animation_items is-1"
style="transform: translate(0px, 0px)"
/>
<img
src="<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/67339510613d1c39170d52c3_Roles-4.avif>"
loading="lazy"
alt=""
class="hero_animation_items is-2"
style="transform: translate(0px, 0px)"
/>
<img
src="<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/6733950eb1fce2c3a788302e_Roles-2.avif>"
loading="lazy"
alt=""
class="hero_animation_items is-3"
style="transform: translate(0px, 0px)"
/>
<img
src="<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/6733950e13b6d12279e9d90d_Roles-5.avif>"
loading="lazy"
alt=""
class="hero_animation_items is-4"
style="transform: translate(0px, 0px)"
/>
<img
src="<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/6733950e127b15e867c57bc5_Roles-3.avif>"
loading="lazy"
alt=""
class="hero_animation_items is-5"
style="transform: translate(0px, 0px)"
/>
<img
src="<https://cdn.prod.website-files.com/64712356c79d959fca2e97a0/6733950e1bb8c8f294694ed2_Roles-6.avif>"
loading="lazy"
alt=""
class="hero_animation_items is-6"
style="transform: translate(0px, 0px)"
/>
</div>
Each .hero_animation_items
image will respond to the mouse’s movements, creating the parallax effect.
The style="transform: translate(0px, 0px)"
attribute initializes each item’s position, setting it up for dynamic transformations in JavaScript.
Step 2: Adding Smooth CSS Transitions
CSS styling enhances each element’s appearance and adds smooth transitions, ensuring graceful movement in response to mouse movements. Absolute positioning for each .hero_animation_items
element provides precise control over their placement relative to the main image. Below, we’ll break down each part of the CSS for clarity.
Wrapper and Flexbox Centering
The .hero_animation_wrapper
styles center all elements within the wrapper for balanced positioning.
.hero_animation_wrapper {
justify-content: center;
align-items: center;
display: flex;
position: relative;
}
Circle Element Styling
To create depth, the circle behind the main image is set to absolute
position with z-index: 0
, aligning it behind the main image. This element is scaled to fit nicely within the wrapper.
.hero_animation_circle {
z-index: 0;
object-fit: scale-down;
align-self: center;
max-width: 60%;
position: absolute;
top: 16%;
bottom: 0%;
}
General Image Styling
These general styles apply to all images, ensuring they’re responsive, properly aligned, and have a consistent look with rounded corners and no borders.
img {
max-width: 100%;
display: inline-block;
vertical-align: middle;
border: 0;
border-radius: var(--size--main-radius);
box-sizing: border-box;
overflow-clip-margin: content-box;
overflow: clip;
}
Main Image Styling
The .hero_animation_main-image
style is specifically for the main image (center element), positioned relative to the wrapper and layered above the circle for visual prominence.
.hero_animation_main-image {
z-index: 2;
object-fit: fill;
position: relative;
}
Parallax Item Styling
Each .hero_animation_items
element is styled for smooth, contained movement, with absolute positioning and size constraints. The transition property ensures a smooth parallax effect as the mouse moves.
.hero_animation_items {
object-fit: contain;
width: 50%; /* Adjusts size relative to wrapper for responsiveness */
max-height: 80px; /* Keeps images proportional without overflow */
position: absolute; /* Places each item accurately within the design */
transition: transform 0.1s ease-out; /* Smooth movement for parallax effect */
}
Specific Item Positioning
Here’s how each .hero_animation_items
element is individually positioned within the wrapper. Using position: absolute
and adjusting the inset
or top
and left
properties lets each item align accurately around the main image.
.hero_animation_items.is-1 {
z-index: 1;
position: absolute;
inset: 0% 0% auto auto;
}
.hero_animation_items.is-2 {
z-index: 2;
position: absolute;
inset: 36% 0% 0% auto;
}
.hero_animation_items.is-3 {
z-index: 2;
position: absolute;
inset: auto 0% 0% auto;
}
.hero_animation_items.is-4 {
z-index: 2;
width: 40%;
position: absolute;
inset: auto auto 0% 0%;
}
.hero_animation_items.is-5 {
z-index: 2;
position: absolute;
inset: 51% auto 0% 0%;
}
.hero_animation_items.is-6 {
position: absolute;
top: 15%;
left: -5%;
}
Summary
With these CSS styles, each item’s movement will appear smooth.
Absolute positioning enables precise placement within the wrapper, ensuring each element maintains its place as the parallax effect is applied.
This combination of styles enhances the overall visual quality and depth of the interactive effect.
Step 3: JavaScript for Mouse Tracking and Parallax Logic
This JavaScript code activates the parallax effect by tracking the mouse’s position and applying subtle movements to each item.
By adjusting each item’s transformation based on its position relative to the center of the wrapper, we achieve an interactive, layered movement effect.
1. Initial Setup and Variable Definitions
To begin, we need to select the wrapper and all the items that will be animated.
By storing these elements in variables, we make it easy to apply transformations to each item in response to mouse movements.
// Select the wrapper element and all items to be animated
const wrapper = document.querySelector('.hero_animation_wrapper');
const parallaxItems = document.querySelectorAll('.hero_animation_items');
2. Calculating and Storing Center Coordinates on Mouse Enter
When the mouse enters the wrapper area, we calculate the center coordinates of the wrapper.
These values serve as the anchor point for our effect, making each item’s movement relative to this center.
By storing these coordinates as data attributes on the wrapper, we avoid recalculating them during every mouse movement, which helps maintain a smooth effect.
wrapper.addEventListener('mouseenter', () => {
// Calculate the center coordinates of the wrapper on mouse enter
const rect = wrapper.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// Store center coordinates as data attributes for smooth movement calculations
wrapper.dataset.centerX = centerX;
wrapper.dataset.centerY = centerY;
});
3. Applying Parallax Effect on Mouse Move
With the mousemove
event, we capture the mouse’s position within the wrapper and calculate its offset from the center coordinates.
Each item is then translated based on this offset, with an intensity multiplier that gives each item a unique degree of movement, adding depth to the effect.
This calculation is done for each item independently, creating a layered parallax effect as the mouse moves.
wrapper.addEventListener('mousemove', (e) => {
// Retrieve the stored center coordinates
const centerX = parseFloat(wrapper.dataset.centerX);
const centerY = parseFloat(wrapper.dataset.centerY);
// Calculate the offset of the mouse position from the center
const offsetX = (e.clientX - centerX) / 40; // Adjust for desired movement intensity
const offsetY = (e.clientY - centerY) / 40;
// Apply the offset to each item, with a unique intensity for depth
parallaxItems.forEach((item, index) => {
const intensity = (index + 1) * 2;
item.style.transform = `translate(${offsetX * intensity}px, ${offsetY * intensity}px)`;
});
});
4. Resetting Transformations on Mouse Leave
When the mouse leaves the wrapper, this final event listener resets each item’s transformation.
This brings all items back to their starting positions, ensuring a visually clean effect that only activates within the wrapper boundaries.
wrapper.addEventListener('mouseleave', () => {
// Reset each item’s position to the starting point on mouse leave
parallaxItems.forEach((item) => {
item.style.transform = 'translate(0, 0)';
});
});
Summary of All Steps
By organizing the code into distinct event-driven actions—setting up, calculating the center, tracking movement, and resetting—we create a responsive and user-friendly parallax effect.
Each step contributes to an immersive and visually dynamic interaction.
JavaScript Code in one Block
Here is the JavaScript code in one block so you can copy and paste it easier.
const wrapper = document.querySelector('.hero_animation_wrapper');
const parallaxItems = document.querySelectorAll('.hero_animation_items');
wrapper.addEventListener('mouseenter', () => {
// Get the bounding rectangle of the wrapper when the mouse enters
const rect = wrapper.getBoundingClientRect();
// Center coordinates of the wrapper
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// Store these values as data attributes on the wrapper for use during mousemove
wrapper.dataset.centerX = centerX;
wrapper.dataset.centerY = centerY;
});
wrapper.addEventListener('mousemove', (e) => {
// Get stored center coordinates
const centerX = parseFloat(wrapper.dataset.centerX);
const centerY = parseFloat(wrapper.dataset.centerY);
// Calculate offset from the center of the wrapper to the mouse position
const offsetX = (e.clientX - centerX) / 40; // Adjust divisor for intensity
const offsetY = (e.clientY - centerY) / 40;
// Apply the transformation to each item element for a parallax effect
parallaxItems.forEach((item, index) => {
const intensity = (index + 1) * 2; // Different intensity for each role
item.style.transform = `translate(${offsetX * intensity}px, ${offsetY * intensity}px)`;
});
});
wrapper.addEventListener('mouseleave', () => {
// Reset transformations when the mouse leaves
parallaxItems.forEach((item) => {
item.style.transform = 'translate(0, 0)';
});
});
How Does This Code Create a Parallax Effect?
The code creates a parallax effect by moving each item
element in response to the mouse’s position relative to the center of the wrapper. Here’s how each part contributes to the effect:
- Layered Depth:
- Each
item
element has a unique intensity multiplier based on its position in the list. This multiplier controls how much each element moves relative to the mouse’s position. - Elements with a higher intensity multiplier move more than those with a lower multiplier, creating a layered effect. This mimics how objects closer to a viewer appear to move faster than distant objects, which is the essence of a parallax effect.
- As a result, the elements appear to be at different “depths,” enhancing the illusion of a 3D space.
- Each
- Smooth, Responsive Movement:
- As the mouse moves within the wrapper, small transformations are applied to each
item
element in real time. The continuousmousemove
tracking makes the elements follow the mouse smoothly and responsively. - Because the movement is tied to the distance between the mouse and the center of the wrapper, the effect looks natural and centered on the element, regardless of where the wrapper is on the page.
- As the mouse moves within the wrapper, small transformations are applied to each
- Interactive Feel:
- The parallax effect is directly responsive to the user’s actions. The elements move in harmony with the mouse, making the page feel interactive and dynamic.
- This direct response to mouse movement adds a sense of depth and immersion, encouraging user engagement.
Detailed Parallax Code Walkthrough: Controlling and Customizing Intensity
This section dives deeper into how each part of the code contributes to the parallax effect, giving readers an understanding of the intensity calculation, and then explores three different ways to adjust this intensity.
Step-by-Step Breakdown of Key Code Elements
(1) Looping Through Each item
Element
This section explains how parallaxItems.forEach()
iterates over each item
element, passing index
to set a unique intensity for each element.
parallaxItems.forEach((item, index) => { ... })
:parallaxItems
is assumed to be a collection (like an array or NodeList) of elements representing each “item” in your setup..forEach()
is an array method that loops over each item inparallaxItems
, passing the current item (item
) and its position in the array (index
) to the callback function.item
represents each individual element inparallaxItems
, andindex
is its zero-based position (0, 1, 2, etc.).
(2) Setting Intensity Based on Index
This covers the calculation of intensity
and how it determines movement speed for each element, with higher index
values resulting in stronger effects.
const intensity = (index + 1) * 2;
:- This line calculates the intensity for each element’s movement based on its position in the array.
index + 1
is used to ensure that the lowest intensity starts at 1 rather than 0. Ifindex
is 0,intensity
becomes2
(since(0 + 1) * 2 = 2
), and it increases for each subsequent element.- Multiplying by
2
scales the effect so that each element moves with a progressively stronger intensity. - Effect of
intensity
: Eachitem
element will have a unique intensity, making it move at a different rate than the others, creating a layered or “depth” effect where elements appear to move at varying speeds.
(3) Applying the CSS Transform
This part explains how transform
uses offsetX
and offsetY
to move elements horizontally and vertically in response to the mouse position, creating the parallax effect.
item.style.transform = translate(${offsetX * intensity}px, ${offsetY * intensity}px)
;``:- This line applies a CSS
transform
to eachitem
element, using the calculatedoffsetX
andoffsetY
values to determine how far to move each element. ${offsetX * intensity}px
: The horizontal translation amount.offsetX
is multiplied byintensity
, so eachitem
element moves a bit more or less than the others, depending on its position in the array.${offsetY * intensity}px
: The vertical translation amount, adjusted similarly byintensity
.- Result: Each
item
element is translated by a unique amount based on the mouse’s position relative to the center, creating a parallax effect where elements at different depths respond to the mouse movement with varying intensities.
- This line applies a CSS
Fine-Tuning the Intensity of the Parallax Effect
The intensity of the effect is controlled by the divisor value in these lines:
const offsetX = (clientX - centerX) / 40;
const offsetY = (clientY - centerY) / 40;
A higher divisor will make the effect less intense, as it reduces the movement distance for each item
element.
Conversely, a lower divisor makes the effect more intense, causing each item
element to move more with the mouse.
For example:
- Less intense effect: Increase the divisor, e.g.,
offsetX = (clientX - centerX) / 60;
which reduces movement. - More intense effect: Decrease the divisor, e.g.,
offsetX = (clientX - centerX) / 10;
which increases movement
Example with Less Intensity
Remember, this reduces the movement of the items.
const offsetX = (clientX - centerX) / 60;
const offsetY = (clientY - centerY) / 60;
Adjust this value to find the level of intensity that feels just right for your design.
3 Ways to Control Intensity for Each Element
Depending on your use case, you have several options to set the intensity (how far an element moves). The easiest and least customizable is the DOM order.
If want to set the intensity of each element independent from its order in the DOM, you’ll need to use other methods.
In our case, there are at least two other methods we can use - (1) a class based method or (2) an attribute based method.
We’ll explore all three below as well as look at when you might want to use each.
(1) Using DOM Order for Intensity Control
This method leverages the elements’ order in the DOM. Each item
element’s movement intensity is set based on its index
in the NodeList
. Rearranging elements in the HTML will change their intensity.
When to Use
Simple scenarios where order-based intensity is acceptable.
How does it work?
When you use JavaScript to select elements with a method like document.querySelectorAll
or element.getElementsByClassName
, it returns a collection (like a NodeList
or HTMLCollection
) where the order of the elements matches their order in the HTML document (the DOM).
So, for example:
<div class="hero_animation_wrapper">
<img src="role1.png" class="hero_animation_items">
<img src="role2.png" class="hero_animation_items">
<img src="role3.png" class="hero_animation_items">
</div>
If you select all .hero_animation_items elements like this:
const parallaxItems = document.querySelectorAll('.hero_animation_items');
The parallaxItems
collection will contain each element in the same order they appear in the HTML:
parallaxItems[0]
will be the first.hero_animation_items
element (role1.png
)parallaxItems[1]
will be the second.hero_animation_items
element (role2.png
)parallaxItems[2]
will be the third.hero_animation_items
element (role3.png
)
The index in parallaxItems.forEach((item, index) => {...})
directly reflects this DOM order.
So, if you rearrange the elements in the HTML, their index values will change accordingly, which can alter the intensity of each element’s movement in the parallax effect.
This order-based indexing can be useful if you want specific elements to have different effects based on their positions without having to assign unique classes or attributes.
2. Using Class-Based Intensity
You can assign specific classes to each element to represent different intensity levels, such as intensity-low
, intensity-medium
, or intensity-high
.
When to Use
When you want to control the movement intensity for each element independently and want to do so by assigning specific combo classes to the elements.
How does it work?
Example HTML with Classes
<div class="hero_animation_wrapper">
<img src="role1.png" class="hero_animation_items intensity-low">
<img src="role2.png" class="hero_animation_items intensity-medium">
<img src="role3.png" class="hero_animation_items intensity-high">
</div>
JavaScript Using Class-Based Intensity
In your JavaScript, you can set different intensity values based on the class of each element:
parallaxItems.forEach((item) => {
let intensity;
// Set intensity based on the assigned class
if (item.classList.contains('intensity-low')) {
intensity = 2;
} else if (item.classList.contains('intensity-medium')) {
intensity = 4;
} else if (item.classList.contains('intensity-high')) {
intensity = 6;
} else {
intensity = 3; // Default intensity if no class is matched
}
item.style.transform = `translate(${offsetX * intensity}px, ${offsetY * intensity}px)`;
});
In this example, each element moves with a different intensity based on its assigned class. This setup allows you to control movement by simply adding or changing classes in the HTML.
3. Using Attribute-Based Intensity
Another approach is to use custom data-*
attributes to set the intensity directly on each element. This approach allows for more granular control and doesn’t require adding multiple classes.
When to use
When you don’t want to add combo classes and you need more fine-tuned control over the movement of each individual element.
How does it work?
Example HTML with Custom Attributes
<div class="hero_animation_wrapper">
<img src="role1.png" class="hero_animation_items" data-intensity="1.5">
<img src="role2.png" class="hero_animation_items" data-intensity="3">
<img src="role3.png" class="hero_animation_items" data-intensity="5">
</div>
JavaScript Using Attribute-Based Intensity
You can access these custom attributes using JavaScript and use their values directly in the transform calculation:
parallaxItems.forEach((item) => {
// Get the intensity from the custom attribute, or use a default value
const intensity = parseFloat(item.getAttribute('data-intensity')) || 2;
// Apply the parallax effect based on the custom intensity
item.style.transform = `translate(${offsetX * intensity}px, ${offsetY * intensity}px)`;
});
In this case:
data-intensity
specifies a unique intensity for each element, making it easy to fine-tune the movement effect.- You can control each element’s movement directly by adjusting the
data-intensity
attribute in the HTML, without needing to modify JavaScript.
Advantages of Using Classes or Attributes
- Classes: Useful if you want predefined movement categories (e.g.,
low
,medium
,high
), making it simple to apply consistent effects across multiple elements. - Attributes: Ideal for fine-grained control, allowing you to set any intensity level without being restricted to a specific set of classes.
Summary
Using classes or attributes makes it easy to control each element’s movement independently, regardless of its DOM position.
This setup is more flexible and scalable, especially when you want to customize the behavior of individual elements without modifying the JavaScript logic extensively.
End to End Webflow Design and Development Services
From Web Design and SEO Optimization to Photography and Brand Strategy, we offer a range of services to cover all your digital marketing needs.
Webflow Web Design
We design custom Webflow websites that are unique, SEO optimized, and designed to convert.
Webflow Maintenance
Gain peace of mind knowing that a Webflow Professional Partner is maintaining your website.
Claim Your Design Spot Today
We dedicate our full attention and expertise to a select few projects each month, ensuring personalized service and results.