Toast Catcher JavaScript Project for Beginners Using HTML & JavaScript in 3-Step Tutorial with Free Code

Introduction :

Toast Catcher JavaScript Project
Toast Catcher JavaScript Project

If you’re just starting your journey into front-end development and looking for a JavaScript project that’s simple, fun, and helps reinforce core web skills — this one’s for you! 🍞

In this beginner-friendly tutorial, we’ll guide you through building a Toast Catcher Javascript project game using HTML, CSS, and JavaScript. The concept is easy: you catch falling toast with a plate — miss five times and it’s game over! It’s a hands-on way to understand DOM manipulation, event handling, and responsive design — all in just under 5 steps.

In this beginner-friendly guide, we’ll walk through every step using HTML, CSS, and JavaScript to build a fully working, real-world component. You’ll get:

📦 Full copy-paste-ready source code

💬 A interface with smart responses

📱 A fully responsive UI

📘 A complete Project Setup Guide

Whether you’re a student, hobbyist, or aspiring front-end developer, this game is a perfect real-world practice project. Let’s get started!

Setup and Requirements For Toast Catcher JavaScript Project :

Before we begin, ensure you have the following:

✅ Requirements:

  • A code editor like Visual Studio Code
  • Basic knowledge of HTML, CSS, and JavaScript
  • A modern web browser (Chrome, Firefox, Edge)
  • No additional libraries or frameworks needed

⏱️ Time Required: Under 5 minutes to build!

📂 Folder Structure:

Create a project folder and set up the following files:

toast-catcher-game/
│── index.html  (HTML structure)
│── index.css  (CSS for styling)
│── script.js   ( needed)

Now, let’s dive into building the Toast Catcher JavaScript Project step by step!

Toast Catcher JavaScript Project

Project Overview:

The Toast Catcher JavaScript Project is a simple and addictive browser game where toast slices fall from the top of the screen and the user moves a plate left or right to catch them.

Falling Toast Mechanic: Automatically generated toasts fall vertically.

Player Movement: Use arrow keys or on-screen controls to move the catcher.

Lives System: Player starts with 5 lives — one is lost each time toast hits the floor.

Game Over Alert: Once lives reach zero, the game resets.

Responsive UI: Works on all screen sizes, including mobile.

Easy to Customize: Add your own graphics, change speed, or use different objects.

It’s a real-world, interactive Toast Catcher JavaScript Project that’s not only fun but also boosts your understanding of event handling and animations.

Toast Catcher JavaScript Project
Toast Catcher JavaScript Project

Step 1: HTML For Toast Catcher JavaScript Project:

HTML Code for Toast Catcher JavaScript Project

HTML
<!DOCTYPE html>
<!-- https://aspirepages.in/ -->
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Toast Catcher | Aspirepages</title>
  <link rel="stylesheet" href="./index.css">

</head>
<body>

