<canvas class="canvas" id="cvs">Your browser is not supported!</canvas>
body {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  margin: 0;
}

* {
  box-sizing: border-box;
}

// Canvas
.canvas {
  margin: 0 auto;
  display: block;
}
const cvs = document.querySelector("#cvs"),
  ctx = cvs.getContext("2d");
cvs.width = 306;
cvs.height = 480;

// imageZ
const getImagePromise = src => {
  const img = new Image();
  img.src = src;
  return new Promise((res, rej) => {
    img.addEventListener('load', () => res(img));
    img.addEventListener('error', rej);
  });
}

const bird = {
  y: 168,
  maxY: cvs.height - 118,
  minY: 168,
  speed: 0,
};

const G = 3.81 / 1e3; // JS milliseconds adjust

let groundX = 0;

const images = {
  bg: null, ground: null, bird: null
}

// дождаться загрузки всех трех картинок
Promise.all([
  getImagePromise("https://itproger.com/img/news/flappy_bird_bg.png"),
  getImagePromise("https://itproger.com/img/news/flappy_bird_fg.png"),
  getImagePromise("https://itproger.com/img/news/flappy_bird_bird.png"),
]).then(([bg, ground, bird]) => {
  images.bg = bg;
  images.ground = ground;
  images.bird = bird;
  loopBird();
}).catch(err => console.error(err));

let ts_ = 0;
const loopBird = ts => {
  if (ts_) {
    const dt = ts - ts_; //  сколько времени прошло
    const dy = bird.speed * dt + G * dt * dt / 2;
    bird.speed += dt * G;
    bird.y += dy;
    if (bird.y > bird.maxY) bird.speed *= -1;
  } else {
    bird.y += 0.5;
  }
  ts_ = ts;
  ctx.drawImage(images.bg, 0, 0, 480, 362); 
  ctx.drawImage(images.bird, 30, bird.y);
  ctx.drawImage(images.ground, groundX, cvs.height - 118);
  window.requestAnimationFrame(loopBird);
};

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.