HoverFree

Gravity Simulation

Click anywhere in the frame to launch coloured balls subject to gravity — they bounce off the walls, gradually lose their momentum and pile up on the floor. Each click adds a new one, indefinitely.

JSPhysicsInteractive

Updated

Click to add balls
Hover or click the scene to interact

This effect is free — the Hover category contains 48 effects total, including 5 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

Grallux — Gravity Tap to drop a ball
Click to add a ball

① Physics playground — visualising gravity for primary school students

WhenIn the Grallux school app, students tap the screen or click inside the zone to drop coloured balls and watch their trajectories and bounces.
WhyManipulating virtual objects under gravity makes the concept tangible and playful; each ball added shows they all fall at the same rate, illustrating gravitational equality.
SettingsNight-blue background to evoke a physics blackboard; standard ball size (30 px); random colours to distinguish each student's drops.
Live events ● Live
314
Sign-ups today
73
Purchases
$3 180
Revenue
New events appear here

② Metrics dashboard — real-time new-event visualisation

WhenEach new conversion or sign-up automatically drops a ball into the zone at the bottom of the dashboard; the rhythm of falling balls conveys a sense of activity without reading a number.
WhyA peripheral animated indicator draws the eye without interrupting work; the physical behaviour (accumulation, bouncing) is more expressive than a plain incrementing counter.
SettingsSolid-colour balls (green) on a dark dashboard background; the data-scenes script simulates new arrivals every 1.6 s via dispatchEvent on the container.
Drevorn ✓ Booking confirmed
See you on 24 December!
Confirmation sent to your inbox

③ Booking confirmation celebration — online accommodation app

WhenAfter a booking is confirmed, an "Add to calendar" button launches coloured balls as a background layer — each click on the CTA button drops a ball at the right position inside the gravity container.
WhyAn exuberant visual reaction at the confirmation moment creates a positive memory and reinforces the sense of achievement from completing the booking.
SettingsColoured balls overlaid on the confirmation message (high z-index on text); balls bounce in the background without hiding essential information.

How it works

The .fx-0381-root frame (250 px tall) contains the .gs381-container. On each click (or touchstart), the script creates a div.gs381-ball positioned where the cursor or finger touched the frame, with a small random initial velocity (vx between −2 and +2 px, vy between −4 and −1 px upward). Each ball's colour is chosen at random from a palette of five hues (violet, pink, indigo, cyan, green).

The requestAnimationFrame loop updates each ball on every frame with a step normalised to 60 fps: f = dt / 16.67. Gravity adds 0.5 × f to vy; friction multiplies both components by 0.99 × f. On floor bounce, vy is multiplied by −0.7 and vx by 0.8. Side bounce: vx reversed. An IntersectionObserver pauses the loop when the frame is off screen.

Accessibility

  • Touch: the touchstart event adds a ball at the finger's position, with preventDefault() to avoid page scrolling. The effect works fully on mobile.
  • Keyboard: the effect does not respond to keys. If accessible use is required, add an "Add a ball" <button> that simulates a click at the container's centre.
  • Reduced motion: under prefers-reduced-motion: reduce, the loop does not start. Clicks still create a visible ball at the click point, but no gravity or bouncing occur — the resulting state is readable and static.
  • Decorative role: the balls are purely visual. The label is readable, and its English equivalent is provided in html_en.

Browser compatibility

Pure DOM and CSS, no dependency. IntersectionObserver available since Chrome 51, Firefox 55, Safari 12.1. Physics via requestAnimationFrame normalised to elapsed time. Not verified on real Safari; expected full.

Chrome 51+✓ Full (measured)
Firefox 55+✓ Full (measured)
Safari 12.1+✓ Expected full (not measured)
Edge 79+✓ Full (measured)
Mobile iOS 12.2+✓ Full on touch (not measured)
Android Chrome 51+✓ Full on touch (not measured)

Without JavaScript, the frame appears empty with the label centred. Without IntersectionObserver, the loop also runs off screen.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="fx-0381-root">
  <div class="gs381-container">
    <span class="gs381-label">Click to add balls</span>
  </div>