<div class="frame">
	<svg id="game" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="450" height="680" fill="none" viewBox="0 0 450 680">
		<path fill="#7DC8CF" d="M0 1h450v680H0z"/>
		<path fill="#976F4A" d="M0 0h450v80H0z"/>
		<path fill="#FDC571" d="M0 631h450v50H0z"/>
		<path fill="#67442B" d="M0 74h450v6H0z"/>
		<rect width="430" height="54" x="10" y="10" fill="#67442B" rx="12"/>
		<path xmlns="http://www.w3.org/2000/svg" fill="#FFE2B4" d="M391.652 25.303c3.524-3.523 9.148-3.665 12.842-.427l.017-.016.352-.334a9.407 9.407 0 0 1 13.285 13.286l-.333.352-13.303 13.303-13.304-13.304.016-.017c-3.238-3.695-3.097-9.318.428-12.843Z"/>
		<g fill="#FEDAA1" font="inherit" >
			<text id="score" x="25" y="46" >00000000</text>
			<text id="lives" x="345" y="46" font="inherit" ></text>
		</g>

		<path fill="#FEECBF" d="M104.139 183.699c1.5-19.5-32-23-32-1.5-15.5-5.5-20.5 13.5-12.5 16 14.5 0 43.5.5 55 .5 10.5-4-.5-22-10.5-15Zm295.5-2c1.642-21.263-33-24.5-34.5-3.5-18.5-6-24.258 16.956-15.5 19.682h60.837c11.495-4.362.111-23.815-10.837-16.182ZM93.078 379.908c1.792-23.22-34.939-29.209-41.439-2.209-16.5-8-22.677 16.903-13.12 19.88h66.384c12.543-4.763.121-26.007-11.825-17.671Zm192.561-69.209c1.295-16-22.804-22.105-27.5-3.5-11.919-5.513-18.639 12.448-11.736 14.5h47.954c9.061-3.282-.089-16.744-8.718-11Zm16 232.5c1.307-16.633-24.76-21.841-29.5-2.5-12.033-5.731-18.163 12.839-11.194 14.971h48.413c10.281-3.471.993-18.442-7.719-12.471Z"/>

		<g font-size="36" id="game-over-screen" pointer-events="none" visibility="hidden" >
			<text x="225" y="220" text-anchor="middle" stroke="#603719" stroke-width="10">GAME OVER</text>
			<text x="225" y="220" text-anchor="middle" fill="#f16f33">GAME OVER</text>
			<text id="final-score-info" x="225" y="260" text-anchor="middle" font-size="24" fill="white">Final Score: 0</text>
		</g>

		<!-- symbols -->
		<symbol id="toast" xmlns="http://www.w3.org/2000/svg" width="95" height="57" viewBox="0 0 95 57">
			<path class="toast__wings" fill="#FDE2A9" stroke="#7F3412" stroke-linejoin="round" stroke-width="3" d="M3.793 18.165c.905-.484 1.938-.526 2.887-.399 1.88.252 4.141 1.27 6.375 2.527 3.967 2.234 8.385 5.539 11.558 8.103l1.279 1.048.105.096a1.5 1.5 0 0 1 .424.87l1.47 11.701a1.5 1.5 0 0 1-.432 1.254c-3.981 3.94-8.147 4.232-10.628 2.541-1.168-.796-2.067-2.187-1.735-3.753.053-.25.135-.483.24-.702-1.743-.454-3.15-1.018-4.257-1.658-1.747-1.01-2.903-2.31-3.22-3.799-.292-1.363.181-2.602 1.058-3.47-1.424-.708-2.823-1.57-4-2.646-2.349-2.147-3.818-5.157-2.921-9.21l.05-.2c.265-.997.838-1.817 1.747-2.303Zm85.793-2.258c-1.486-.17-3.23.45-4.847 1.238-2.93 1.426-6.42 3.904-9.438 6.2l-1.26.971-.133.116a1.5 1.5 0 0 0-.448 1.1l.251 12.107a1.5 1.5 0 0 0 1.44 1.468c3.919.158 6.967-.368 9.192-1.296 2.19-.913 3.742-2.294 4.306-3.937.288-.837.307-1.725.002-2.556-.304-.828-.892-1.499-1.647-1.973a5.05 5.05 0 0 0-.67-.351 23.515 23.515 0 0 0 2.936-2.553c1.003-1.038 1.868-2.154 2.475-3.29.6-1.126.998-2.366.924-3.61-.055-.913-.297-1.79-.868-2.481-.595-.72-1.4-1.06-2.215-1.153Z"/>
			<mask id="a" fill="#fff">
				<path d="M63.941.713c6.616-.394 12.298 4.65 12.692 11.266l.12 2.016a12.94 12.94 0 0 1-1.657 7.165l1.421 23.884a8 8 0 0 1-7.51 8.461l-34.86 2.074a8 8 0 0 1-8.461-7.511l-1.343-22.575a12.946 12.946 0 0 1-3.543-8.17l-.06-1.017c-.427-7.167 5.038-13.323 12.204-13.75L63.941.714Z"/>
			</mask>
			<path fill="#C7511B" d="M63.941.713c6.616-.394 12.298 4.65 12.692 11.266l.12 2.016a12.94 12.94 0 0 1-1.657 7.165l1.421 23.884a8 8 0 0 1-7.51 8.461l-34.86 2.074a8 8 0 0 1-8.461-7.511l-1.343-22.575a12.946 12.946 0 0 1-3.543-8.17l-.06-1.017c-.427-7.167 5.038-13.323 12.204-13.75L63.941.714Z"/>
			<path fill="#7E3112" d="m63.941.713-.178-2.995.178 2.995Zm12.692 11.266-2.995.178 2.995-.178Zm.12 2.016 2.994-.178-2.994.178Zm-1.657 7.165-2.61-1.478a2.999 2.999 0 0 0-.385 1.656l2.995-.178Zm1.421 23.884 2.995-.178-2.995.178Zm-50.831 3.024-2.995.178 2.995-.178Zm-1.343-22.575 2.995-.178a3 3 0 0 0-.819-1.887l-2.176 2.065Zm-3.543-8.17-2.995.179 2.995-.179Zm-.06-1.017-2.995.178 2.994-.178Zm12.204-13.75-.178-2.994.178 2.995ZM63.941.714l.178 2.994a9 9 0 0 1 9.52 8.45l2.994-.178 2.995-.178c-.493-8.27-7.595-14.575-15.865-14.083l.178 2.995ZM76.633 11.98l-2.995.178.12 2.016 2.995-.178 2.994-.178-.12-2.016-2.994.178Zm.12 2.016-2.995.178a9.94 9.94 0 0 1-1.273 5.509l2.611 1.478 2.611 1.477a15.938 15.938 0 0 0 2.04-8.82l-2.994.178Zm-1.657 7.165-2.995.178 1.421 23.884 2.995-.178 2.995-.178L78.09 20.98l-2.995.178Zm1.421 23.884-2.995.178a5 5 0 0 1-4.694 5.288l.178 2.995.178 2.995c6.065-.361 10.689-5.57 10.328-11.634l-2.995.178Zm-7.51 8.461-.179-2.995-34.86 2.074.179 2.995.178 2.995 34.86-2.074-.179-2.995Zm-34.86 2.074-.178-2.995a5 5 0 0 1-5.288-4.694l-2.995.178-2.995.178c.361 6.064 5.57 10.688 11.634 10.328l-.178-2.995Zm-8.461-7.511 2.995-.178-1.343-22.576-2.995.179-2.995.178 1.343 22.575 2.995-.178Zm-1.343-22.575 2.176-2.065a9.946 9.946 0 0 1-2.724-6.283l-2.995.178-2.995.179a15.945 15.945 0 0 0 4.362 10.055l2.176-2.064Zm-3.543-8.17 2.995-.178-.06-1.017-2.996.178-2.994.178.06 1.018 2.995-.179Zm-.06-1.017 2.994-.178c-.328-5.513 3.875-10.249 9.389-10.577l-.179-2.994-.178-2.995c-8.82.525-15.546 8.1-15.021 16.922l2.994-.178Zm12.204-13.75.179 2.995L64.12 3.707 63.94.713l-.178-2.995L32.766-.438l.178 2.995Z" mask="url(#a)"/>
			<mask id="b" fill="#fff">
				<path d="M64.353 3.719a9 9 0 0 1 9.518 8.45l.177 2.969a10.949 10.949 0 0 1-1.721 6.587l1.39 23.368a5 5 0 0 1-4.694 5.288l-32.78 1.95a5 5 0 0 1-5.288-4.694l-1.362-22.894a10.953 10.953 0 0 1-2.882-6.789l-.058-.973c-.36-6.064 4.263-11.273 10.327-11.634L64.353 3.72Z"/>
			</mask>
			<path fill="#FEBB63" d="M64.353 3.719a9 9 0 0 1 9.518 8.45l.177 2.969a10.949 10.949 0 0 1-1.721 6.587l1.39 23.368a5 5 0 0 1-4.694 5.288l-32.78 1.95a5 5 0 0 1-5.288-4.694l-1.362-22.894a10.953 10.953 0 0 1-2.882-6.789l-.058-.973c-.36-6.064 4.263-11.273 10.327-11.634L64.353 3.72Z"/>
			<path fill="#F78E39" d="M64.353 3.719 64.175.724l.178 2.995Zm9.695 11.419 2.995-.178-2.995.178Zm-1.721 6.587-2.525-1.62a3 3 0 0 0-.47 1.799l2.995-.179Zm1.39 23.368 2.995-.178-2.995.178Zm-4.694 5.288.178 2.995-.178-2.995Zm-32.78 1.95.178 2.995-.178-2.995Zm-5.288-4.694-2.995.178 2.995-.178Zm-1.362-22.894 2.994-.178a3 3 0 0 0-.787-1.853l-2.207 2.031Zm-2.882-6.789-2.995.178 2.995-.178ZM36.98 5.347l-.179-2.994.178 2.994ZM64.352 3.72l.178 2.995a6 6 0 0 1 6.346 5.633l2.994-.178 2.995-.179C76.473 5.375 70.79.33 64.175.724l.178 2.995Zm9.518 8.45-2.994.178.176 2.969 2.995-.178 2.995-.178-.177-2.97-2.995.178Zm.177 2.969-2.995.178a7.949 7.949 0 0 1-1.25 4.789l2.524 1.62 2.524 1.621a13.947 13.947 0 0 0 2.192-8.386l-2.995.178Zm-1.721 6.587-2.995.178 1.39 23.368 2.995-.178 2.995-.178-1.39-23.368-2.995.178Zm1.39 23.368-2.995.178a2 2 0 0 1-1.877 2.116l.178 2.994.178 2.995a8 8 0 0 0 7.51-8.46l-2.994.177Zm-4.694 5.288-.178-2.994-32.78 1.95.178 2.994.178 2.995 32.78-1.95-.178-2.995Zm-32.78 1.95-.178-2.994a2 2 0 0 1-2.116-1.878l-2.994.178-2.995.179a8 8 0 0 0 8.46 7.51l-.177-2.995Zm-5.288-4.694 2.994-.178-1.361-22.894-2.995.178-2.995.178 1.362 22.894 2.995-.178Zm-1.362-22.894 2.207-2.031a7.954 7.954 0 0 1-2.094-4.936l-2.995.178-2.995.178a13.953 13.953 0 0 0 3.669 8.642l2.208-2.031Zm-2.882-6.789 2.995-.178-.058-.973-2.995.178-2.995.178.058.973 2.995-.178Zm-.058-.973 2.995-.178a8 8 0 0 1 7.51-8.461l-.178-2.995-.178-2.994C29.084 2.812 23.2 9.44 23.658 17.159l2.995-.178ZM36.98 5.347l.178 2.995 27.373-1.628-.178-2.995-.178-2.995-27.373 1.629.178 2.994Z" mask="url(#b)"/>
			<path stroke="#7F3412" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M49.28 31.137c2.615 1.848 5.61 1.67 7.488-.445"/>
			<g class="toast__eyes">
				<ellipse cx="61.42" cy="25.123" fill="#7F3412" rx="2.5" ry="3.5" transform="rotate(-3.404 61.42 25.123)"/>
				<ellipse cx="42.994" cy="26.502" fill="#7F3412" rx="2.5" ry="3.5" transform="rotate(-3.404 42.994 26.502)"/>
			</g>
		</symbol>
	</svg>
