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:
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>