Ping Pong Game Using Pure JavaScript

HTML
<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>CodeAtNow - PingPong Game</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/[email protected]/inter.min.css'><link rel="stylesheet" href="./style.css">

</head>
<body>
<!-- partial:index.partial.html -->
<div id="unsubscribe">
    <div class="letter">
        <div class="shadow"></div>
        <div class="background"></div>
        <div class="body">
            <div class="game idle">

                <div class="headline">
                    <span>Exit Ping Pong </span>
                    <div class="close">
                        <svg viewBox="0 0 24 24">
                            <path d="M19.7,4.3c-0.4-0.4-1-0.4-1.4,0L12,10.6L5.7,4.3c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l6.3,6.3l-6.3,6.3 c-0.4,0.4-0.4,1,0,1.4C4.5,19.9,4.7,20,5,20s0.5-0.1,0.7-0.3l6.3-6.3l6.3,6.3c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3 c0.4-0.4,0.4-1,0-1.4L13.4,12l6.3-6.3C20.1,5.3,20.1,4.7,19.7,4.3z"></path>
                        </svg>
                    </div>
                </div>

                <div class="paddle one"></div>
                <div class="ball"></div>
                <div class="paddle two"></div>

                <div class="controls">
                    <span>Use <strong>up</strong> &amp; <strong>down</strong> arrow keys or</span>
                    <div>
                        <button class="up">
                            <svg viewBox="0 0 24 24">
                                <path d="M5.23 10.64a1 1 0 0 0 1.41.13L11 7.14V19a1 1 0 0 0 2 0V7.14l4.36 3.63a1 1 0 1 0 1.28-1.54l-6-5-.15-.09-.13-.07a1 1 0 0 0-.72 0l-.13.07-.15.09-6 5a1 1 0 0 0-.13 1.41z"></path>
                            </svg>
                        </button>
                        <button class="down">
                            <svg viewBox="0 0 24 24">
                                <path d="M18.77 13.36a1 1 0 0 0-1.41-.13L13 16.86V5a1 1 0 0 0-2 0v11.86l-4.36-3.63a1 1 0 1 0-1.28 1.54l6 5 .15.09.13.07a1 1 0 0 0 .72 0l.13-.07.15-.09 6-5a1 1 0 0 0 .13-1.41z">
                            </svg>
                        </button>
                    </div>
                </div>

                <div class="start">
                    <button>Start</button>
                    <small>or press up/down key</small>
                </div>

            </div>
        </div>
    </div>
    <h1>Ping Pong</h1>
    <p>Just Play Ping Pong</p>
    <div class="cta">
        <button class="confirm">
            Confirm
        </button>
    </div>
</div>
    
  <script  src="./script.js"></script>

</body>
</html>
CSS
:root {
  --text-color: #646B8C;
  --headline-color: #fcfcfc;
  --mail: #555B77;
  --mail-triangle: #494F69;
  --mail-background: #404660;
  --mail-shadow: #D1D6EE;
  --paper: #fff;
  --paper-border: #D1D6EE;
  --confirm-color: #fff;
  --confirm-background: #275EFE;
  --game-paddle: #404660;
  --game-ball: #252525;
  --controls-text: #646B8C;
  --controls-icon: #646B8C;
  --controls-background: #E1E6F9;
}

#unsubscribe button,
#game button {
  outline: none;
  border: none;
  display: table;
  margin: 0 auto;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  padding: 8px 20px;
  line-height: 21px;
  border-radius: 7px;
  cursor: pointer;
  -webkit-appearance: none;
  -webkit-tap-highlight-color: transparent;
}