</div>

  <script  src="./script.js"></script>

</body>
</html>

HTML Explanation For Toast Catcher JavaScript Project:

id="game"

  • This gives the element a unique identifier called “game”.
  • It allows JavaScript and CSS to easily select and manipulate this specific element on the page.
  • For example, in JavaScript, you can do document.getElementById('game') to access it.

xmlns="http://www.w3.org/2000/svg"

  • This defines the XML namespace for SVG elements.
  • It tells the browser that the elements inside are SVG graphics, so they are rendered properly.
  • Without this, the browser might not interpret the SVG tags correctly.

xmlns:xlink="http://www.w3.org/1999/xlink"

  • This declares an additional XML namespace for XLink, which is used to reference external resources inside SVG (like images or links).
  • It’s needed if you want to use features like <use> to reuse elements or link to external files.

width="1000" and height="700"

  • These define the displayed width and height of the SVG element on the page, in pixels.
  • So the SVG will take up a 1000 by 700 pixel area in the layout.

viewBox="0 0 1000 700"

  • This sets the coordinate system inside the SVG.
  • It means that inside the SVG canvas, the top-left corner is coordinate (0,0), and the bottom-right corner is (1000,700).
  • This allows the SVG to scale proportionally if the rendered size changes, keeping the aspect ratio consistent.

