Collision Detection
Eight coloured spheres generated on initialisation bounce continuously inside their allocated area: every frame detects circle-to-circle collisions and wall bounces, then swaps velocities. Autonomous simulation with no user interaction, loop paused off-screen.
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
The .fx-0387-root wrapper centres the collision area .fx-0387-area, a container that is 100 % wide with a height that follows its parent. At init, eight div.fx-0387-ball elements are created with a random radius (12–22 px), a random HSL colour, and an initial velocity of ±4 px per frame at 60 fps. Their starting positions are drawn randomly within the container.
Each frame, the loop moves every ball by its velocity (scaled by the elapsed time since the previous frame, capped at 50 ms to prevent jumps after a background tab). It checks the frame edges (simple bounce: flip the sign of the matching velocity component) then checks every ball pair for collisions: if the distance between centres is less than the sum of radii, velocities are recalculated along the collision angle.
A ResizeObserver watches .fx-0387-area and re-reads its dimensions on every resize, keeping balls inside the area without recreating them. An IntersectionObserver watches the root: the requestAnimationFrame loop stops when the area leaves the viewport and resumes on return, with no frames computed off-screen. Under reduced motion, the balls are displayed at their initial positions with no loop.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, the loop does not start. The balls are shown at their initial positions, visible against the dark background. The animation carries no informational content. - Decorative role: the animation is purely visual. It carries no information accessible by other means. If the balls represent named entities (molecules, projects), those names must be exposed as text beside the animation area.
- Keyboard and touch: no user interaction is built into this effect — the balls move autonomously. There are no focusable elements or touch handlers.
- Off-screen: the loop stops automatically when the area leaves the viewport. In a hidden tab, the browser suspends
requestAnimationFramecalls on its own.
Browser compatibility
ES5 JavaScript (requestAnimationFrame, IntersectionObserver, matchMedia), no dependency. IntersectionObserver available since Chrome 51, Firefox 55 and Safari 12.1. Not measured in Firefox and WebKit.
Without JavaScript, the frame stays empty. Without IntersectionObserver, the loop runs off-screen too. Without matchMedia, the animation starts even under reduced motion.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0387-root"><div class="physics-container"><div class="fx-0387-area"></div></div></div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Number of balls (JS — loop for (var n = 0; n < 8; n++)) |
8 balls | 4 balls give a sparser simulation, 16 a very busy frame with frequent collisions. |
Size range (JS — var r = 25 + 20 * Math.random()) |
Radius between 12.5 and 22.5 px | Increase the first term for larger spheres, reduce the second for more uniform sizes. |
Initial speed (JS — vx: (Math.random() - 0.5) * 4) |
±2 px per frame at 60 fps | Multiply by 2 to double the speed. Bounces remain correct at any speed. |
Colours (JS — 'hsl(' + Math.round(360 * Math.random()) + ',70%,60%)') |
Random hue, 70 % saturation, 60 % lightness | Replace with a fixed list of hex colours for a controlled palette: ['#6366f1','#a855f7','#ec4899'][n % 3]. |
Area height (CSS — .physics-container { height: 300px }) |
300 px (width always follows the container) | Increase or reduce the CSS value to match your layout. Width is read dynamically at init and updated on resize. |
FAQ
clientWidth and clientHeight of .fx-0387-area, updated automatically on resize.click listener to each el at init. The DOM element is accessible in the closure via the balls array.