Build a Classic Pong Game with SVG and JavaScript

Create a fully functional Pong game using SVG.js, covering game mechanics, AI opponents, collision detection, and polished visual effects with copy-paste code examples.

Why Build Pong for Learning Game Development

Pong represents an ideal entry point into game development for several compelling reasons. The game's core mechanics involve moving two paddles, bouncing a ball, and detecting collisions--these fundamental concepts appear in virtually every game ever created.

By building Pong, you learn how to:

  • Render graphics using scalable vector elements
  • Handle user input from keyboard interactions
  • Detect collisions between objects in 2D space
  • Track game state including scores and positions
  • Implement AI opponents with adjustable difficulty

SVG.js simplifies this approach by providing an intuitive API for creating, manipulating, and animating SVG elements. The library has no external dependencies, making it lightweight and straightforward to integrate into any project. If you're interested in exploring how SVG powers interactive web experiences, our web development services team can help you build similar interactive applications for your business.

Setting Up SVG.js and Your Development Environment

Installing and Including SVG.js

SVG.js offers multiple installation options to suit different development workflows. For npm-based projects, install the library using your preferred package manager:

npm install @svgdotjs/svg.js

For projects without a build step, include SVG.js directly from a CDN:

<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.2.0/svg.min.js"></script>

Initialize the SVG canvas by selecting an HTML container and specifying dimensions:

const width = 450;
const height = 300;
const draw = SVG('pong').size(width, height);

The SVG() function accepts either a DOM element ID or a direct reference to an HTML element.

Drawing Game Elements with SVG.js

Creating the Court and Background

Every game needs a proper playing surface. The court establishes boundaries and provides visual context for gameplay:

// Create background covering the entire game area
const background = draw.rect(width, height).fill('#E3E8E6');

// Draw center dividing line with dashed styling
const centerLine = draw.line(width / 2, 0, width / 2, height);
centerLine.stroke({
 width: 5,
 color: '#ffffff',
 dasharray: '5, 5'
});

Creating Paddles and the Ball

Game pieces require precise positioning and distinct colors for each player:

const paddleWidth = 20;
const paddleHeight = 100;

// Create left paddle with green fill
const paddleLeft = draw.rect(paddleWidth, paddleHeight);
paddleLeft.x(0).cy(height / 2).fill('#00ff99');

// Create right paddle by cloning
const paddleRight = paddleLeft.clone();
paddleRight.x(width - paddleWidth).fill('#ff0066');

// Create ball element
const ball = draw.circle(ballSize);
ball.center(width / 2, height / 2).fill('#7f7f7f');

Implementing Game Logic and the Game Loop

Understanding the Animation Loop

Games require continuous updates to reflect changing state. The requestAnimationFrame API provides smooth, efficient animation:

let lastTime = null;

