Gravity Drag
Three coloured balls float in a frame and can be grabbed with the mouse or a finger: release them and gravity pulls them back to the floor with bounce and friction. Flick them sharply to watch their trajectory, or set them down gently to stack them.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Hover has 48 effects, including 5 free. Explore the category →
3 usage examples



How it works
Each ball is a div.gd380-ball positioned absolutely inside the .gd380-zone (300 px tall). Its state properties (x, y, vx, vy, isDragging) are kept in a JavaScript array. The requestAnimationFrame loop updates each ball on every frame with a step normalised to 60 fps: f = dt / 16.67.
When a ball is not grabbed: gravity adds 0.5 × f to vy; if y + 50 exceeds the zone's height, the ball bounces (vy × −0.65) and loses some horizontal speed (vx × 0.85). If it exits through a side, vx is reversed. When grabbed (mousedown or touchstart), its position follows the cursor or finger in the zone's coordinate system, with the grab offset stored; on release (mouseup/touchend), the velocity is derived from the position difference over the last 20 ms to allow flicking.
Accessibility
- Touch:
touchstart,touchmoveandtouchendevents let you grab and throw the balls with a finger on mobile and tablet.preventDefault()blocks page scrolling while a ball is held. - Keyboard: the effect does not offer keyboard navigation. The balls are not buttons and do not receive focus. If accessible use is required, add invisible
<button>elements that apply a preset impulse to each ball via Enter. - Reduced motion: under
prefers-reduced-motion: reduce, the loop does not start; the balls stay at their initial positions, visible and readable, without falling or bouncing. - Decorative role: the balls carry the words DRAG, ME, UP as a visual call to action. If your context requires different accessible text, add an
aria-labelon.gd380-zone.
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: same behaviour at 60 Hz and 120 Hz. Not verified on real Safari; expected full.
Without JavaScript, the three balls show at their initial positions, static. Without IntersectionObserver, the loop also runs off screen.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0380-root">
<div class="gd380-zone">
<div class="gd380-ball ball-1" data-ball="0" style="left:50px;top:60px">DRAG</div>
<div class="gd380-ball ball-2" data-ball="1" style="left:150px;top:90px">ME</div>
<div class="gd380-ball ball-3" data-ball="2" style="left:250px;top:50px">UP</div>
</div>
<span class="gd380-label" aria-hidden="true">Grab and throw the balls</span>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Gravity (JS — vy += 0.5 * f) |
0.5 px/frame² (normalised to 60 fps) | 0.2 gives a lunar fall; 1.5 a fast drop. On a 120 Hz screen, normalisation maintains the same feel as at 60 Hz. |
Bounce (JS — vy *= -0.65) |
65% of speed retained | 0.8 gives very elastic bounces; 0.3 a quick stop. 1.0 is physically impossible and creates an infinite cycle. |
Floor friction (JS — vx *= 0.85) |
85% of horizontal speed retained | 0.98 gives a long slide; 0.5 stops the ball almost instantly. |
Ball size (CSS — .gd380-ball { width, height }) |
50 px | Also adjust floor collisions (y + 50 > zoneH) to the actual ball height. |
Colours (CSS — .gd380-ball.ball-1, .ball-2, .ball-3) |
indigo, violet, pink gradient | Replace the background with your brand colours. The box-shadow follows if you keep it on a dark background. |
FAQ
div.gd380-ball.ball-4 elements inside .gd380-zone with an initial left/top style, and add the colour in the CSS. The script automatically iterates over all .gd380-ball elements with querySelectorAll. Beyond 6 balls, collisions between them are not handled: they pass through each other.