<header>
  <h1>Brick Breaker</h1>
</header>

<main>
  <canvas id="canvas" width="480" height="320"></canvas>
</main>

<div id="modal">
  <span id="start">Start</span>
</div>

<footer>
  <p>Created by <a href="https://remybeumier.be" target="_blank">Rémy Beumier</a></p>
</footer>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  font-family: Arial;
  background: #e53935;
  color: white;
}

header h1 {
  font-weight: normal;
  padding: 20px;
  letter-spacing: 0.1em;
}

#modal {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  height: 326px;
  width: 482px;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(256,256,256,1);
  z-index: 100;
  cursor: pointer;
}

#modal #start {
  padding: 10px 20px;
  border: solid 2px #e53935;
  font-size: 20px;
  color: #e53935;
}

#modal:hover #start {
  background: #e53935;
  color: #fff;
}

canvas {
/*   width: 300px; */
/*   height: 300px; */
  background: #fff;
  margin: auto;
  position: relative;
  z-index: 1;
  border: solid 1px 
}

.hidden {
  display: none !important;
}

footer {
  font-size: 14px;
  padding: 30px;
}

footer a {
  text-decoration: none;
  color: #bbb;
}

footer a:hover {
  text-decoration: underline;
  color: #fff;
}

// make canvas on start!
// improve points, speed
// add levels??
// improved solution from:
// https://developer.mozilla.org/fr/docs/Games/Workflows/2D_Breakout_game_pure_JavaScript

var canvas = document.getElementById("canvas");
var modalElt = document.getElementById("modal");
var ctx = canvas.getContext("2d");
var ballRadius = 6;
var x = canvas.width/2;
var y = canvas.height-10-ballRadius;
var dx = 2;
var dy = -2;
var paddleHeight = 10;
var paddleWidth = 75;
var paddleX = (canvas.width-paddleWidth)/2;
var rightPressed = false;
var leftPressed = false;
var brickRowCount = 5;
var brickColumnCount = 3;
var brickWidth = 75;
var brickHeight = 20;
var brickPadding = 10;
var brickOffsetTop = 30;
var brickOffsetLeft = 30;
var score = 0;
var lives = 3;
var gameColor = "#e53935";
var animm;
var playing;

var bricks = [];
for(var c=0; c<brickColumnCount; c++) {
  bricks[c] = [];
  for(var r=0; r<brickRowCount; r++) {
    bricks[c][r] = { x: 0, y: 0, status: 1 };
  }
}

document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
document.addEventListener("mousemove", mouseMoveHandler, false);

function keyDownHandler(e) {
  if(e.key == "Right" || e.key == "ArrowRight") {
    rightPressed = true;
  }
  else if(e.key == "Left" || e.key == "ArrowLeft") {
    leftPressed = true;
  }
}

function keyUpHandler(e) {
  if(e.key == "Right" || e.key == "ArrowRight") {
    rightPressed = false;
  }
  else if(e.key == "Left" || e.key == "ArrowLeft") {
    leftPressed = false;
  }
}

function mouseMoveHandler(e) {
  var relativeX = e.clientX - canvas.offsetLeft;
  if(relativeX > 0 && relativeX < canvas.width) {
    paddleX = relativeX - paddleWidth/2;
  }
}

function collisionDetection() {
  for(var c=0; c<brickColumnCount; c++) {
    for(var r=0; r<brickRowCount; r++) {
      var b = bricks[c][r];
      if(b.status == 1) {
        // fix collision below -> ballRadius to remove/add?
        if(x > b.x-ballRadius && x < b.x+brickWidth+ballRadius && y > b.y-ballRadius && y < b.y+brickHeight+ballRadius) {
          dy = -dy;
          b.status = 0;
          score++;
          if(score == brickRowCount*brickColumnCount) {
            // console.log("YOU WIN, CONGRATS!");
            modalElt.firstElementChild.innerHTML = "You win, congrats!";
            gameReset();
          }
        }
      }
    }
  }
}

function drawBall() {
  ctx.beginPath();
  ctx.arc(x, y, ballRadius, 0, Math.PI*2);
  ctx.fillStyle = gameColor;
  ctx.fill();
  ctx.closePath();
}
function drawPaddle() {
  ctx.beginPath();
  ctx.rect(paddleX, canvas.height-paddleHeight, paddleWidth, paddleHeight);
  ctx.fillStyle = gameColor;
  ctx.fill();
  ctx.closePath();
}
function drawBricks() {
  for(var c=0; c<brickColumnCount; c++) {
    for(var r=0; r<brickRowCount; r++) {
      if(bricks[c][r].status == 1) {
        var brickX = (r*(brickWidth+brickPadding))+brickOffsetLeft;
        var brickY = (c*(brickHeight+brickPadding))+brickOffsetTop;
        bricks[c][r].x = brickX;
        bricks[c][r].y = brickY;
        ctx.beginPath();
        ctx.rect(brickX, brickY, brickWidth, brickHeight);
        ctx.fillStyle = gameColor;
        ctx.fill();
        ctx.closePath();
      }
    }
  }
}
function drawScore() {
  ctx.font = "16px Arial";
  ctx.fillStyle = gameColor;
  ctx.fillText("Score: "+score, 8, 20);
}
function drawLives() {
  ctx.font = "16px Arial";
  ctx.fillStyle = gameColor;
  ctx.fillText("Lives: "+lives, canvas.width-65, 20);
}

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawBricks();
  drawBall();
  drawPaddle();
  drawScore();
  drawLives();
  collisionDetection();
  // paddle hit
  if(y + dy > canvas.height-ballRadius-paddleHeight) {
    if(x > paddleX && x < paddleX + paddleWidth) {
      dy = -dy;
    }
  }
  // side canvas hit
  if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
    dx = -dx;
  }
  // top canvas hit
  if(y + dy < ballRadius) {
    dy = -dy;
  }
  // bottom canvas hit
  else if(y + dy > canvas.height-ballRadius) {
    if(!(x > paddleX && x < paddleX + paddleWidth)) {
      lives--;
      if(!lives) {
        // console.log("GAME OVER");
        modalElt.firstElementChild.innerHTML = "Game over...";
        gameReset();
      }
      else {
        x = canvas.width/2;
        y = canvas.height-10-ballRadius;
        dx = 3;
        dy = -3;
        paddleX = (canvas.width-paddleWidth)/2;
      }
    }
  }

  if(rightPressed && paddleX < canvas.width-paddleWidth) {
    paddleX += 7;
  }
  else if(leftPressed && paddleX > 0) {
    paddleX -= 7;
  }

  x += dx;
  y += dy;
  
  if (playing) {
    animm = requestAnimationFrame(draw);
  }
}

draw();

modalElt.addEventListener("click", function() {
  modalElt.classList.add("hidden");  
  playing = true;
  animm = requestAnimationFrame(draw);
});

function gameReset() {
  cancelAnimationFrame(animm);
  playing = false;
  modalElt.classList.remove("hidden");
  // reset variables
  score = 0;
  lives = 3;
  x = canvas.width/2;
  y = canvas.height-10-ballRadius;
  dx = 2;
  dy = -2;
  paddleX = (canvas.width-paddleWidth)/2;
  // show all bricks back
  for(var c=0; c<brickColumnCount; c++) {
    bricks[c] = [];
    for(var r=0; r<brickRowCount; r++) {
      bricks[c][r] = { x: 0, y: 0, status: 1 };
    }
  }
  setTimeout(function() {
    modalElt.firstElementChild.innerHTML = "Start";
  }, 1000);
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.