function gameLoop(timestamp) {
 const dt = lastTime ? (timestamp - lastTime) / 1000 : 0;
 lastTime = timestamp;
 update(dt);
 requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

The dt (delta time) value enables frame-rate-independent movement.

Ball Movement and Wall Collisions

let vx = 150;
let vy = 100;

function update(dt) {
 ball.dmove(vx * dt, vy * dt);
 
 const cx = ball.cx();
 const cy = ball.cy();

 // Bounce off top and bottom walls
 if ((vy < 0 && cy <= 0) || (vy > 0 && cy >= height)) {
 vy = -vy;
 }

 // Bounce off left and right walls
 if ((vx < 0 && cx <= 0) || (vx > 0 && cx >= width)) {
 vx = -vx;
 }
}

Paddle Collision Detection

if ((vx < 0 && cx <= paddleWidth && cy > paddleLeftY && cy < paddleLeftY + paddleHeight) ||
 (vx > 0 && cx >= width - paddleWidth && cy > paddleRightY && cy < paddleRightY + paddleHeight)) {
 
 const hitPosition = cy - ((vx < 0 ? paddleLeftY : paddleRightY) + paddleHeight / 2);
 vy = hitPosition * 7;
 vx = -vx * 1.05;
}

Adding Player Controls and AI Opponent

Keyboard Input for Player Control

let paddleDirection = 0;
const paddleSpeed = 300;

SVG.on(document, 'keydown', function(e) {
 if (e.keyCode === 38) paddleDirection = -1;
 if (e.keyCode === 40) paddleDirection = 1;
});

SVG.on(document, 'keyup', function(e) {
 if (e.keyCode === 38 || e.keyCode === 40) paddleDirection = 0;
});

Implementing the AI Opponent

const difficulty = 3;

function updateAI(dt) {
 const ballCY = ball.cy();
 const paddleCY = paddleLeft.cy();
 
 const dy = Math.min(difficulty, Math.abs(ballCY - paddleCY));
 paddleLeft.cy(cy > paddleCY ? paddleCY + dy : paddleCY - dy);
 
 // Constrain to court boundaries
 paddleLeft.cy(
 Math.max(paddleHeight / 2, Math.min(height - paddleHeight / 2, paddleLeft.cy()))
 );
}

The difficulty variable limits AI movement speed--higher values create harder opponents. For more advanced AI implementations, explore how AI automation services can enhance your applications with intelligent features.

Polishing with Visual Effects

Color Morphing for the Ball

const ballColor = new SVG.Color('#ff0066');
ballColor.morph('#00ff99');

function updateBallColor() {
 const progress = ball.x() / width;
 ball.fill(ballColor.at(progress));
}

Creating Explosion Effects on Scoring

function boom() {
 const winningPaddle = vx > width / 2 ? paddleLeft : paddleRight;
 
 const gradient = draw.gradient('radial', function(stop) {
 stop.at(0, winningPaddle.attr('fill'), 1);
 stop.at(1, winningPaddle.attr('fill'), 0);
 });

 const blast = draw.circle(300);
 blast.center(ball.cx(), ball.cy()).fill(gradient);

 blast.animate(1000, '>').opacity(0).after(function() {
 blast.remove();
 });
}

Complete Working Example

Here's a complete, playable Pong game combining all elements:

<!DOCTYPE html>
<html>
<head>
 <title>Pong with SVG.js</title>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.2.0/svg.min.js"></script>
 <style>
 body { display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #222; }
 #pong { border: 2px solid #333; }
 </style>
</head>
<body>
 <div id="pong"></div>
 <script>
 const width = 450, height = 300;
 const paddleWidth = 20, paddleHeight = 100;
 const ballSize = 20;
 
 const draw = SVG('pong').size(width, height);
 const background = draw.rect(width, height).fill('#1a1a1a');
 const centerLine = draw.line(width/2, 0, width/2, height);
 centerLine.stroke({ width: 5, color: '#333', dasharray: '5, 5' });
 
 const paddleLeft = draw.rect(paddleWidth, paddleHeight).x(0).cy(height/2).fill('#00ff99');
 const paddleRight = draw.rect(paddleWidth, paddleHeight).x(width-paddleWidth).cy(height/2).fill('#ff0066');
 const ball = draw.circle(ballSize).center(width/2, height/2).fill('#7f7f7f');
 
 let playerLeft = 0, playerRight = 0;
 const scoreLeft = draw.text('0').font({ size: 32, family: 'Menlo', anchor: 'end', fill: '#fff' }).move(width/2-10, 10);
 const scoreRight = scoreLeft.clone().text('0').font('anchor', 'start').x(width/2+10);
 
 let vx = 0, vy = 0, paddleDirection = 0;
 const paddleSpeed = 300, difficulty = 3;
 
 SVG.on(document, 'keydown', e => { if(e.keyCode===38) paddleDirection=-1; if(e.keyCode===40) paddleDirection=1; });
 SVG.on(document, 'keyup', e => { if(e.keyCode===38||e.keyCode===40) paddleDirection=0; });
 
 draw.on('click', () => { if(vx===0 && vy===0) { vx = Math.random()*400-200; vy = Math.random()*400-200; }});
 
 function reset() {
 vx = 0; vy = 0;
 ball.animate(100).center(width/2, height/2);
 paddleLeft.animate(100).cy(height/2);
 paddleRight.animate(100).cy(height/2);
 }
 
 function update(dt) {
 ball.dmove(vx*dt, vy*dt);
 const cx = ball.cx(), cy = ball.cy();
 
 if((vy<0 && cy<=0) || (vy>0 && cy>=height)) vy = -vy;
 
 const pLeftY = paddleLeft.y(), pRightY = paddleRight.y();
 if((vx<0 && cx<=paddleWidth && cy>pLeftY && cy<pLeftY+paddleHeight) ||
 (vx>0 && cx>=width-paddleWidth && cy>pRightY && cy<pRightY+paddleHeight)) {
 vy = (cy - ((vx<0 ? pLeftY : pRightY) + paddleHeight/2)) * 7;
 vx = -vx * 1.05;
 }
 
 let py = paddleRight.y();
 if(paddleDirection===-1 && py>0) paddleRight.y(py - paddleSpeed*dt);
 else if(paddleDirection===1 && py<height-paddleHeight) paddleRight.y(py + paddleSpeed*dt);
 
 const aiCY = paddleLeft.cy();
 const dy = Math.min(difficulty, Math.abs(cy - aiCY));
 paddleLeft.cy(cy > aiCY ? aiCY + dy : aiCY - dy);
 
 if((vx<0 && cx<=0) || (vx>0 && cx>=width)) {
 if(vx<0) { playerRight++; scoreRight.text(playerRight); } 
 else { playerLeft++; scoreLeft.text(playerLeft); }
 reset();
 }
 }
 
 let lastTime = null;
 function loop(ts) {
 if(lastTime) update((ts-lastTime)/1000);
 lastTime = ts;
 requestAnimationFrame(loop);
 }
 requestAnimationFrame(loop);
 </script>
</body>
</html>

Performance Considerations and Best Practices

Optimizing SVG Rendering

  • Use transform attributes for movement rather than changing x or y directly
  • Group related elements in <g> containers to minimize DOM updates
  • Add will-change: transform in CSS for frequently animated elements

Memory Management

Always remove temporary visual effects after animation completes:

blast.animate(1000, '>').opacity(0).after(function() {
 blast.remove(); // Clean up DOM element
});

Frame Rate Independence

Always multiply movement by delta time:

// Correct: Frame-independent movement
ball.dmove(vx * dt, vy * dt);

This ensures consistent speed across different frame rates. Performance optimization is crucial for any web application you build.

Extending Your Pong Game

Once the core game works, consider adding these enhancements:

FeatureDescriptionDifficulty
Sound EffectsAdd audio feedback for hits and scoringEasy
Difficulty LevelsMenu to select AI challenge levelEasy
Power-upsRandom items that change gameplayMedium
Two-Player ModeLocal multiplayer on same keyboardEasy
Network MultiplayerOnline competitive playAdvanced

Sound Effects Example

const hitSound = new Audio('paddle-hit.mp3');

function playHitSound() {
 hitSound.currentTime = 0;
 hitSound.play().catch(() => {});
}

Conclusion

Building Pong with SVG.js demonstrates how modern web technologies enable interactive experiences without heavy game engines. The SVG approach offers advantages in crisp vector graphics, accessibility through DOM integration, and straightforward debugging.

This project covered essential game development concepts that transfer directly to more complex games and interactive applications.

Ready to build more interactive web experiences? Our web development services team creates custom applications using modern technologies like React, Next.js, and SVG. From interactive games to complex business applications, we bring your ideas to life with clean, performant code.

Frequently Asked Questions

What is SVG.js?

SVG.js is a lightweight JavaScript library for manipulating and animating SVG (Scalable Vector Graphics). It provides an intuitive API for creating, modifying, and animating vector graphics with no external dependencies.

Why use SVG instead of Canvas for games?

SVG offers resolution-independent graphics that remain sharp at any zoom level. Elements are part of the DOM, making them accessible and easy to debug with browser developer tools. SVG.js handles cross-browser compatibility.

How do I make the AI harder?

Increase the `difficulty` variable value. Higher values allow the AI paddle to move faster and track the ball more effectively. A difficulty of 1-2 is easy, 3-5 is medium, and 6+ is challenging.

Can I use this code in production?

Yes, SVG.js is MIT licensed and suitable for production use. The complete Pong implementation provided can be used as a starting point for web-based games and interactive content.

Sources

  1. SVG.js Official Documentation - Library for manipulating and animating SVG with no dependencies
  2. CSS-Tricks: Pong with SVG.js - Step-by-step implementation guide
  3. HTML5 Game Development: Pong Game with SVG.js - Overview of SVG.js capabilities and features
  4. CodePen: Fully Functional Pong Game with Effects - Working example of complete Pong game

Ready to Build Interactive Web Experiences?

Our team creates custom web applications using modern technologies like React, Next.js, and SVG. From interactive games to complex business applications, we bring your ideas to life with clean, performant code.