Examples

Examples

Interactive cookbook entries demonstrating key nipplejs features. Each game highlights a specific option or combination of options. Try them on a touch device for the best experience — on desktop, click and drag to control the joystick.

Click the Code button on any demo to see the nipplejs setup.

Neon Snake

Steer a growing snake to collect glowing orbs. Each orb makes the snake longer, and the analog joystick gives smooth directional control.

This demo uses a static joystick, ideal for single-stick games where the control should always be visible.

nipplejs setup
import nipplejs from 'nipplejs';

const manager = nipplejs.create({
  zone: document.getElementById('zone'),
  mode: 'static',
  position: { left: '50%', top: '50%' },
  // color accepts a string or { front, back } object.
  // Values use CSS 'background', so gradients work.
  color: {
    front: 'linear-gradient(135deg, #34d399, #10b981)',
    back: 'rgba(16, 185, 129, 0.15)',
  },
});

manager.on('move', (evt) => {
  const { vector } = evt.data;
  // vector.x: -1 (left) to 1 (right)
  // vector.y: -1 (down) to 1 (up)
  player.x += vector.x * speed;
  player.y -= vector.y * speed;
});

manager.on('end', () => {
  // Stop moving
});
Start

Asteroid Dodge

Pilot a ship left and right to dodge falling asteroids. The difficulty ramps up over time with faster and more frequent obstacles.

This demo uses lockX to restrict the joystick to horizontal movement only, which maps naturally to a lane-dodging mechanic.

nipplejs setup
import nipplejs from 'nipplejs';

const manager = nipplejs.create({
  zone: document.getElementById('zone'),
  mode: 'static',
  position: { left: '50%', bottom: '15%' },
  lockX: true, // Horizontal movement only
  color: {
    front: 'linear-gradient(135deg, #38bdf8, #0ea5e9)',
    back: 'rgba(56, 189, 248, 0.12)',
  },
});

manager.on('move', (evt) => {
  const { vector } = evt.data;
  // Only vector.x changes — lockX freezes Y
  ship.x += vector.x * speed;
});
Start

Dual-Stick Arena

Control movement with the left stick and aim/fire with the right stick. Enemies swarm in from every edge — survive as long as you can. Best experienced on a mobile device with multitouch.

This demo creates two separate collections in split zones, which is the standard pattern for dual-stick controls. Each zone gets its own nipplejs.create() call with multitouch support.

nipplejs setup
import nipplejs from 'nipplejs';

// Left stick — movement
const moveStick = nipplejs.create({
  zone: document.getElementById('left-zone'),
  mode: 'static',
  position: { left: '20%', bottom: '20%' },
  // Layer an icon over a gradient background
  color: {
    front: 'url("move.svg") center/75% no-repeat, ' +
      'linear-gradient(135deg, #818cf8, #38bdf8)',
    back: 'rgba(99, 102, 241, 0.12)',
  },
});

// Right stick — aim & shoot
const aimStick = nipplejs.create({
  zone: document.getElementById('right-zone'),
  mode: 'static',
  position: { left: '80%', bottom: '20%' },
  color: {
    front: 'url("shoot.svg") center/75% no-repeat, ' +
      'linear-gradient(135deg, #e879f9, #ec4899)',
    back: 'rgba(236, 72, 153, 0.12)',
  },
});

moveStick.on('move', (evt) => {
  player.x += evt.data.vector.x * speed;
  player.y -= evt.data.vector.y * speed;
});

aimStick.on('move', (evt) => {
  aimAngle = evt.data.angle.radian;
  fireProjectile(aimAngle);
});
Start

Space Observatory

Scan the sky with a crosshair to find and lock onto celestial targets. Hold the crosshair on a target for one second to lock on.

This demo uses follow mode to split one joystick into two controls: vector moves a fine-aim crosshair within the joystick radius, while baseDelta pans the camera when you push beyond the edge. This dual-layer control is the key use case for follow: true.

nipplejs setup
import nipplejs from 'nipplejs';

const manager = nipplejs.create({
  zone: document.getElementById('zone'),
  mode: 'static',
  position: { left: '50%', bottom: '15%' },
  follow: true, // Base follows your thumb
  restOpacity: 0.8, // Keep scope visible at rest
  // color uses CSS 'background' — images, gradients, anything
  color: {
    front: 'url("scope.svg") center/cover',
    back: 'radial-gradient(circle, ' +
      'rgba(56,189,248,0.06) 0%, ' +
      'rgba(56,189,248,0.1) 68%, ' +
      'rgba(56,189,248,0.3) 68%)',
  },
});

manager.on('move', (evt) => {
  const { vector, baseDelta } = evt.data;

  // vector: fine aim within joystick radius
  crosshair.x = vector.x * aimRange;
  crosshair.y = -vector.y * aimRange;

  // baseDelta: camera pan when dragging beyond radius
  camera.x += baseDelta.x * panSpeed;
  camera.y -= baseDelta.y * panSpeed;
});
Start

Space Drift

Fly a drifting ship through space collecting waypoints. The ship maintains its last heading when you release the joystick, creating a momentum-like feel.

This demo uses restJoystick: false so the joystick thumb stays at its last position instead of snapping back to center. This visual cue reinforces the “drift” mechanic — the ship keeps moving in the last direction.

nipplejs setup
import nipplejs from 'nipplejs';

const manager = nipplejs.create({
  zone: document.getElementById('zone'),
  mode: 'static',
  position: { left: '50%', bottom: '15%' },
  restJoystick: false, // Stays where you leave it
  color: {
    front: 'linear-gradient(135deg, #a78bfa, #e879f9)',
    back: 'rgba(167, 139, 250, 0.12)',
  },
});

let velocity = { x: 0, y: 0 };

manager.on('move', (evt) => {
  const { vector } = evt.data;
  // Set velocity — persists after release
  velocity.x = vector.x * speed;
  velocity.y = -vector.y * speed;
});

// No 'end' handler needed —
// restJoystick: false keeps the last input
// so velocity naturally persists

function gameLoop() {
  ship.x += velocity.x;
  ship.y += velocity.y;
  requestAnimationFrame(gameLoop);
}
Start