To have fun the launch of our new assortment of results, we partnered with Codrops for a enjoyable little tutorial exploring an interactive animation the place pictures seem following the mouse cursor. The twist: as soon as a picture has appeared, it falls to the underside of the viewport, bounces, after which disappears.
Go to the demo hyperlink to obtain the ZIP information or copy the element immediately into your Webflow challenge. Let’s get began!
HTML Construction
The construction right here is kind of easy. We’ll merely name the set of pictures we wish for our impact:
<part class="codrops_mwg">
<div class="medias">
<img src="./property/medias/01.png" alt="">
<img src="./property/medias/02.png" alt="">
<img src="./property/medias/03.png" alt="">
<img src="./property/medias/04.png" alt="">
</div>
</part>
Some CSS
Now, I’ll cover these pictures utilizing a little bit of CSS.
.codrops_mwg .medias img {
width: 1px;
peak: 1px;
high: 0;
left: 0;
place: absolute;
visibility: hidden;
pointer-events: none;
}
A fast rationalization: we may have loaded these pictures immediately by way of JavaScript to maintain the DOM clear. So why didn’t we try this? As a result of this method permits us to load the photographs instantly, even when they’re hidden. This manner, there might be no delay when a picture is created with our script.
Right here’s how I retrieve all of the URLs of the photographs used on this impact. These URLs are actually saved in my pictures array.
const root = doc.querySelector('.codrops_mwg')
const pictures = []
root.querySelectorAll('.medias img').forEach(picture => {
pictures.push(picture.getAttribute('src'))
})
Beginning the animation
We’ll set off a operate every time the cursor strikes a sure distance. Right here we monitor each X and Y actions to compute the overall distance traveled:
const root = doc.querySelector('.codrops_mwg')
let incr = 0,
oldIncrX = 0,
oldIncrY = 0
root.addEventListener("mousemove", e => {
const valX = e.clientX
const valY = e.clientY
// Add the optimistic distinction between the final two positions (X + Y)
incr += Math.abs(valX - oldIncrX) + Math.abs(valY - oldIncrY)
oldIncrX = valX
oldIncrY = valY
})
Right here, we calculate the delta between the present cursor place and the earlier place on each axes. We then add this whole distinction to our incr variable. We additionally retailer the deltas as a result of we’ll want them later to find out the path the picture ought to drift whereas falling.
Triggering the Picture Creation Perform
Subsequent, we’ll name the operate to create a picture as soon as incr exceeds a selected threshold. To make the impact responsive, we’ll set the edge to window.innerWidth / 8.
const root = doc.querySelector('.codrops_mwg')
let incr = 0,
oldIncrX = 0,
oldIncrY = 0,
resetDist = window.innerWidth / 8
root.addEventListener("mousemove", e => {
const valX = e.clientX
const valY = e.clientY
incr += Math.abs(valX - oldIncrX) + Math.abs(valY - oldIncrY)
if(incr > resetDist) {
incr = 0
createMedia(valX, valY - root.getBoundingClientRect().high, valX - oldIncrX, valY - oldIncrY)
}
oldIncrX = valX
oldIncrY = valY
})
Discover that we cross two further parameters to createMedia(): deltaX and deltaY. These signify the path the cursor was transferring in the meanwhile the picture is created. We’ll use them to make the picture drift in the identical path because it falls.
For the y place, we regulate by subtracting the space from the highest of the basis aspect to the highest of the viewport. This ensures the impact works accurately even when the web page is scrolled down:
e.clientY - root.getBoundingClientRect().high
Contained in the createMedia() Perform
The createMedia() operate now accepts 4 parameters: x, y, deltaX, and deltaY. We begin by creating a picture.
const picture = doc.createElement("img")
picture.setAttribute('src', pictures[indexImg]) root.appendChild(picture)
We additionally add a small guard: if the cursor is simply too near the underside of the viewport, we skip the creation to keep away from a visually damaged fall:
const H = window.innerHeight if (y > H - 200) return
Let’s create a easy GSAP Timeline(). When the timeline finishes, the picture is faraway from the DOM:
const tl = gsap.timeline({
onComplete: () => {
root.removeChild(picture);
tl && tl.kill()
}
})
The primary tween handles the looks with the identical elastic bounce, however with out setting x and y — these might be dealt with by separate tweens working concurrently:
tl.fromTo(picture, {
xPercent: -50 + (Math.random() - 0.5) * 80,
yPercent: -50 + (Math.random() - 0.5) * 10,
scaleX: 1.3,
scaleY: 1.3,
rotation:(Math.random() - 0.5) * 20
}, {
scaleX:1,
scaleY:1,
ease: 'elastic.out(2, 0.6)',
period: 0.4
})
Subsequent, we animate the horizontal place. The picture drifts within the path the cursor was transferring, utilizing deltaX. This tween begins similtaneously the earlier one due to the '<' place parameter:
tl.fromTo(picture, {
x,
}, {
x: '+=' + deltaX * 2,
rotation: 0,
ease: 'power1.in',
period: 0.4 }, '<')
Concurrently, we animate the vertical place. The picture falls from its preliminary y place to the underside of the viewport. The yPercent: -95 mixed with scale: 0.9 ensures the underside fringe of the picture lands exactly on the backside of the viewport:
tl.fromTo(picture, {
y
}, {
y: '+=' + (H - y),
scale: 0.9,
yPercent: -95,
ease: 'again.in(1.1)',
period: 0.4
}, '<')
Lastly, the bounce. After hitting the underside, the picture bounces again up and disappears. We proceed drifting horizontally and add a random rotation for a pure really feel:
tl.to(picture, {
x: '+=' + deltaX * 1.6,
rotation:(Math.random() - 0.5) * 40,
ease: 'power1.in',
period: 0.3
})
tl.to(picture, {
yPercent: 150,
ease: 'again.in(' + (1.5 + (1 - y/H)) + ')',
period: 0.3
}, '<')
Discover the dynamic easing:Â 'again.in('Â +Â (1.5Â +Â (1Â -Â y/H))Â +Â ')'. The upper the picture begins, the stronger the overshoot on the bounce, making a extra dramatic impact for pictures that fall from additional up.
Going additional
By tweaking the random values or exploring different easing choices from GSAP’s documentation, you may obtain totally different visible results. Attempt adjusting the again.in depth for kind of dramatic bounces. The GSAP Easing documentation web page is a superb playground for this!
About Made With Gsap
Made With Gsap is a platform that includes an ever-growing assortment of 100+ distinctive and well-crafted JS results. We’ve simply launched 50 new additions, plus a brand-new impact dropping each week.
To have fun the launch of this new assortment, we’re providing 10% off your first subscription interval with the code Codrops10 — accessible till the tip of Might.
Thanks once more to Manoela and Codrops for the chance to share this enjoyable bouncy impact and for repeatedly inspiring the artistic improvement group.
When you’d wish to observe the journey, you’ll find us on Instagram, LinkedIn, Youtube, X (Twitter) and Bluesky.