#unsubscribe .letter {
  width: 84px;
  height: 72px;
  margin: 0 auto 32px auto;
  position: relative;
  -webkit-animation: letter 2s ease infinite;
          animation: letter 2s ease infinite;
}
#unsubscribe .letter:before, #unsubscribe .letter:after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 48px;
  z-index: 1;
}
#unsubscribe .letter:before {
  background: var(--mail);
  -webkit-clip-path: polygon(0 0, 50% 55%, 100% 0, 100% 100%, 0 100%);
          clip-path: polygon(0 0, 50% 55%, 100% 0, 100% 100%, 0 100%);
}
#unsubscribe .letter:after {
  background: var(--mail-triangle);
  -webkit-clip-path: polygon(0 100%, 50% 55%, 100% 100%);
          clip-path: polygon(0 100%, 50% 55%, 100% 100%);
}
#unsubscribe .letter .background {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: var(--mail-background);
  -webkit-clip-path: polygon(0 24px, 50% 0, 100% 24px, 100% 100%, 0 100%);
          clip-path: polygon(0 24px, 50% 0, 100% 24px, 100% 100%, 0 100%);
}
#unsubscribe .letter .shadow {
  background: black;
  width: 92px;
  height: 4px;
  border-radius: 50%;
  position: absolute;
  top: 108%;
  left: -4px;
  background: var(--mail-shadow);
  -webkit-animation: shadow 2s ease infinite;
          animation: shadow 2s ease infinite;
}
#unsubscribe .letter .body {
  width: 360px;
  height: 260px;
  bottom: 0;
  left: -138px;
  border-radius: 1px;
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--paper-border);
  position: absolute;
  transform: translateY(36%) translateZ(0) scale(0.2, 0.16) rotate(90deg);
}
#unsubscribe .letter .body .game {
  width: 360px;
  height: 260px;
  position: relative;
  transition: opacity 0.3s ease 0.8s;
}
#unsubscribe .letter .body .game .headline {
  position: absolute;
  left: 0;
  right: 0;
  top: -32px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  transform: translateZ(0);
}
#unsubscribe .letter .body .game .headline span {
  color: var(--headline-color);
  font-size: 16px;
  font-weight: 600;
  line-height: 24px;
}
#unsubscribe .letter .body .game .headline .close {
  cursor: pointer;
}
#unsubscribe .letter .body .game .headline .close svg {
  width: 20px;
  height: 20px;
  display: block;
  fill: var(--text-color);
  padding: 2px;
}
#unsubscribe .letter .body .game .paddle,
#unsubscribe .letter .body .game .ball {
  top: 0;
  position: absolute;
  transition: opacity 0.3s;
  transform: translate(var(--x, 0), var(--y, 0));
  margin: 4px;
}
#unsubscribe .letter .body .game .paddle {
  width: 6px;
  height: 48px;
  border-radius: 3px;
  --y: 106px;
  background: var(--game-paddle);
}
#unsubscribe .letter .body .game .paddle.one {
  left: 0;
}
#unsubscribe .letter .body .game .paddle.two {
  right: 0;
}
#unsubscribe .letter .body .game .ball {
  background: var(--game-ball);
  border-radius: 50%;
  width: 16px;
  height: 16px;
  left: 0;
}
#unsubscribe .letter .body .game .controls {
  bottom: -80px;
  left: 0;
  right: 0;
  position: absolute;
}
#unsubscribe .letter .body .game .controls span {
  display: block;
  text-align: center;
  margin-bottom: 12px;
  font-size: 14px;
  font-weight: 500;
  color: var(--controls-text);
}
#unsubscribe .letter .body .game .controls div {
  display: flex;
  justify-content: center;
}
#unsubscribe .letter .body .game .controls div button {
  width: 64px;
  padding: 8px 0;
  margin: 0;
  background: var(--controls-background);
}
#unsubscribe .letter .body .game .controls div button:not(:last-child) {
  margin-right: 16px;
}
#unsubscribe .letter .body .game .controls div button svg {
  width: 20px;
  height: 20px;
  display: block;
  margin: 0 auto;
  fill: var(--controls-icon);
}
#unsubscribe .letter .body .game .start {
  position: absolute;
  text-align: center;
  white-space: nowrap;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  transition: opacity 0.3s;
}
#unsubscribe .letter .body .game .start button {
  color: var(--confirm-color);
  background: var(--confirm-background);
}
#unsubscribe .letter .body .game .start small {
  margin: 4px 0 0 0;
  display: block;
  font-style: italic;
  font-size: 12px;
  color: var(--text-color);
}
#unsubscribe .letter .body .game:not(.idle) .start {
  opacity: 0;
  pointer-events: none;
}
#unsubscribe .letter .body .game:not(.init) .ball {
  opacity: 0;
}
#unsubscribe h1 {
  text-align: center;
  margin: 0 0 8px 0;
  font-family: inherit;
  font-weight: 600;
  font-size: 24px;
  color: var(--headline-color);
}
#unsubscribe p {
  text-align: center;
  margin: 0;
  font-size: 16px;
  color: var(--text-color);
}
#unsubscribe .cta {
  margin-top: 32px;
}
#unsubscribe .cta button {
  color: var(--confirm-color);
  background: var(--confirm-background);
}
#unsubscribe:not(.show-game) .letter .body .game {
  opacity: 0;
  pointer-events: none;
  transition-delay: 0s;
}
#unsubscribe.show-game .letter {
  -webkit-animation-play-state: paused;
          animation-play-state: paused;
}
#unsubscribe.show-game .letter .body {
  -webkit-animation: paper 0.8s linear forwards;
          animation: paper 0.8s linear forwards;
}
#unsubscribe.show-game .letter .shadow {
  -webkit-animation-play-state: paused;
          animation-play-state: paused;
}
#unsubscribe.hide-game .letter .body {
  -webkit-animation: paper-back 0.8s linear forwards;
          animation: paper-back 0.8s linear forwards;
}

