TOUCHSCREEN GAMEPAD

HTML CSS JavaScript

This versatile touchscreen gamepad allows players to play web-based games on their smartphone or tablet. It transforms the phone screen into a familiar game controller, complete with a D-pad (directional-pad) for movement and two action buttons (typically labeled "A" and "B"). Simply tap or hold your finger on the virtual buttons, and the game responds as if you were using a keyboard.

The gamepad is designed to appear automatically on screen as soon as a touch input is detected, ensuring it's available precisely when needed without any manual activation from the player. The gamepad intelligently handles multi-touch and finger sliding, allowing for fluid control without interrupting your gameplay with unwanted scrolling.

The code works by listening for touch events on designated areas of the screen. When you touch a button, the script simulates a keyboard "keydown" event for the corresponding key (like 'ArrowUp' or 'KeyA'). When you lift your finger, it simulates a "keyup" event. This clever mapping makes any web game designed for keyboard input compatible with the touchscreen. The code also manages visual feedback by adding and removing a 'pressed' class to the buttons and ensures the page doesn't scroll while you're immersed in the game.

<!-- Code by M4TTBIT - m4ttbit.dev - 2025 -->

<div id="gamepad">
  <div id="dpad">
      <div id="up" class="dpad-btn"></div>
      <div id="left" class="dpad-btn"></div>
      <div id="right" class="dpad-btn"></div>
      <div id="down" class="dpad-btn"></div>
  </div>
  <div id="action-buttons">
      <div id="a-btn" class="action-btn">A</div>
      <div id="b-btn" class="action-btn">B</div>
  </div>
</div>

<script src="js/gamepad.js"></script>
/* Code by M4TTBIT - m4ttbit.dev - 2025 */

/* Dark color theme options shown*/
/* Change colors to match your preferences */

#gamepad {
  display: none;
  justify-content: space-between;
  align-items: center;
  z-index: 10;
  padding: 16px 24px 0px 24px;
  margin: auto;
  box-sizing: border-box;
  pointer-events: none;
  position: relative;
  width: 100%;
}

@media (hover: none) and (pointer: coarse), (max-width: 768px) {
  #gamepad {
    display: flex;
  }
}

/* Show when activated by touch */
#gamepad.gamepad-active {
  display: flex;
}

#dpad {
  position: relative;
  width: 150px;
  height: 150px;
  pointer-events: auto; /* Capture clicks on the d-pad */
  /* Prevent scrolling and text selection on mobile */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.dpad-btn {
  position: absolute;
  width: 50px;
  height: 50px;
  background-color: #2c2c2c;
  border: 2px solid #49DFFC;
  border-radius: 5px;
}

#up {
  top: 0;
  left: 50px;
}

#left {
  top: 50px;
  left: 0;
}

#right {
  top: 50px;
  left: 100px;
}

#down {
  top: 100px;
  left: 50px;
}

#action-buttons {
  display: flex;
  pointer-events: auto; /* Capture clicks on the action buttons */
  /* Prevent scrolling and text selection on mobile */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.action-btn {
  width: 60px;
  height: 60px;
  background-color: #2c2c2c;
  border: 2px solid #49DFFC;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 24px;
  color: #49DFFC;
  margin-left: 20px;
}

.dpad-btn.pressed, .action-btn.pressed {
    background-color: #555;
    transform: scale(0.95);
}

body.no-scroll {
  overflow: hidden;
  /* Prevents pull-to-refresh and other boundary effects */
  overscroll-behavior: none;
}
// Code by M4TTBIT - m4ttbit.dev - 2025

