How to Get a Shape to Point to Mouse or Touch with JavaScript and HTML5 Canvas

2020-09-25 18:00:40 | #programming #javascript #html5 #canvas

Tested On

  • Linux Ubuntu 20.04
  • Windows 10
  • macOS Catalina

First, I have to warn you, this is going to involve a tiny bit of Trigonometry, but I promise it won't be difficult! Alright, for everyone who's still here, let's continue. So why Trigonometry? Because when dealing with a 2d plane, you'll find that the points between shapes and the mouse form right triangles. And triangles are made up of angles we use to rotate shapes with.

But before we dive into that, let's take a look at a demo of what we'll be building. Try moving your mouse or finger over the canvas, below, and observe how the arrows rotate in its direction. And feel free to update the positions, width and height of the arrows in the code editor. If you don't understand what some of this syntax means, don't worry. It will all be explained.

Code Editor

Points and Right Triangles

Take a look at the following diagram. Notice that no matter where the mouse cursor is positioned, a right triangle is formed. The distance between the shape and the mouse on the x and y axes form the adjacent and opposite legs of the triangle, and the direct line forms the hypotenuse.

If you read that last sentence and thought, "hypote-what?", here's another diagram. When we talk about right triangles, we label them like so: the angle is the angle opposite of the 90°right angle, the adjacent leg is the leg right next to the angle, the opposite leg is the leg on the opposite end of the angle, and the hypotenuse is the longest leg.

Why Do We Need to Understand Right Triangles to Rotate a Shape Towards the Mouse?

Well, when we position a shape on the canvas, we know the XY coordinates of the shape's location. And when we move our mouse on the screen, we'll also know the mouse's XY coordinates. This makes it easy to measure our right triangle's adjacent leg (on the x-axis), and the opposite leg (on the y-axis), with some simple subtraction:

The problem, however, is that we don't know the angle the shape should be rotated. As the mouse moves, it could be 30°, 192°, or really any angle between 0° and 360°, so we need to know how to perform a calculation that will return that angle, by memorizing some formulas. To help with that memorization, we rely on SOH-CAH-TOA.

SOH-CAH-TOA

SOH-CAH-TOA is a useful mnemonic device for remembering whether to use sine, cosine, or tangent when trying to calculate the length of an unknown leg of a triangle. But wait, aren't we trying to determine the unknown angle of a triangle? Yes, that's correct. We'll get to that, in a second. First, you have to familiarize yourself with sine, cosine, and tangent.

Abbreviation Description Formula
SOH Sine is Opposite over Hypotenuse sin(angle) = o/h
CAH Cosine is Adjacent over Hypotenuse cos(angle) = a/h
TOA Tangent is Opposite over Adjacent tan(angle) = o/a

Sine, Cosine, and Tangent are very useful in performing calculations related to height and distance. So when you know the angle, and one of the legs, you can use one of these formulas to determine the other leg. Let's illustrate this concept with a real-world scenario.

If you drop an anchor, there's a chance ocean currents are going to pull the boat and the anchor apart some distance before the anchor reaches the floor. You won't need to reposition the boat or anchor to find the depth of the ocean, however, because by knowing the length of the cable and the angle the cable makes with the sea floor (39° in this case), you can calculate that the ocean is 25.2 meters deep (opposite leg) with the formula sin(39) = x / 40. Remember, sine is the opposite leg over the hypotenuse.

sin(39) = x / 40
.68 = x / 40
40 * .68 = x
25.2 = x

Alternatively, if you know the angle and the length of the adjacent leg, you can use cosine to determine the length of the hypotenuse, and you can use tangent to calculate the length of the opposite leg.

Sine, Cosine, and Tangent are just ratios of the legs of a right triangle. Notice that in our example above, if you divide the opposite leg (25.2) by the hypotenuse (40), you get 0.63. And if you plug sin(39°) into your calculator, you also get 0.63. In JavaScript, you will have to convert to radians first, like so: Math.sin(39 * Math.PI / 180), but you will reach the same result.

How Do I Calculate The Unknown Angle of a Right Triangle?

Ok, now that we are familiar with using sine, cosine, and tangent to calculate an unknown leg, we can finally learn how to calculate the angle. We use arcsine, arccosine, arctangent. when when we already know the sides, but need to determine the angle of a right triangle. So with our example above, if you knew the hypotenuse and the opposite leg, you could plug it into the following formula: Math.asin(25.2/40) * 180 / Math.PI, and get 39°, which is exactly the angle the cable makes with the sea floor. Note: We had to multiply by 180 / Math.PI to convert the resulting radians to degrees. Now that we know how to go back and forth, let's learn about how to calculate the angle/rotation, when we already know the length of the opposite leg and the adjacent leg.

Arc Tangent vs Arc Tangent 2

To calculate the angle, we need to use Arc Tangent 2, like so: Math.atan2(length_of_opposite_leg, length_of_adjacent_leg). So why use this instead of Math.atan(opposite_leg/adjacent_leg) to calculate the rotation of our arrow? First, let's review how Math.atan() works. Math.atan() accepts a ratio of the opposite leg / adjacent leg.

So given the above 4 right triangles that are produced when a Rectangle is placed in the middle of the screen, and the mouse cursor is at any of the 4 cursor positions, we get the following 4 ratios to pass into Math.atan().

Quadrant Legs Ratio
A -1/2 -0.5
B 1/2 0.5
C 1/-2 -0.5
D -1/-2 0.5

