HTML5 Canvas · Vanilla JS · No Libraries
BreakOut
Thirty-two bricks stand between you and a clean sweep. Read the angle — where the ball meets the paddle decides everything. Mouse, keys or thumb, 60 frames a second.
- 0Score
- 32Bricks left
- 0Best streak
- 0:00Cleared
Explicit collision,
fixed timestep
// paddle deflection — never perpendicular off = clamp((ball.x - paddle.cx) / 30, -1, 1) ang = off * 60° vx = SPEED * sin(ang) vy = -SPEED * cos(ang) // always upward // brick resolution — closest AABB only ox = min(ball.r - b.x, b.x + 50 - (ball.x - r)) oy = min(ball.b - b.y, b.y + 20 - (ball.y - r)) ox < oy ? flip dx : flip dy
Every collision pushes the ball clear of the surface it hit, so it can never be re-detected inside the same brick. One brick per frame, the shallowest penetration wins, and the velocity magnitude is renormalised to 3.44 px/frame after every bounce. The 60 Hz accumulator keeps physics identical on a 120 Hz phone.
- Field
- 400 × 600 px canvas
- Brick grid
- 8 cols × 4 rows · 50 × 20 px · y = 40 + row*20, x = col*50
- Paddle
- 60 × 10 px · y = 580 · travel clamped 0 → 340
- Ball
- r = 3 px · start (200, 570) · v = (2, −2.8) normalised
- Lives
- 3 · reset to paddle on loss
- Score
- +10 per brick · 320 maximum
- Loop
- requestAnimationFrame · fixed 1/60 accumulator
Edge cases, handled
Velocity never spawns straight up; paddle corners deflect at ±60°; side impacts flip dx instead of dy; the ball can never tunnel through a brick row at speed.
Touch without the jank
touchstart / touchmove / touchend drive a smoothed paddle target with
touch-action:none, so dragging never scrolls the page. Mouse and arrow keys share the same clamped path.Draw budget
Brick sprites are pre-rendered once to offscreen canvases; the field background and scanlines are cached patterns. One drawImage each, even at 32 bricks alive.
Auto-pause
Losing window focus or backgrounding the tab parks the simulation mid-flight — no ball lost to a phone call.