Copy URL or Code
Paste to your AI coding assistant and customize:
Done. Your AI handles the rest.
Fully customizable. Uses mo.js for smooth physics-based animations. Perfect for loading states, playful micro-interactions, or hero sections.
/*
================================================================================
AI COMPONENT: Bouncy Ball (Plop Animation)
================================================================================
OVERVIEW:
A playful bouncing ball animation with squash/stretch physics, ground shadow,
and dust puff particles. Uses mo.js for smooth, physics-based motion.
SETUP:
1. Include mo.js library in your HTML:
<script src="https://cdn.jsdelivr.net/npm/@mojs/core"></script>
<script src="https://cdn.jsdelivr.net/npm/@mojs/player"></script>
2. Or install via npm:
npm install @mojs/core @mojs/player
3. Copy the HTML, CSS, and JavaScript below
================================================================================
CUSTOMIZATION OPTIONS
================================================================================
| Property | Variable/Property | Default |
|------------------|----------------------------|---------------|
| Orb color | orb.fill | #2B3940 |
| Background | CSS body background | #EA485C |
| Shadow opacity | groundShadow.fill alpha | 0.18 |
| Puff color | puffConfig.fill | #FFFEF8 |
| Animation speed | timeline.speed | 0.52 |
| Settle duration | SETTLE_DURATION | 420ms |
| Deform intensity | DEFORM_FACTOR | 1.45 |
================================================================================
HTML
================================================================================
*/
<!-- HTML Structure -->
<div id="plop-zone" class="drop-zone"></div>
/*
================================================================================
CSS
================================================================================
*/
body, html {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
body {
background: #EA485C;
position: relative;
}
.drop-zone {
overflow: hidden;
position: absolute;
width: 140px;
height: 100px;
left: 50%;
top: 50%;
margin-left: -70px;
margin-top: -50px;
z-index: 1;
}
/*
================================================================================
JAVASCRIPT
================================================================================
*/
const SLIDE_DURATION = 420;
const SETTLE_DURATION = 420;
const dropZone = document.body.querySelector('#plop-zone');
const ENTRY_OFFSET = 280;
// Ground shadow - grows/shrinks with bounce
const groundShadow = new mojs.Shape({
shape: 'circle',
fill: 'rgba(0,0,0, .18)',
parent: dropZone,
top: '100%',
left: '50%',
radius: 21,
scaleY: 0.28,
x: -21,
opacity: { 1: 0.22 },
scale: { 1: 0.72 },
easing: 'cubic.out',
delay: 210,
duration: 260,
isShowEnd: false,
isForce3d: true
}).then({
opacity: { to: 1, easing: 'bounce.out' },
scale: { to: 1, easing: 'bounce.out' },
duration: SETTLE_DURATION
});
// Custom easing for squash/stretch deformation
const { approximate } = mojs.easing;
const easeOutUpper = (p) => {
return mojs.easing.cubic.out(p) - p;
};
const easeOut = mojs.easing.mix(
{ to: 0.5, value: 0.375 },
{ to: 1, value: easeOutUpper }
);
const DEFORM_FACTOR = 1.45;
const stretchCurve = approximate((p) => {
return 1 + DEFORM_FACTOR * easeOut(p);
});
const squashCurve = approximate((p) => {
return 1 - DEFORM_FACTOR * easeOut(p);
});
// Bouncing orb with squash/stretch
const orb = new mojs.Shape({
parent: dropZone,
shape: 'circle',
fill: '#2B3940',
left: '50%',
top: '100%',
radius: 21,
y: -23,
x: -21,
scaleX: { 1: 1, curve: stretchCurve },
scaleY: { 1: 1, curve: squashCurve },
origin: '50% 100%',
duration: 210,
isForce3d: true
}).then({
y: { to: -72, easing: 'cubic.out' },
scaleY: { to: 1.12, easing: 'cubic.out' },
scaleX: { to: 0.88, easing: 'cubic.out' },
duration: 210
}).then({
y: { to: -23, easing: 'bounce.out' },
scaleY: { to: 1, easing: 'bounce.out' },
scaleX: { to: 1, easing: 'bounce.out' },
duration: SETTLE_DURATION
});
// Puff particles on impact
const puffConfig = {
parent: dropZone,
shape: 'circle',
fill: '#FFFEF8',
left: '50%',
top: '100%',
scale: { 1: 0 },
x: { 0: 48 },
y: { 0: -14 },
isForce3d: true,
swirlSize: 28,
swirlFrequency: 1.1,
isTimelineLess: true
};
const puffs = new mojs.Timeline();
for (let i = 0; i < 6; i++) {
puffs.add(new mojs.ShapeSwirl({
...puffConfig,
radius: Math.random() * 9 + 4,
delay: i * 45
}));
}
// Apply 3D hardware acceleration
mojs.h.force3d(dropZone);
// Slide-in entry animation
const slideTween = new mojs.Tween({
onUpdate: (p) => {
dropZone.style.transform = `translateX(${ENTRY_OFFSET * (1 - p)}px)`;
},
duration: SLIDE_DURATION
});
// Combine all animations into timeline
const timeline = new mojs.Timeline({ delay: 480, speed: 0.52 });
timeline.add(slideTween, orb, groundShadow, puffs);
// Loop with MojsPlayer
new MojsPlayer({ add: timeline, isPlaying: true, isRepeat: true });<!-- Basic Usage -->
<!DOCTYPE html>
<html>
<head>
<style>
body, html {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
body {
background: #EA485C;
position: relative;
}
.drop-zone {
overflow: hidden;
position: absolute;
width: 140px;
height: 100px;
left: 50%;
top: 50%;
margin-left: -70px;
margin-top: -50px;
z-index: 1;
}
</style>
</head>
<body>
<div id="plop-zone" class="drop-zone"></div>
<script src="https://cdn.jsdelivr.net/npm/@mojs/core"></script>
<script src="https://cdn.jsdelivr.net/npm/@mojs/player"></script>
<script>
// Paste the JavaScript code from above here
</script>
</body>
</html>
<!-- Customization Examples -->
// Change orb to blue
const orb = new mojs.Shape({
fill: '#3B82F6',
// ... rest of config
});
// Change background (in CSS)
body {
background: #6366F1; // Indigo
}
// Make animations faster
const timeline = new mojs.Timeline({ delay: 300, speed: 1 });
// More dramatic squash/stretch
const DEFORM_FACTOR = 1.8;
// Change puffs to gold particles
const puffConfig = {
fill: '#FFD700',
// ... rest of config
}