A short visual burst of floating images that rise and drift smoothly across the screen on form submission. Adds a lively, rewarding touch to confirm success and fades away cleanly after the animation.
<!-- GSAP core + Physics2D (add these once per project) -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/Physics2DPlugin.min.js"></script>
<style>
.ou-emoji-img {
position: fixed;
top: 0;
left: 0;
will-change: transform;
transform-origin: center;
image-rendering: auto;
user-select: none;
pointer-events: none;
z-index: 9999;
backface-visibility: hidden;
}
</style>
window.addEventListener("load", () => {
if (typeof gsap === "undefined" || typeof Physics2DPlugin === "undefined") return;
gsap.registerPlugin(Physics2DPlugin);
// customize animation settings here
const settings = {
images: [
"https://showcase.oura.supply/wp-content/uploads/2025/10/Love-Emoji.svg",
"https://showcase.oura.supply/wp-content/uploads/2025/10/Playful-Emoji.svg",
"https://showcase.oura.supply/wp-content/uploads/2025/10/Cheeky-Emoji.svg"
],
total: 40, // controls how many images appear
size: { min: 180, max: 280 }, // sets min/max image size
speed: { min: 3.5, max: 5.5 }, // defines travel duration
velocity: { min: 700, max: 1000 }, // controls launch power
gravity: { min: -1500, max: -1000 }, // defines motion arc
drift: 80 // adjusts horizontal movement
};
function spawnImageWave() {
const { images, total, size, speed, velocity, gravity, drift } = settings;
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
for (let i = 0; i < total; i++) {
const img = document.createElement("img");
img.src = gsap.utils.random(images);
img.classList.add("ou-float-img");
const startX = (i / total) * screenWidth + gsap.utils.random(-100, 100);
const startY = screenHeight + gsap.utils.random(150, 400);
const imgSize = gsap.utils.random(size.min, size.max);
const delay = gsap.utils.random(0, 1.2);
gsap.set(img, {
position: "fixed",
width: imgSize,
height: "auto",
left: startX,
top: startY,
rotation: gsap.utils.random(-15, 15),
zIndex: 9999,
pointerEvents: "none"
});
document.body.appendChild(img);
// subtle horizontal float
gsap.to(img, {
x: `+=${gsap.utils.random(-drift, drift)}`,
repeat: -1,
yoyo: true,
duration: gsap.utils.random(2, 3.5),
ease: "sine.inOut"
});
// upward movement and cleanup
gsap.to(img, {
delay,
duration: gsap.utils.random(speed.min, speed.max),
physics2D: {
velocity: gsap.utils.random(velocity.min, velocity.max),
angle: gsap.utils.random(-88, -92),
gravity: gsap.utils.random(gravity.min, gravity.max)
},
rotation: gsap.utils.random(-45, 45),
ease: "none",
onComplete: () => img.remove()
});
}
}
// listens for form success event
const triggerBtn = document.querySelector("#ou-image-trigger");
const successClass = "elementor-message-success";
const observerTimeout = 6000;
if (!triggerBtn) return;
triggerBtn.addEventListener("click", () => {
let observerActive = true;
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (
observerActive &&
node.nodeType === 1 &&
node.classList.contains(successClass)
) {
spawnImageWave();
observer.disconnect();
observerActive = false;
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
if (observerActive) observer.disconnect();
observerActive = false;
}, observerTimeout);
});
});
Add the ID "ou-image-trigger" to your form’s submit button so the animation runs after a successful submission.
Some solutions only work on the live site. Always publish and test after each change, as results may not appear in the editor.