Notice how A and C, and B and D produce the same ratios? This is a big problem when we want to accurately rotate a shape across all 4 quadrants, across all 360°. Ratios won't suffice. There is where Math.atan2(y, x) comes in. Instead of accepting a ratio, Math.atan2(y, x) takes the measurement of 2 sides. So in the case of our mouse cursor, relative to the shape's position, we pass in the y measurement (mouse.y - shape.y) and the x measurement (mouse.x - shape.x), which gives us the proper rotation.

Let's Write Some JavaScript Code

At this point, you might be thinking, "Enough with the math lesson—just give me the code, already!" Here it is, in full. Just import it into an HTML file, with a canvas element with an ID of "canvas", and you will have your rotation shape. If this sounds confusing, you might want to take our Learn JavaScript and ES6 with HTML5 Canvas course, which sets you up, from scratch.

var Polygon = function(kind, x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
  this.rotation = 0;

  var generateShape = function(kind) {
    switch(kind) {
      case 'arrow':
        return [
          this.x + this.width * 0.25, this.y + this.height,
          this.x + this.width * 0.25, this.y + this.height * 0.5,
          this.x, this.y + this.height * 0.5,
          this.x + this.width * 0.5, this.y,
          this.x + this.width, this.y + this.height * 0.5,
          this.x + this.width * 0.75, this.y + this.height * 0.5,
          this.x + this.width * 0.75, this.y + this.height
        ];
        break;
    }
  }

  generateShape = generateShape.bind(this);
  this.points = generateShape(kind);

  this.render = function(ctx) {
    ctx.save();

    // Rotation
    ctx.translate(this.x, this.y);
    ctx.rotate(this.rotation);
    ctx.translate(-(this.x + this.width / 2), -(this.y + this.height /2));

    // Draw arrow
    ctx.beginPath();
    ctx.moveTo(this.points[0], this.points[1]);
    for (var i = 2, len = this.points.length; i < len; i+=2) {
      ctx.lineTo(this.points[i], this.points[i + 1]);
    }
    ctx.lineTo(this.points[0], this.points[1]);
    ctx.closePath();
    ctx.fillStyle = '#2793ef';
    ctx.fill();

    ctx.restore();
  }
}

var MouseTouchTracker = function(canvas, callback){
  var onMove = function(evt) {
    evt.preventDefault();

    var rect = canvas.getBoundingClientRect();
    var offsetTop = rect.top;
    var offsetLeft = rect.left;

    if (evt.touches) {
      callback(evt.touches[0].clientX - offsetLeft, evt.touches[0].clientY - offsetTop);
    } else {
      callback(evt.clientX - offsetLeft, evt.clientY - offsetTop);
    }
  }

  canvas.ontouchmove = onMove.bind(this);
  canvas.onmousemove = onMove.bind(this);
}


// Main
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var arrow = new Polygon('arrow', 100, 100, 50, 50);
arrow.render(ctx);

var mtt = new MouseTouchTracker(canvas, function(x, y) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  arrow.rotation = Math.atan2(y - arrow.y, x - arrow.x) + Math.PI / 2;
  arrow.render(ctx);
});

Here's an explanation of the code above.

Lines 1-47 set up a Polygon class that we can use to instantiate arrows. Lines 2-6 is where we declare some necessary shape properties. Lines 8-22 define a generateShape function, where we can pass in a polygon type (for example, "arrow"), and it will return vector points that create that shape relative to the shape's size and position. This is where we can also add triangle and hexagon vectors, but that's beyond the scope of this tutorial.

Lines 27-47 declare a render function we can call when we are ready to draw the shape to the canvas. To rotate the shape, we have to ctx.translate(this.x, this.y) (move) to the shape's location, rotate the context, then move back to where we started, minus half the shape's width and height, which will prepare the canvas to draw the shape with the centerpoint in the middle, instead of the top left. Having a centered centerpoint allows the shape to rotate from it's center.

Lines 50-67 provide a MouseTouchTracker class that adds a mouse/touch move listener to the canvas element, so that when the user interacts with the screen, we can use the XY coordinates to calculate the legs and angle of the triangle.

Line 74 is where we create an instance of our arrow with the desired size and position. Line 75 is how we render it to the screen. Line 77 is where we instantiate our mouse/touch tracker with the canvas element and a callback function that will get fired, and rerender the canvas on user interaction. Then we just clear the canvas before drawing on each frame, and rotate our arrow with using Arc Tangent 2.

Conclusion

So there you have it—a tutorial explaining how to calculate the rotation of a shape towards the mouse/touch, knowing only two sides of the right triangle that is formed by the distance between the shape and the mouse/touch.

If you're interested in making interactives and games, we recommend you take our Learn JavaScript While Building a Game Framework course, which teaches you JavaScript and ES6 while you build an HTML5 Canvas, physics-based framework, from scratch. You'll also learn drag and drop, mouse following, velocity, acceleration, friction, and many other useful functionality you can build interactives and games with. In addition, it compiles into a browser-compatible ES2015 build. Start making canvas applications, today.

Want To See More Exercises?

View Exercises View Courses

Comments

You must log in to comment. Don't have an account? Sign up for free.

Subscribe to comments for this post

Want To Receive More Free Content?

Would you like to receive free resources, tailored to help you reach your IT goals? Get started now, by leaving your email address below. We promise not to spam. You can also sign up for a free account and follow us on and engage with the community. You may opt out at any time.



Tell Us About Your Project









Contact Us

Do you have a specific IT problem that needs solving or just have a general IT question? Use the contact form to get in touch with us and an IT professional will be with you, momentarily.

Hire Us

We offer web development, enterprise software development, QA &amp; testing, google analytics, domains and hosting, databases, security, IT consulting, and other IT-related services.

Free IT Tutorials

Head over to our tutorials section to learn all about working with various IT solutions.

Contact