</div>
CSS
.fx-0381-root,.fx-0381-root *,.fx-0381-root *::before,.fx-0381-root *::after{box-sizing:border-box}.fx-0381-root{--text-muted:#8b8b93;display:flex;align-items:center;justify-content:center;width:100%;height:250px;overflow:hidden;position:relative;background:rgb(26,26,36);color:rgb(255,255,255);font-family:sans-serif}.gs381-container{width:100%;height:250px;position:relative;overflow:hidden}.gs381-ball{position:absolute;width:30px;height:30px;border-radius:50%;cursor:pointer}.gs381-label{position:absolute;bottom:10px;left:50%;transform:translateX(-50%);font-size:.75rem;color:var(--text-muted);pointer-events:none;z-index:10;white-space:nowrap}
JavaScript (fx-0381)
(function(){var pref=window.matchMedia('(prefers-reduced-motion: reduce)');document.querySelectorAll('.fx-0381-root').forEach(function(root){var container=root.querySelector('.gs381-container');if(!container)return;var balls=[];var gravity=.5,bounce=.7,friction=.99;var rafId=null,lastTs=0;container.addEventListener('click',function(e){var r=container.getBoundingClientRect();var ball=document.createElement('div');ball.className='gs381-ball';ball.style.background='hsl('+(Math.random()*360)+',70%,60%)';var x=e.clientX-r.left-15,y=e.clientY-r.top-15;ball.style.left=x+'px';ball.style.top=y+'px';container.appendChild(ball);balls.push({el:ball,x:x,y:y,vx:(Math.random()-.5)*10,vy:0});if(balls.length>20){var old=balls.shift();old.el.remove()}});container.addEventListener('touchstart',function(e){e.preventDefault();var r=container.getBoundingClientRect();var ball=document.createElement('div');ball.className='gs381-ball';ball.style.background='hsl('+(Math.random()*360)+',70%,60%)';var x=e.touches[0].clientX-r.left-15,y=e.touches[0].clientY-r.top-15;ball.style.left=x+'px';ball.style.top=y+'px';container.appendChild(ball);balls.push({el:ball,x:x,y:y,vx:(Math.random()-.5)*10,vy:0});if(balls.length>20){var old=balls.shift();old.el.remove()}},{passive:false});if(pref.matches)return;function loop(ts){var dt=lastTs>0?Math.min(ts-lastTs,50):16.67;var f=dt/16.67;lastTs=ts;var h=container.offsetHeight,w=container.offsetWidth;balls.forEach(function(b){b.vy+=gravity*f;b.vx*=Math.pow(friction,f);b.x+=b.vx*f;b.y+=b.vy*f;if(b.y>h-30){b.y=h-30;b.vy*=-bounce}if(b.x<0){b.x=0;b.vx*=-bounce}if(b.x>w-30){b.x=w-30;b.vx*=-bounce}b.el.style.left=b.x+'px';b.el.style.top=b.y+'px'});rafId=requestAnimationFrame(loop)}function start(){if(!rafId)rafId=requestAnimationFrame(loop)}function stop(){if(rafId){cancelAnimationFrame(rafId);rafId=null;lastTs=0}}new IntersectionObserver(function(en){en[0].isIntersecting?start():stop()}).observe(root);start()})})();

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Gravity (JS — vy += 0.5 * f) 0.5 px/frame² (normalised to 60 fps) 0.2 gives a slow fall; 1.2 an inverted lunar effect.
Bounce (JS — vy *= -0.7) 70% of speed retained on bounce 0.9 gives long-bouncing balls; 0.3 a quick stop.
Ball size (JS — ball.style.width = ball.style.height = '30px') 30 px Also adjust floor detection (b.y + 30 > H).
Colour palette (JS — colors array) 5 hues: violet, pink, indigo, cyan, green Restrict to two brand colours or extend to seven for a rainbow effect.
Frame height (CSS — .fx-0381-root { height: 250px }) 250 px The inner container follows automatically.

FAQ

Do balls accumulate indefinitely?

Yes, by default. To cap the count, add at the start of the click handler: if (balls.length >= 30) balls.shift().el.remove();. This removes the oldest ball when the limit is reached.

Can balls be added from code, without a click?

Yes. Creating a ball is a simple function called with a position {x, y} inside the container. Extract it and call it on an external event: WebSocket message, timer, or API response.

Does the effect work in a frame smaller than 250 px?

Yes, down to about 80 px tall. Below that, the balls bounce very quickly between floor and ceiling and give a chaotic effect. Adjust height on both .fx-0381-root and .gs381-container.