
Virtual Joystick is a vanilla JavaScript library that creates customizable on-screen joystick controls for touch-enabled devices. Weighs under 5KB gzipped and requires no external dependencies.
The library provides both static and dynamic positioning modes, built-in haptic feedback through the Vibration API, and keyboard emulation for WASD or arrow key mapping.
It’s ideal for building HTML5 canvas games, interactive web applications, or any touch-based UI that requires directional input.
Features:
- Multi-Touch Support: Handles multiple simultaneous touch inputs for mobile interactions.
- Haptic Feedback Integration: Built-in Vibration API support provides tactile response during joystick movement.
- Dual Positioning Modes: Static mode fixes the joystick at a defined position, while dynamic mode spawns the control wherever the user touches.
- Keyboard Emulation: Maps joystick directions to arrow keys or WASD for consistent input handling across devices.
- Movement Constraints: Supports circular, square, and axis-locked movement patterns for different control schemes.
- Customizable Appearance: Full control over colors, sizes, sensitivity, and visual feedback zones.
- Real-Time Configuration: Update joystick settings dynamically without reinitializing the instance.
Use Cases:
- Mobile HTML5 Games: Implement touch controls for canvas-based games where users need directional movement without covering the screen with their thumbs.
- Progressive Web Applications: Add joystick navigation to PWAs that require spatial input, such as virtual tours, 3D model viewers, or interactive maps.
- Accessibility Interfaces: Create alternative input methods for users who need larger, customizable touch targets instead of traditional button controls.
- Prototype Testing: Rapidly test touch-based control schemes in web prototypes before committing to native mobile development.
How to use it:
1. Download the package and import the virtual-joystick.js into your project.
import VirtualJoystick from "./src/virtual-joystick.js";
2. Create an empty DIV container to hold the virtual joystick.
<div class="joystick"></div>
3. Create a new instance of the VirtualJoystick class and pass in a DOM element as the first argument to create the joystick interface.
const joystickContainer = document.querySelector('.joystick');
const joystick = new VirtualJoystick(joystickContainer, {
// options
});4. Available options to customize the virtual joystick.
| Option | Default | Description |
|---|---|---|
| width | 100 | Width of the joystick base in pixels. |
| height | 100 | Height of the joystick base in pixels. |
| color | “gray” | Background color of the joystick base (legacy). |
| handleColor | “white” | Color of the joystick handle (legacy). |
| handleRadius | 20 | Radius of the joystick handle in pixels. |
| onChange | null | Callback function triggered when the joystick position changes. |
| onStart | null | Callback function triggered when user interaction starts. |
| onEnd | null | Callback function triggered when user interaction ends. |
| sensitivity | 1 | Multiplier for joystick movement sensitivity. |
| boundaries | false | If true, constrains the handle movement strictly within the base. |
| autoCenter | true | If true, the handle returns to the center when released. |
| deadzone | 0.1 | Threshold (0-1) below which movement is ignored. |
| shape | “circle” | Shape of the joystick base (‘circle’ or ‘square’). |
| mode | “static” | Interaction mode: ‘static’ (fixed position) or ‘dynamic’ (appears on touch). |
| lockAxis | null | Locks movement to a specific axis (‘x’ or ‘y’). |
| zones | [] | Array of zone objects for defining detection regions. |
| vibration | true | Enables haptic feedback when entering zones (if supported). |
| theme | Object | Custom styling object for ‘base’ and ‘handle’ appearances. |
| keyboardEmulation | Object | Configuration for mapping joystick directions to keyboard keys. |
| maxMoveRadius | null | Optional visual constraint radius; defaults to physical bounds if null. |
const joystick = new VirtualJoystick(joystickContainer, {
width: 100,
height: 100,
color: "gray",
handleColor: "white",
handleRadius: 20,
onChange: null,
onStart: null,
onEnd: null,
sensitivity: 1,
boundaries: false,
autoCenter: true,
deadzone: 0.1,
shape: "circle",
mode: "static", // 'static' or 'dynamic'
lockAxis: null, // null, 'x', or 'y'
zones: [],
vibration: true,
theme: {
base: {
background: "rgba(128, 128, 128, 0.5)",
border: "3px solid rgba(0, 0, 0, 0.8)",
shadow: "0 0 10px rgba(0, 0, 0, 0.3)",
},
handle: {
background: "rgba(255, 255, 255, 0.9)",
border: "2px solid rgba(0, 0, 0, 0.8)",
shadow: "0 0 5px rgba(0, 0, 0, 0.5)",
},
},
keyboardEmulation: {
enabled: false,
map: {
up: "ArrowUp",
down: "ArrowDown",
left: "ArrowLeft",
right: "ArrowRight",
},
},
maxMoveRadius: null,
});5. API Methods
joystick.destroy(): Removes the joystick DOM elements and unbinds all event listeners.joystick.setOption(key, value): Updates a specific setting in real-time without re-initializing.
Changelog:
v2.0 (12/10/2025)
- update and complete remake
FAQs:
Q: How do I prevent the joystick from interfering with page scrolling on mobile devices?
A: Add touch-action: none to the container element’s CSS. This prevents the browser from interpreting touch events as scroll gestures.
Q: Can I use multiple joystick instances on the same page?
A: Yes. Create separate container elements and initialize independent VirtualJoystick instances for each. Each instance maintains its own event listeners and state. This works well for dual-stick control schemes in games.
Q: Why isn’t haptic feedback working on my device?
A: The Vibration API has limited browser support and requires user interaction to activate. Check navigator.vibrate availability before enabling haptics. iOS devices don’t support the Vibration API in web browsers.







