Introduction: Making a Joystick With HTML (pure JavaScript)

HI!I'm planning to make a mousebot for quite sometime now. For the first step, I decided to make a joystick with pure javascript so it can be loaded to an Arduino. I'll be explaining the code through out this tutorial and will be uploading the whole source code at the end.

Software used:

Visual studio code

Step 1: Put Text on Your Html

For computers to know that its an html file and it should be loaded, the following codes are necessary:

<!DOCTYPE html>
<html>

To define the title and others information about the document we specifiy it insde the head tag as folows:

<head>
    <title>   Mousebot </title>
    <meta name="viewport" content="user-scalable=no">
</head><br>

To prevent the html body from scrolling when used on mobile we specify it with the following.

  • scroll="no - Doesnt add Scrollbars
  • style - Indicates that it is a CSS property
  • overflow: hidden - A CSS property to hide content that is outside the display
  • position: fixed; - To make elements stays in the same place
<body scroll="no" style="overflow: hidden;position: fixed;<br>

For the Font style, color and size we specify it with this code:

font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif ;
color:rgb(128, 128, 128);
font-size: xx-large;"<br>

For the text, I placed the title on a

which is a heading to be bigger and others to a

whici is for paragraph to make it smaller. Other i used style="text-align:center" To place the text a the center. the Span enables the text in it to be written on a same line. the "id" is used later to change the values between written in span element.

 <h1 style="text-align:center">
        MOUSEBOT </h1>
    <p style="text-align: center;">
        X: <span id="x_coordinate"> </span>
        Y: <span id="y_coordinate"> </span>
        Speed: <span id="speed"> </span> %
        Angle: <span id="angle"> </span>
    </p><br>

To have a "canvas" on which we will draw the joystick later use this piece of code and name it "canvas"

 <canvas id="canvas" ></canvas><br>

To ensure no error, we will close all tags as follows:

</body>
</html><br>

And thats it fir the static html side. Try to copy this to vscode and save as index.html. Open it on your browser and you should see the attached image. Here is the whole code:

<!DOCTYPE html>
<html>
<head>
    <title>   Mousebot </title>
    <meta name="viewport" content="user-scalable=no">
</head>
<body scroll="no" style="overflow: hidden;position: fixed;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif ;
color:rgb(128, 128, 128);
font-size: xx-large;">
 <h1 style="text-align:center">
        MOUSEBOT </h1>
    <p style="text-align: center;">
        X: <span id="x_coordinate"> </span>
        Y: <span id="y_coordinate"> </span>
        Speed: <span id="speed"> </span> %
        Angle: <span id="angle"> </span>
    </p>
    <canvas id="canvas" ></canvas>
</body>
</html><br>

Step 2: Specify Steps to Do on Every Action

When the file is loaded, we want it to do some steps. we secify those steps inside this code