Step 2 : CSS For Toast Catcher JavaScript Project:

CSS Code For Toast Catcher JavaScript Project

CSS
:root {
  background-color: #7DC8CF;
  font-family: system-ui;
  font-variant-numeric: tabular-nums;
}

*,
:after,
:before {
  box-sizing: border-box;
}

svg {
  display: block;
  font-size: 1.5rem;
  font-weight: 900;
  max-width: 100%;
  height: auto;
  max-height: 100%;
}

body {
  height: 100vh;
  margin: 0;
  display: grid;
  place-items: center;
  background: lightblue;
}

.frame {
  border: 6px solid #67442b;
  border-radius: 16px;
  background: #67442b;
}

#game {
  border-radius: 10px;
  user-select: none;
}

.toast {
  cursor: pointer;
}
.toast__wings {
  animation: wings-flapping linear infinite 0.4s;
  transform-origin: center 85%;
  pointer-events: none;
}
.toast__eyes {
  animation: blink linear infinite 2s;
  transform-origin: 15px;
}

@keyframes wings-flapping {
  0%, 100% {
    scale: 1 1;
  }
  50% {
    scale: 1 0.8;
  }
}
@keyframes blink {
  0%, 90% {
    scale: 1 1;
  }
  100% {
    scale: 1 0;
  }
}

