Touch-enabled Virtual JoyStick Controller In JavaScript

Category: Javascript | June 17, 2020
Author:stemkoski
Views Total:6,120 views
Official Page:Go to website
Last Update:June 17, 2020
License:MIT

Preview:

Touch-enabled Virtual JoyStick Controller In JavaScript

Description:

Just another virtual joystick controller that supports both mouse and touch events.

Written in plain JavaScript & HTML/CSS. No Canvas and SVG required.

How to use it:

1. Code the HTML for the joystick.

<div style="width: 128px">
  <img src="images/joystick-base.png"/>
  <div id="stick" style="position: absolute; left:32px; top:32px;">
  <img src="images/joystick-red.png"/>    
  </div>
</div>

2. Create an element to display the current status.

<div id="status">
  Joystick
</div>

3. The main JavaScript class for the joystick. Possible parameters:

  • stickID: ID of HTML element (representing joystick) that will be dragged
  • maxDistance: maximum amount joystick can move in any direction
  • deadzone: joystick must move at least this amount from origin to register value change
class JoystickController
{
  constructor( stickID, maxDistance, deadzone )
  {
    this.id = stickID;
    let stick = document.getElementById(stickID);
    // location from which drag begins, used to calculate offsets
    this.dragStart = null;
    // track touch identifier in case multiple joysticks present
    this.touchId = null;
    
    this.active = false;
    this.value = { x: 0, y: 0 }; 
    let self = this;
    function handleDown(event)
    {
        self.active = true;
      // all drag movements are instantaneous
      stick.style.transition = '0s';
      // touch event fired before mouse event; prevent redundant mouse event from firing
      event.preventDefault();
        if (event.changedTouches)
          self.dragStart = { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        else
          self.dragStart = { x: event.clientX, y: event.clientY };
      // if this is a touch event, keep track of which one
        if (event.changedTouches)
          self.touchId = event.changedTouches[0].identifier;
    }
    
    function handleMove(event) 
    {
        if ( !self.active ) return;
        // if this is a touch event, make sure it is the right one
        // also handle multiple simultaneous touchmove events
        let touchmoveId = null;
        if (event.changedTouches)
        {
          for (let i = 0; i < event.changedTouches.length; i++)
          {
            if (self.touchId == event.changedTouches[i].identifier)
            {
              touchmoveId = i;
              event.clientX = event.changedTouches[i].clientX;
              event.clientY = event.changedTouches[i].clientY;
            }
          }
          if (touchmoveId == null) return;
        }
        const xDiff = event.clientX - self.dragStart.x;
        const yDiff = event.clientY - self.dragStart.y;
        const angle = Math.atan2(yDiff, xDiff);
      const distance = Math.min(maxDistance, Math.hypot(xDiff, yDiff));
      const xPosition = distance * Math.cos(angle);
      const yPosition = distance * Math.sin(angle);
      // move stick image to new position
        stick.style.transform = `translate3d(${xPosition}px, ${yPosition}px, 0px)`;
      // deadzone adjustment
      const distance2 = (distance < deadzone) ? 0 : maxDistance / (maxDistance - deadzone) * (distance - deadzone);
        const xPosition2 = distance2 * Math.cos(angle);
      const yPosition2 = distance2 * Math.sin(angle);
        const xPercent = parseFloat((xPosition2 / maxDistance).toFixed(4));
        const yPercent = parseFloat((yPosition2 / maxDistance).toFixed(4));
        
        self.value = { x: xPercent, y: yPercent };
      }
    function handleUp(event) 
    {
        if ( !self.active ) return;
        // if this is a touch event, make sure it is the right one
        if (event.changedTouches && self.touchId != event.changedTouches[0].identifier) return;
        // transition the joystick position back to center
        stick.style.transition = '.2s';
        stick.style.transform = `translate3d(0px, 0px, 0px)`;
        // reset everything
        self.value = { x: 0, y: 0 };
        self.touchId = null;
        self.active = false;
    }
    stick.addEventListener('mousedown', handleDown);
    stick.addEventListener('touchstart', handleDown);
    document.addEventListener('mousemove', handleMove, {passive: false});
    document.addEventListener('touchmove', handleMove, {passive: false});
    document.addEventListener('mouseup', handleUp);
    document.addEventListener('touchend', handleUp);
  }
}

4. Enable the joystick instance and update the status as follows:

let myStick = new JoystickController("stick", 64, 8);
function update()
{
  document.getElementById("status").innerText = "Joystick1: " + JSON.stringify(joystick1.value);
}
function loop()
{
  requestAnimationFrame(loop);
  update();
}
loop();

You Might Be Interested In:


Leave a Reply