window.addEventListener('load', () => {<br>

We then get the html elemenent named "canvas"

canvas = document.getElementById('canvas');

and specify that we will be drawing 2D diagrams on this with

ctx = canvas.getContext('2d');<br>

We will then call a function named resize for the display. We will be making this functions later.

 resize();<br>

We will also call for functions named startDrawing when the mouse is pressed ,stopDrawing when it isn't pressed and draw when there mouse is moved.The touchscreen displays counter parts are as follows touchstart touchend, touchcancel, touchmove are used. For the Window to listen to this action we used this code.

document.addEventListener('mousedown', startDrawing);
document.addEventListener('mouseup', stopDrawing);
document.addEventListener('mousemove', Draw);

document.addEventListener('touchstart', startDrawing);
document.addEventListener('touchend', stopDrawing);
document.addEventListener('touchcancel', stopDrawing);
document.addEventListener('touchmove', Draw);<br>

When the screen is resized,we call the resize funtion as follows:

  window.addEventListener('resize', resize);<br>

We also get the elements with id x_coordinate, x_coordinate , speed and angle withdocument.getElementById(id) and add.innerText to makethe valie inside this elemets to = 0

<p>document.getElementById("x_coordinate").innerText = 0;<br>            document.getElementById("x_coordinate").innerText = 0;
            document.getElementById("speed").innerText = 0;
            document.getElementById("angle").innerText = 0;</p>

Step 3: Draw Your Backround

We will now make our function named backgound. To place the center of the circle on the x-coordinates located on the width center, divide width/2. Then to put the center on the lower part of the circle, divide the height with 3.

function background() {
            x_orig = width / 2;
            y_orig = height / 3;

The following code then signifies that we will start to draw now

 ctx.beginPath();<br>

To draw a circle, we need to specify the center with respect to the x and y axis(as shown on the image attached), its radius, the start and end angle in radians and the rotation(true for clockwise) in this format arc(x, y, radius, startAngle, endAngle, anticlockwise).

  ctx.arc(x_orig, y_orig, radius + 20, 0, Math.PI * 2, true);<br>

We choose its color with its hex value which you can get here with this code:

ctx.fillStyle = '#ECE5E5';<br>

We then fill the circle with this color using this code:

 ctx.fill();<br>

Step 4: Draw Your Joystick

for the joystick funtion, we will be accepting to variable named "width" and "height" for the center of our circle's radius. we will also draw a smaller circle with this code

function joystick(width, height) {
	ctx.beginPath();
	ctx.arc(width, height, radius, 0, Math.PI * 2, true);

Fill it with your desired color:

	ctx.fillStyle = '#F08080';
	ctx.fill();<br>

Pick the color of its borders

	ctx.strokeStyle = '#F6ABAB';<br>

Specify the width of the borders

	 ctx.lineWidth = 8;

Draws the border

            ctx.stroke();<br>

Step 5: Resize Your Drawings

For the resize function, i specify the radius to 200

   function resize() {
            radius = 200; // specify the radius to 200
             width = window.innerWidth; //Sets the variable width to be equal to the windows width
            height = radius * 6.5; //Sets the variable height
            ctx.canvas.width = width; //sets the canvas width to be equal to variable width
            ctx.canvas.height = height; //sets the canvas height
            background();//draw the background
            joystick(width / 2, height / 3); //sends to the joystick function this variables
        }<br>

How it works:

After resizing the canvas, it will call the background function to draw the background. Then it will call the joystick function and send "width / 2, height / 3" to draw the joystick's circle at its center.

Step 6: Get Mouse Position

Get the absolute mouse position with or get the absolute touch position with:

  • event - returns the JSON data tied with the event mousedown, mouseup, mosemove, touchmove,touch starts etc.
  • .clientX - gets the absolute X position of the mouse or touch
  • .touches[0] - gets the data of the first touch
  • || - means or
function getPosition(event) {
	 var mouse_x = event.clientX || event.touches[0].clientX;
   	 var mouse_y = event.clientY || event.touches[0].clientY;<br>

Get the position relative with the canvas by subtracting the left and top as shown in the figure

  	 coord.x = mouse_x - canvas.offsetLeft;
         coord.y = mouse_y - canvas.offsetTop; }<br>

Step 7: Check Mouse Position

get the distance of the mouse position to the relative to the circle's center using the Pythagorean theorem.

  function is_it_in_the_circle() {
            var current_radius = Math.sqrt(Math.pow(coord.x - x_orig, 2) + Math.pow(coord.y - y_orig, 2));<br>

If its less than the joystick's circle radius, it returns true

 if (radius >= current_radius) return true<br>

else it return false

 else return false
}<br>

Step 8: Start Drawing

To start Drawing, we set paint as true. the variable "paint" is used for determining if the joystick should be drawn upon mouse movements

   function startDrawing(event) {
            paint = true;<br>

gets the position of the mouse

            getPosition(event);

Calls the function is_it_in_the_circle(). if it returns true, clear the canvas and draw the background, the joystick with the mouse position as its center and call the function draw

 if (is_it_in_the_circle()) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                background();
                joystick(coord.x, coord.y);
                Draw(); }}

Step 9: Draws

If paint is true, we will start drawing

  if (paint) {<br>

clears the canvas

  ctx.clearRect(0, 0, canvas.width, canvas.height);<br>

draw the background

 background();<br>

Computes for the angle in radians

var angle = Math.atan2((coord.y - y_orig), (coord.x - x_orig));
                if (Math.sign(angle) == -1) {
                    angle_in_degrees = Math.round(-angle * 180 / Math.PI);
                }
                else {
                    angle_in_degrees =Math.round( 360 - angle * 180 / Math.PI);
                }<br>

Call the function is_it_in_the_circle() to check if its within the circle. if true, draw the joystick with the mouse as its center

 if (is_it_in_the_circle()) {
                    joystick(coord.x, coord.y);<br>

save the x and y coordinates for later use

   		    x = coord.x;
                    y = coord.y; }<br>

if its outside the circle, draw the joysticks circle at the same angle with the mouse position at its original radius

else {
                    x = radius * Math.cos(angle) + x_orig;
                    y = radius * Math.sin(angle) + y_orig;
                    joystick(x, y);}<br>

get mouse position

getPosition(event);<br>

Calculate the radius for its speed and rounds its value

var speed =  Math.round(100 * Math.sqrt(Math.pow(x - x_orig, 2) + Math.pow(y - y_orig, 2)) / radius);

Return the relative x and y value with respect to the axis of the joystick(original x and y coordinates)

 		var x_relative = Math.round(x - x_orig);
                var y_relative = Math.round(y - y_orig);<br>

writes the value for thecorresponding "x_coordinate", "y_coordinate",speed and angle values

		document.getElementById("x_coordinate").innerText =  x_relative;
                document.getElementById("y_coordinate").innerText =y_relative ;
                document.getElementById("speed").innerText = speed;
                document.getElementById("angle").innerText = angle_in_degrees;

Calls the function send and send the values of x_relative,y_relative,speed and angle_in_degrees

  send( x_relative,y_relative,speed,angle_in_degrees);<br>

Step 10: Stop Drawing

Sets the variable paint as false to ensure that it doesn't draw the joystick even if the mouse is moved

 function stopDrawing() {
            paint = false;<br>

Clears the canvas

    ctx.clearRect(0, 0, canvas.width, canvas.height);<br>

draws the background

   background();<br>

draw the joystick at its original coordinates

   joystick(width / 2, height / 3);<br>

writes the value for the "x_coordinate", "y_coordinate",speed and angle as 0

document.getElementById("x_coordinate").innerText = 0;
            document.getElementById("y_coordinate").innerText = 0;
            document.getElementById("speed").innerText = 0;
            document.getElementById("angle").innerText = 0; }

Step 11: Add Websocket

Connects with the arduino websocket at port 81

 var connection = new WebSocket('ws://' + "192.168.4.1" + ':81/', ['arduino']);<br>

On connection with the sends the date to the arduino

connection.onopen = function () {
            connection.send('Connect ' + new Date()); };<br>

On error, Send an alert with the error

connection.onerror = function (error) {
            alert('WebSocket Error ', error); };<br>

Upon receiving a message form the arduino, print it on the console

connection.onmessage = function (e) {
            console.log('Server: ', e.data); };<br>

A function named send saves the x,y,speed and angle values to a JSON format

function send(x,y,speed,angle){
            var data = {"x":x,"y":y,"speed":speed,"angle":angle};<br>

Turn the JSON object to stringify

  data = JSON.stringify(data);<br>

prints the data to the console

 console.log(data);<br>

sends the data to the arduino (the server)

        connection.send(data);

Step 12: Final Code

To view the code copy this to VSCode, save it as html and open it with google chrome. Open the developer tools by Pressing Command + Option + J (Mac) or Control + Shift + J (Windows, Linux, Chrome OS). The error on the console log remains since its not connected to the esp8266 server.

<!DOCTYPE html>
<html>
<head>
    <title>
        Mousebot
    </title>
    <meta name="viewport" content="user-scalable=no">
</head>
<body  style="position: fixed; font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif ;
color:rgb(128, 128, 128);
font-size: xx-large;">
    <h1 style="text-align:center">
        MOUSEBOT </h1>
    <p style="text-align: center;">
        X: <span id="x_coordinate"> </span>
        Y: <span id="y_coordinate"> </span>
        Speed: <span id="speed"> </span> %
        Angle: <span id="angle"> </span>
    </p>
    <canvas id="canvas" name="game"></canvas>
    <script>
        var connection = new WebSocket('ws://' + "192.168.4.1" + ':81/', ['arduino']);
        connection.onopen = function () {
            connection.send('Connect ' + new Date());
        };
        connection.onerror = function (error) {
            console.log('WebSocket Error ', error);
            alert('WebSocket Error ', error);
        };
        connection.onmessage = function (e) {
            console.log('Server: ', e.data);
        };

        function send(x,y,speed,angle){
            var data = {"x":x,"y":y,"speed":speed,"angle":angle};
            data = JSON.stringify(data);
            console.log(data);
            connection.send(data);
        }


    </script>
    <script>
        var canvas, ctx;

        window.addEventListener('load', () => {

            canvas = document.getElementById('canvas');
            ctx = canvas.getContext('2d');          
            resize(); 

            document.addEventListener('mousedown', startDrawing);
            document.addEventListener('mouseup', stopDrawing);
            document.addEventListener('mousemove', Draw);

            document.addEventListener('touchstart', startDrawing);
            document.addEventListener('touchend', stopDrawing);
            document.addEventListener('touchcancel', stopDrawing);
            document.addEventListener('touchmove', Draw);
            window.addEventListener('resize', resize);

            document.getElementById("x_coordinate").innerText = 0;
            document.getElementById("y_coordinate").innerText = 0;
            document.getElementById("speed").innerText = 0;
            document.getElementById("angle").innerText = 0;
        });

      


        var width, height, radius, x_orig, y_orig;
        function resize() {
            width = window.innerWidth;
            radius = 200;
            height = radius * 6.5;
            ctx.canvas.width = width;
            ctx.canvas.height = height;
            background();
            joystick(width / 2, height / 3);
        }

        function background() {
            x_orig = width / 2;
            y_orig = height / 3;

            ctx.beginPath();
            ctx.arc(x_orig, y_orig, radius + 20, 0, Math.PI * 2, true);
            ctx.fillStyle = '#ECE5E5';
            ctx.fill();
        }

        function joystick(width, height) {
            ctx.beginPath();
            ctx.arc(width, height, radius, 0, Math.PI * 2, true);
            ctx.fillStyle = '#F08080';
            ctx.fill();
            ctx.strokeStyle = '#F6ABAB';
            ctx.lineWidth = 8;
            ctx.stroke();
        }

        let coord = { x: 0, y: 0 };
        let paint = false;

        function getPosition(event) {
            var mouse_x = event.clientX || event.touches[0].clientX;
            var mouse_y = event.clientY || event.touches[0].clientY;
            coord.x = mouse_x - canvas.offsetLeft;
            coord.y = mouse_y - canvas.offsetTop;
        }

        function is_it_in_the_circle() {
            var current_radius = Math.sqrt(Math.pow(coord.x - x_orig, 2) + Math.pow(coord.y - y_orig, 2));
            if (radius >= current_radius) return true
            else return false
        }


        function startDrawing(event) {
            paint = true;
            getPosition(event);
            if (is_it_in_the_circle()) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                background();
                joystick(coord.x, coord.y);
                Draw();
            }
        }


        function stopDrawing() {
            paint = false;
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            background();
            joystick(width / 2, height / 3);
            document.getElementById("x_coordinate").innerText = 0;
            document.getElementById("y_coordinate").innerText = 0;
            document.getElementById("speed").innerText = 0;
            document.getElementById("angle").innerText = 0;

        }

        function Draw(event) {

            if (paint) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                background();
                var angle_in_degrees,x, y, speed;
                var angle = Math.atan2((coord.y - y_orig), (coord.x - x_orig));

                if (Math.sign(angle) == -1) {
                    angle_in_degrees = Math.round(-angle * 180 / Math.PI);
                }
                else {
                    angle_in_degrees =Math.round( 360 - angle * 180 / Math.PI);
                }


                if (is_it_in_the_circle()) {
                    joystick(coord.x, coord.y);
                    x = coord.x;
                    y = coord.y;
                }
                else {
                    x = radius * Math.cos(angle) + x_orig;
                    y = radius * Math.sin(angle) + y_orig;
                    joystick(x, y);
                }

            
                getPosition(event);

                var speed =  Math.round(100 * Math.sqrt(Math.pow(x - x_orig, 2) + Math.pow(y - y_orig, 2)) / radius);

                var x_relative = Math.round(x - x_orig);
                var y_relative = Math.round(y - y_orig);
                

                document.getElementById("x_coordinate").innerText =  x_relative;
                document.getElementById("y_coordinate").innerText =y_relative ;
                document.getElementById("speed").innerText = speed;
                document.getElementById("angle").innerText = angle_in_degrees;

                send( x_relative,y_relative,speed,angle_in_degrees);
            }
        } 
    </script>
</body>
</html><br>