CSS Explanation For Toast Catcher JavaScript Project

:root sets styles on the top-level element. It applies a teal background color, uses the system UI font, and makes numbers use fixed-width digits for alignment.

*, :after, :before targets all elements and their pseudo-elements, setting box-sizing: border-box so padding and borders are included in the element’s total size, making layouts easier to manage.

svg elements are styled to display as block (removing inline spacing), have a font size 1.5 times the root size, very bold text, and scale responsively to fit their container without exceeding width or height limits.

The body takes the full viewport height with no margin, uses CSS Grid to center its content horizontally and vertically, and has a light blue background.

Elements with the class frame get a thick dark brown border, rounded corners with 16px radius, and the same dark brown background color.

The element with id game has slightly rounded corners (10px radius) and disables user selection inside it, so text or elements inside can’t be highlighted.

Elements with class toast show a pointer cursor on hover, indicating they are clickable.

.toast__wings applies a continuous wings-flapping animation that scales vertically to simulate movement, with its transform origin near the bottom center, and ignores mouse events so clicks pass through.

.toast__eyes applies a blinking animation that repeatedly scales the eyes vertically from open to closed, pivoting around a fixed point horizontally.

The wings-flapping animation scales the element normally at the start and end but squishes it vertically to 80% in the middle to mimic a flap.

The blink animation keeps the eyes fully open for most of the cycle, then scales them vertically to zero at the end to simulate blinking.

Step 3 : JavaScript For Toast Catcher JavaScript Project :

JavaScript Code For Toast Catcher JavaScript Project

JavaScript
class Game {
	constructor() {
		this.svg = document.getElementById('game')
		this.toastSymbolId = '#toast'
		this.scoreEl = document.getElementById('score')
		this.livesEl = document.getElementById('lives')
		this.finalScoreInfoEl = document.getElementById('final-score-info')
		this.gameOverScreenEl = document.getElementById('game-over-screen')
		this.toasts = []
		this.score = 0
		this.spawnInterval = 2000
		this.remainingLives = 5
		this.gameOver = false
		this.gameStartTime = performance.now()

		this.loop = this.loop.bind(this)
		this.spawnToast()
		this.startSpawnTimer()
		this.updateLives()
		requestAnimationFrame(this.loop)
	}

	startSpawnTimer() {
		if (this.spawnTimer) {
			clearInterval(this.spawnTimer)
		}
		
		const gameTimeSeconds = (performance.now() - this.gameStartTime) / 1000
		this.spawnInterval = Math.max(500, 2000 - Math.floor(gameTimeSeconds / 10) * 100)
		
		this.spawnTimer = setInterval(() => {
			if (!this.gameOver) {
				this.spawnToast()
				
				this.startSpawnTimer()
			}
		}, this.spawnInterval)
	}

	spawnToast() {
		if (this.gameOver) return

		const startX = Math.random() * 350 + 25
		const endX = Math.random() * 350 + 25
		const peakY = 200 + Math.random() * 50

		const toast = document.createElementNS('http://www.w3.org/2000/svg', 'use')
		toast.setAttribute('href', this.toastSymbolId)
		toast.setAttribute('class', 'toast')
		toast.setAttribute('x', startX)
		toast.setAttribute('y', 680)
		this.svg.appendChild(toast)

		const toastObj = {
			el: toast,
			startX,
			endX,
			startY: 680,
			peakY,
			startTime: performance.now(),
			duration: 3000,
			clicked: false,
			upwardSpeed: -8,
			reachedBottom: false
		}

		toast.addEventListener('pointerdown', () => {
			if (!this.gameOver && !toastObj.clicked) {
				this.score += 1
				this.scoreEl.textContent = this.score.toString().padStart(8, '0')
				toastObj.clicked = true
			}
		})

		this.toasts.push(toastObj)
	}