document.addEventListener('DOMContentLoaded', () => {
  const keyMap = {
      'up': 'ArrowUp',
      'down': 'ArrowDown',
      'left': 'ArrowLeft',
      'right': 'ArrowRight',
      'a-btn': 'KeyA',
      'b-btn': 'KeyB'
  };

  // Keep track of which element is being pressed by each touch
  const activeTouches = {};

  function simulateKeyEvent(key, eventType) {
      const event = new KeyboardEvent(eventType, {
          key: key.replace('Key', '').toLowerCase(),
          code: key,
          bubbles: true,
          cancelable: true
      });
      window.dispatchEvent(event);
  }

  function handleTouchStart(event) {
      event.preventDefault();
      document.body.classList.add('no-scroll');
      // Iterate through all new touches
      for (const touch of event.changedTouches) {
          const target = document.elementFromPoint(touch.clientX, touch.clientY);
          if (target && target.id && keyMap[target.id]) {
              // If the touch is on a valid button, press it
              target.classList.add('pressed');
              simulateKeyEvent(keyMap[target.id], 'keydown');
              // And track it by its unique identifier
              activeTouches[touch.identifier] = target;
          }
      }
  }

  function handleTouchEnd(event) {
      event.preventDefault();
      // If there are no more touches on the screen, remove the class
      // to re-enable scrolling.
      if (event.touches.length === 0) {
          document.body.classList.remove('no-scroll');
      }
      // Iterate through all touches that have just been released
      for (const touch of event.changedTouches) {
          const target = activeTouches[touch.identifier];
          // If this touch was pressing a button, release it
          if (target && target.id && keyMap[target.id]) {
              target.classList.remove('pressed');
              simulateKeyEvent(keyMap[target.id], 'keyup');
              // Stop tracking this touch
              delete activeTouches[touch.identifier];
          }
      }
  }

  function handleTouchMove(event) {
      event.preventDefault();
      // Iterate through all touches that have moved
      for (const touch of event.changedTouches) {
          const newTarget = document.elementFromPoint(touch.clientX, touch.clientY);
          const oldTarget = activeTouches[touch.identifier];

          if (newTarget !== oldTarget) {
              // The finger has slid to a new element (or off the buttons)
              // Release the old button if it was a valid one
              if (oldTarget && oldTarget.id && keyMap[oldTarget.id]) {
                  oldTarget.classList.remove('pressed');
                  simulateKeyEvent(keyMap[oldTarget.id], 'keyup');
              }

              // Press the new button if it's a valid one
              if (newTarget && newTarget.id && keyMap[newTarget.id]) {
                  newTarget.classList.add('pressed');
                  simulateKeyEvent(keyMap[newTarget.id], 'keydown');
                  activeTouches[touch.identifier] = newTarget;
              } else {
                  // The finger slid off the buttons, so stop tracking it
                  delete activeTouches[touch.identifier];
              }
          }
      }
  }

  const dpad = document.getElementById('dpad');
  const actionButtons = document.getElementById('action-buttons');

  // Add all necessary event listeners to both button containers
  [dpad, actionButtons].forEach(container => {
      container.addEventListener('touchstart', handleTouchStart, { passive: false });
      container.addEventListener('touchend', handleTouchEnd, { passive: false });
      container.addEventListener('touchcancel', handleTouchEnd, { passive: false });
      container.addEventListener('touchmove', handleTouchMove, { passive: false });
  });

  // A one-time event listener to show the gamepad on any touch event
  // This is for devices like touchscreen laptops that might not be caught
  // by the CSS media query alone
  function showGamepadOnTouch() {
      const gamepad = document.getElementById('gamepad');
      if (gamepad) {
          gamepad.classList.add('gamepad-active');
      }
  }

  document.addEventListener('touchstart', showGamepadOnTouch, { once: true });
});

Modifying this gamepad for different games or preferences is straightforward. To add new buttons, you'd simply include their corresponding HTML elements (e.g., a start-btn div) within the dpad or action-buttons containers and then add their id and desired keyboard mapping to the keyMap object. For instance, keyMap['start-btn'] = 'Enter'; would map a button with the ID start-btn to the 'Enter' key. You can also change existing button names or mappings directly within the keyMap object. For example, changing 'a-btn': 'KeyA' to 'a-btn': 'KeyX' would make the 'A' button simulate the 'X' key instead. This flexibility allows for easy customization to suit various game control schemes.

COMMENTS