@-webkit-keyframes paper {
  30% {
    z-index: 0;
    transform: translateY(18%) translateZ(0) scale(0.2, 0.16) rotate(90deg);
  }
  60%, 100% {
    z-index: 2;
  }
  60% {
    transform: translateY(0) translateZ(0) scale(0.2, 0.16) rotate(0deg);
  }
  100% {
    transform: translateY(63%) translateZ(0);
  }
}

@keyframes paper {
  30% {
    z-index: 0;
    transform: translateY(18%) translateZ(0) scale(0.2, 0.16) rotate(90deg);
  }
  60%, 100% {
    z-index: 2;
  }
  60% {
    transform: translateY(0) translateZ(0) scale(0.2, 0.16) rotate(0deg);
  }
  100% {
    transform: translateY(63%) translateZ(0);
  }
}
@-webkit-keyframes paper-back {
  0% {
    transform: translateY(63%) translateZ(0);
  }
  30% {
    transform: translateY(0) translateZ(0) scale(0.2, 0.16) rotate(0deg);
  }
  60% {
    z-index: 0;
    transform: translateY(18%) translateZ(0) scale(0.2, 0.16) rotate(90deg);
  }
  0%, 30% {
    z-index: 2;
  }
  100% {
    transform: translateY(36%) translateZ(0) scale(0.2, 0.16) rotate(90deg);
  }
}
@keyframes paper-back {
  0% {
    transform: translateY(63%) translateZ(0);
  }
  30% {
    transform: translateY(0) translateZ(0) scale(0.2, 0.16) rotate(0deg);
  }
  60% {
    z-index: 0;
    transform: translateY(18%) translateZ(0) scale(0.2, 0.16) rotate(90deg);
  }
  0%, 30% {
    z-index: 2;
  }
  100% {
    transform: translateY(36%) translateZ(0) scale(0.2, 0.16) rotate(90deg);
  }
}
@-webkit-keyframes letter {
  50% {
    transform: translateY(-4px);
  }
}
@keyframes letter {
  50% {
    transform: translateY(-4px);
  }
}
@-webkit-keyframes shadow {
  50% {
    opacity: 0.7;
    transform: translateY(4px) scale(0.8);
  }
}
@keyframes shadow {
  50% {
    opacity: 0.7;
    transform: translateY(4px) scale(0.8);
  }
}
html {
  box-sizing: border-box;
  -webkit-font-smoothing: antialiased;
}

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

body {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  font-family: "Inter", "Inter UI", Arial;
  background: #2e2f2c;
}
body .dribbble {
  position: fixed;
  display: block;
  right: 20px;
  bottom: 20px;
}
body .dribbble img {
  display: block;
  height: 28px;
}
body .twitter {
  position: fixed;
  display: block;
  right: 64px;
  bottom: 14px;
}
body .twitter svg {
  width: 32px;
  height: 32px;
  fill: #1da1f2;
}
JS
const $ = (s, o = document) => o.querySelector(s);
const $$ = (s, o = document) => o.querySelectorAll(s);

var unsubscribe = $('#unsubscribe'),
    game = $('.game', unsubscribe),
    confirmButton = $('.confirm', unsubscribe),
    upButton = $('.controls .up', game),
    downButton = $('.controls .down', game),
    startButton = $('.start', game),
    closeButton = $('.close', game);

var ball = {
        elem: $('.ball', game),
        x: 0,
        y: 0,
        top: 0,
        left: 0
    },
    one = {
        elem: $('.paddle.one', game),
        y: 0,
        top: 0,
        score: 0
    },
    two = {
        elem: $('.paddle.two', game),
        y: 0,
        score: 0
    },
    interval;