	checkGameOver() {
		if (this.remainingLives <= 0) {
			this.gameOver = true
			clearInterval(this.spawnTimer)

			this.gameOverScreenEl.setAttribute('visibility','visible')
			this.finalScoreInfoEl.textContent = `Final Score: ${this.score}`
		}
	}

	loop(timestamp) {
		if (this.gameOver) {
			requestAnimationFrame(this.loop)
			return
		}

		this.toasts = this.toasts.filter(toast => {
			const t = (timestamp - toast.startTime) / toast.duration

			if (toast.clicked) {
				const currentY = parseFloat(toast.el.getAttribute('y'))
				const newY = currentY + toast.upwardSpeed
				toast.el.setAttribute('y', newY)
				toast.el.setAttribute('pointer-events', 'none')
				if (newY < -50) {
					toast.el.remove()
					return false
				}
			} else {

				if (t > 1) {

					if (!toast.reachedBottom) {
						this.remainingLives--
						this.updateLives()
						this.checkGameOver()
						toast.reachedBottom = true
					}
					toast.el.remove()
					return false
				}

				const x = toast.startX + (toast.endX - toast.startX) * t
				const y = toast.startY - (4 * t * (1 - t)) * (toast.startY - toast.peakY)

				toast.el.setAttribute('x', x)
				toast.el.setAttribute('y', y)
			}

			return true
		})

		requestAnimationFrame(this.loop)
	}
	
	updateLives(){
			this.livesEl.textContent = `${this.remainingLives}X`
	}
	
}


const game = new Game()

JavaScript Explantion For Toast Catcher JavaScript Project

The class Game is defined to control the game logic.

