Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

I Built a One-Tap Stacking Game in Vanilla JS: The Tiny Details That Make It Addictive

One-tap games look trivial. You tap, a block drops, done. But when I built Sky Stack, a small stacking game in plain JavaScript and canvas, the fun turned out to depend on a few tiny decisions. Here is how it works and w

I Built a One-Tap Stacking Game in Vanilla JS: The Tiny Details That Make It Addictive

Sky Stack, a one-tap stacking game

One-tap games look trivial. You tap, a block drops, done. But when I built Sky Stack, a small stacking game in plain JavaScript and canvas, the fun turned out to depend on a few tiny decisions. Here is how it works and what made it feel good to play.

👉 Play it first (30 seconds): arelvox.com/sky-stack

The whole game in one idea

The tower is an array of blocks, and each block is only an x position and a width:

s.stack = [{ x: 90, w: 300 }];     // the base
s.cur   = { x: -300, w: 300 };     // the block sliding across the screen

Every frame the current block moves left or right and bounces at the edges. When you tap, you compare it with the top of the tower.

The core: intersecting two ranges

Dropping a block is just the overlap of two 1D ranges:

function drop() {
  var t = topBlock(), c = s.cur;
  var left = Math.max(t.x, c.x);
  var right = Math.min(t.x + t.w, c.x + c.w);
  var overlap = right - left;

  if (overlap <= 0) return gameOver();   // missed the tower completely

  // the part that hangs over the edge becomes a falling piece
  var cutX = c.x < t.x ? c.x : right;
  s.falls.push({ x: cutX, w: c.w - overlap, y: yOf(s.stack.length), vy: 0 });

  s.stack.push({ x: left, w: overlap });
  nextBlock();
}

That is the entire game logic. Everything else is about feel.

1. A "perfect" zone that forgives you

Pixel-perfect drops are almost impossible on a phone, so anything within 5 pixels counts as perfect. A perfect drop cuts nothing off:

if (Math.abs(c.x - t.x) <= 5) {
  s.combo++;
  var grow = s.combo >= 3 ? Math.min(12, 300 - t.w) : 0;   // reward streaks
  s.stack.push({ x: t.x - grow / 2, w: t.w + grow });
}

The grow line matters most. In most stacking games your tower only shrinks, so every run feels like a slow loss. Here, from the third perfect drop in a row, the block grows back a little. Suddenly a bad drop is something you can recover from, and that is what makes people tap "play again".

2. Sound that climbs with your combo

No audio files, just the Web Audio API. Each perfect drop plays a note one step higher than the last:

function tone(freq, dur) {
  var o = ac.createOscillator(), g = ac.createGain();
  o.type = 'triangle';
  o.frequency.value = freq;
  g.gain.setValueAtTime(0.22, ac.currentTime);
  g.gain.exponentialRampToValueAtTime(0.0001, ac.currentTime + dur);
  o.connect(g); g.connect(ac.destination);
  o.start(); o.stop(ac.currentTime + dur);
}

tone(440 * Math.pow(1.122, Math.min(combo, 14)), 0.18);   // roughly one whole tone per step

You hear your streak building before you look at the score. On a miss, a dull low note plays and the pitch resets. It costs about ten lines and changes the game a lot.

3. Juice: falling pieces, particles, a little shake

  • The piece you cut off keeps falling and rotating instead of disappearing.
  • A perfect drop throws white particles from the edges.
  • navigator.vibrate(15) gives a tiny buzz on phones that support it.
  • The camera eases upward as the tower grows, so you never see a hard jump.

None of this changes the rules, but without it the game felt dead in testing.

4. A restart bug I almost shipped

The first version had a nasty bug: the tap that ended the game also counted as a tap on the "play again" overlay, so a new run started instantly and players never saw their score. The fix was to keep the two states strictly apart:

function onTap() {
  if (!playing) { start(); return; }
  if (s.over) return;          // ignore taps while the game-over screen is up
  drop();
}

The overlay has its own handler that only restarts once the game is really over. Keyboard Space and Enter only restart when the overlay is visible.

5. It has to fit anywhere

The game is a single self-contained block of HTML, CSS and JS. No framework, no build step, no external requests. It runs inside a WordPress "Custom HTML" block. One tip if you ever do the same: WordPress can mangle && inside inline scripts, so the code uses ternaries instead. Annoying, but it works.

Try it and beat my score

Drop your best score in the comments. Anything over 50 is already good, and 100+ means your timing is seriously sharp.

Play Sky Stack →

If you like small browser games, there are a few more on the same site: a daily number puzzle with a Time Attack mode and a typing game with boss bugs.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.