function init() {

    if(game.classList.contains('idle')) {

        one.y = game.offsetHeight / 2 - one.elem.offsetHeight / 2;
        two.y = game.offsetHeight / 2 - one.elem.offsetHeight / 2;

        start();

        game.classList.remove('idle');
        game.classList.add('init');

    }

}

startButton.addEventListener('click', init);

confirmButton.addEventListener('click', e => {
    unsubscribe.classList.add('show-game');
});

closeButton.addEventListener('click', e => {
    unsubscribe.classList.add('hide-game');
    unsubscribe.classList.remove('show-game');
    setTimeout(() => unsubscribe.classList.remove('hide-game'), 800);
});

function start() {

    ball.x = game.offsetWidth / 2 - ball.elem.offsetWidth / 2;
    ball.y = game.offsetHeight / 2 - ball.elem.offsetHeight / 2;
    ball.top = Math.random() * 2 + 2;
    //ball.left = ((Math.random() < .5) ? 1 : -1) * (Math.random() * 2 + 2);
    ball.left = (1 * Math.random() * 2 + 2);

    interval = window.setInterval(render, 1000 / 60);
}

function render() {

    one.y += one.top;
    two.y = ball.y - two.elem.offsetHeight / 2;

    ball.x += ball.left;
    ball.y += ball.top;

    if(one.y <= 0) {
        one.y = 0;
    }

    if(two.y <= 0) {
        two.y = 0;
    }

    if(one.y >= game.offsetHeight - one.elem.offsetHeight) {
        one.y = game.offsetHeight - one.elem.offsetHeight;
    }

    if(two.y > game.offsetHeight - two.elem.offsetHeight) {
        two.y = game.offsetHeight - two.elem.offsetHeight;
    }

    if(ball.y <= 0 || ball.y >= game.offsetHeight - ball.elem.offsetHeight) {
        ball.top = -ball.top;
    }

    if(ball.x <= (one.elem.offsetWidth - 2)) {
        if((ball.y + ball.elem.offsetHeight / 2 ) > one.y && (ball.y + ball.elem.offsetHeight / 2 ) < one.y + one.elem.offsetHeight) {
            ball.left = -ball.left;
        } else {
            two.score++;
            setTimeout(() => game.classList.add('idle'), 500);
            clearInterval(interval);
            //start();
        }
    }

    if(ball.x >= game.offsetWidth - ball.elem.offsetWidth - (two.elem.offsetWidth - 2)) {
        if((ball.y + ball.elem.offsetHeight / 2 ) > two.y && (ball.y + ball.elem.offsetHeight / 2 ) < two.y + two.elem.offsetHeight) {
            ball.left = -ball.left;
        } else {
            one.score++
            setTimeout(() => game.classList.add('idle'), 500);
            clearInterval(interval);
            //start();
        }
    }

    one.elem.style.setProperty('--y', one.y + 'px');
    two.elem.style.setProperty('--y', two.y + 'px');
    ball.elem.style.setProperty('--x', ball.x + 'px');
    ball.elem.style.setProperty('--y', ball.y + 'px');

}

document.addEventListener('keydown', e => {
    e.preventDefault();
    init();
    if(e.keyCode == 38 || e.which == 38) {
        one.top = -8;
    }
    if(e.keyCode == 40 || e.which == 40) {
        one.top = 8;
    }
}, false);

document.addEventListener('keyup', e => {
    e.preventDefault();
    init();
    if(e.keyCode == 38 || e.which == 38) {
        one.top = 0;
    }
    if(e.keyCode == 40 || e.which == 40) {
        one.top = 0;
    }
}, false);

upButton.onmousedown = e => {
    init();
    one.top = -8;
};

downButton.onmousedown = e => {
    init();
    one.top = 8;
};

upButton.onmouseup = e => {
    one.top = 0;
};

downButton.onmouseup = e => {
    one.top = 0;
};

upButton.ontouchstart = e => {
    init();
    one.top = -8;
};

downButton.ontouchstart = e => {
    init();
    one.top = 8;
};

upButton.ontouchend = e => {
    one.top = 0;
};

downButton.ontouchend = e => {
    one.top = 0;
};

Post a Comment

0Comments
Post a Comment (0)