class Game {

In the constructor, it selects various DOM elements by their IDs for the game SVG, toast symbol, score display, lives display, final score info, and the game over screen.

constructor() {
this.svg = document.getElementById('game')
this.toastSymbolId = '#toast'
this.scoreEl = document.getElementById('score')
this.livesEl = document.getElementById('lives')
this.finalScoreInfoEl = document.getElementById('final-score-info')
this.gameOverScreenEl = document.getElementById('game-over-screen')

Initializes an empty array to track all the toasts currently on screen.

  this.toasts = []

Sets starting game values: score to 0, spawn interval 2000ms, remaining lives 5, gameOver flag false, and records game start time.

  this.score = 0
this.spawnInterval = 2000
this.remainingLives = 5
this.gameOver = false
this.gameStartTime = performance.now()

Binds the loop method to the current instance so this stays consistent when called by requestAnimationFrame.

  this.loop = this.loop.bind(this)

Immediately spawns the first toast, starts the timer to spawn more, updates the lives display, and begins the animation loop.

  this.spawnToast()
this.startSpawnTimer()
this.updateLives()
requestAnimationFrame(this.loop)
}

The method startSpawnTimer() clears any existing spawn timer to avoid duplicates.

startSpawnTimer() {
if (this.spawnTimer) {
clearInterval(this.spawnTimer)
}

Calculates elapsed game time in seconds and adjusts the spawn interval: starting at 2000ms, decreasing by 100ms every 10 seconds, but not below 500ms.

  const gameTimeSeconds = (performance.now() - this.gameStartTime) / 1000
this.spawnInterval = Math.max(500, 2000 - Math.floor(gameTimeSeconds / 10) * 100)

Sets a repeating timer that spawns a new toast at the current interval, unless the game is over. It then restarts the timer to recalculate interval.

  this.spawnTimer = setInterval(() => {
if (!this.gameOver) {
this.spawnToast()
this.startSpawnTimer()
}
}, this.spawnInterval)
}

The spawnToast() method exits immediately if the game is over.

spawnToast() {
if (this.gameOver) return

Generates random start and end X positions and a random peak Y height for the toast’s flight arc.

  const startX = Math.random() * 350 + 25
const endX = Math.random() * 350 + 25
const peakY = 200 + Math.random() * 50

Creates a new SVG <use> element referencing the toast symbol for the graphic.

  const toast = document.createElementNS('http://www.w3.org/2000/svg', 'use')
toast.setAttribute('href', this.toastSymbolId)
toast.setAttribute('class', 'toast')
toast.setAttribute('x', startX)
toast.setAttribute('y', 680)
this.svg.appendChild(toast)

Creates an object representing the toast, storing its properties including position, time started, animation duration, click state, upward speed after clicked, and if it reached bottom.

  const toastObj = {
el: toast,
startX,
endX,
startY: 680,
peakY,
startTime: performance.now(),
duration: 3000,
clicked: false,
upwardSpeed: -8,
reachedBottom: false
}

Adds an event listener to the toast for pointer clicks. When clicked, if game is active and toast not already clicked, increments score, updates score display, and marks toast as clicked.

  toast.addEventListener('pointerdown', () => {
if (!this.gameOver && !toastObj.clicked) {
this.score += 1
this.scoreEl.textContent = this.score.toString().padStart(8, '0')
toastObj.clicked = true
}
})

Adds the toast object to the array tracking active toasts.

  this.toasts.push(toastObj)
}

The method checkGameOver() tests if the player has no lives left.

checkGameOver() {
if (this.remainingLives <= 0) {

If lives are gone, it sets the game as over, stops spawning toasts, makes the game over screen visible, and shows the final score.

    this.gameOver = true
clearInterval(this.spawnTimer)

this.gameOverScreenEl.setAttribute('visibility','visible')
this.finalScoreInfoEl.textContent = `Final Score: ${this.score}`
}
}

The loop(timestamp) method runs every animation frame and updates the game state.

loop(timestamp) {
if (this.gameOver) {
requestAnimationFrame(this.loop)
return
}

Filters through all active toasts to update or remove them.

  this.toasts = this.toasts.filter(toast => {

Calculates normalized time t between 0 and 1 representing progress of the toast’s animation.

    const t = (timestamp - toast.startTime) / toast.duration

If the toast was clicked, it moves the toast upwards by a fixed speed, disables pointer events, and removes the toast if it goes off screen.

    if (toast.clicked) {
const currentY = parseFloat(toast.el.getAttribute('y'))
const newY = currentY + toast.upwardSpeed
toast.el.setAttribute('y', newY)
toast.el.setAttribute('pointer-events', 'none')
if (newY < -50) {
toast.el.remove()
return false
}

If the toast was not clicked and its animation time is over:

    } else {
if (t > 1) {

If this is the first time it reached bottom, subtract one life, update lives display, and check for game over.

        if (!toast.reachedBottom) {
this.remainingLives--
this.updateLives()
this.checkGameOver()
toast.reachedBottom = true
}

Removes the toast from SVG and filters it out of active toasts.

        toast.el.remove()
return false
}

If the toast is still flying, calculates new x and y positions for the flight arc and updates the toast position accordingly.

      const x = toast.startX + (toast.endX - toast.startX) * t
const y = toast.startY - (4 * t * (1 - t)) * (toast.startY - toast.peakY)

toast.el.setAttribute('x', x)
toast.el.setAttribute('y', y)
}

Keeps the toast in the array if not removed.

    return true
})

Requests the next animation frame to continue updating.

  requestAnimationFrame(this.loop)
}

The method updateLives() updates the lives display element with the current number of remaining lives.

updateLives(){
this.livesEl.textContent = `${this.remainingLives}X`
}

Closing the class.

}

Finally, an instance of the game is created to start everything.

const game = new Game()

Live Preview The Demo Website:

Toast Catcher JavaScript Project

Download complete code :

Git Hub link :

Responsive AI Chatbot Template

Conclusion :

You just built a fully functional Toast Catcher JavaScript Project using pure HTML, CSS, and JavaScript — great job! 🎉

💬 Got stuck somewhere? Drop your questions in the comments.
📲 Want more free tutorials like this? Follow us on Telegram or Instagram.
🔗 Check out more JavaScript UI Projects or Frontend Tools

Tags :

3D Website Effect AI Chatbot Tutorial aspirepages Beginner Chatbot Project Build Chatbot with JavaScript Chatbot with HTML CSS JS Creative Web Design Gemini API JavaScript Example Gemini Chatbot Integration Google Gemini API How to Create 3D Effects How to Create a Chatbot html card HTMLCSSJavaScript HTML CSS JavaScript Tutorial Interactive Website Design JavaScript Chatbot JavaScript for Websites ResponsiveCardSlider Web Development Tutorial

Read our previous blog post :

⚙️ Responsive mega menu built using HTML, CSS, and JS for navbar layout
🔥 Fully Responsive Mega Menu for Navbar – Built in Minutes!

Leave a Comment