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

2020-09-30 22:04:45 | #programming #javascript #html5 #canvas

Tested On

  • Linux Ubuntu 20.04
  • Windows 10
  • macOS Catalina

Today, we learn how to create a shape, using JavaScript and HTML5 Canvas, that hunts down the user's mouse or touch. To make this follower look natural, we'll simulate some physics by giving the shape acceleration, force/speed, and friction.

This is a follow-up to How to Get a Shape to Point to Mouse or Touch with HTML5 Canvas. I strongly recommend you complete that tutorial, first, as it explains shape rotation towards a point, with plenty of real-world scenarios, charts, and formulas, and examples.

But first, a demo. Click the "Run" button. Then, interact with the canvas, below, and notice how the arrows accelerate and make sharp turns towards your mouse/touch. If you look closely, you'll notice that it's actually the first arrow that targets your mouse, while the remaining arrows target each other. Try updating the code by adding more arrows and switching their targets. And when you're ready, proceed to the next section, and learn how to code this functionality, yourself.

Code Editor

Angular Velocity

The main concept, here, involves two steps. First, we need to use the distance between the shape and the mouse to calculate the angle the shape needs to rotate towards. We need then to calculate the velocity, or how many pixels it should travel along the x and y axes, per frame.

The first step is explained in extensive detail in one of our previous tutorials, but I'll summarize it, here. To get the distance on both axes, we just subtract the position of the mouse from the shape's position. Notice the right triangle? Knowing the length of the opposite leg and the adjacent leg, we can use Math.atan2(mouseY - shapeY, mouseX - shapeX) to calculate the angle. We then use that angle to calculate the velocity the shape should accelerate with, towards the mouse. We then update the shape's position, nudging it pixel-by-pixel on each animation frame.

Sine Waves and Smooth Motion

If you've ever heard of sine waves or cosine waves, the image below may seem familiar to you. The sine wave on the left is a graph of the values produced by feeding the sine function all of the angles between 0 and 360. The sine of 0 degrees produces 0, 90 degrees produces 1, 180 degrees produces 0, 270 degrees produces -1, and, finally, 360 degrees produces 0 again. If you were to keep increasing the degrees, the sine wave would continue, so what we have here is a formula for producing a range of values (1 to -1), back and forth, forever. And if you were to multiply those values by 50, you could get a range of 50 to -50. The cosine wave on the right is the same, except the values are shifted over, giving you a different starting position, but producing the same values.

So how exactly do these waves apply to animation? Well, by feeding the sine or cosine of an angle that we increment on every animation frame, we can produce that range of 50 to -50 and animate a shape smoothly going up and down on the y axis, left and right on the x axis, or use both sine and cosine waves simulateously to move the shape in a circle mothion.

How to Code Angular Velocity on a 2D Plane with JavaScript and HTML5 Canvas

There's quite a bit of code involved, but we'll explain each section, in detail. First, we need to create a Polygon class that we can instantiate whenever we need a polygonal shape. This example contains point data for an arrow, since that's all we need for this tutorial. We immediately declare shape-related properties, such as x, y, width, height, rotation, and physics properties like force, friction, vx and vy.

Within the render function on lines 30-69, we pass the angle into Math.cos() and Math.sin(), which produces an elliptical movement. To explain in more detail, passing an incrementing angle into Math.sin() and multiplying it by the force, produces a range of values to oscillate between. Math.cos() produces the same values, but they are offset, by Math.pi / 2, and when the sine values are assigned to the shape's x axis and the cosine values are passed into the y axis, we get that circular hunting behavior.

var Polygon = function(kind, x, y, width, height, force, friction) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
  this.rotation = 0;
  this.force = force;
  this.friction = friction;
  this.vx = 0;
  this.vy = 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.render = function(ctx, targetX, targetY) {
    ctx.save();
    this.points = generateShape(kind);

    var dx = targetX - this.x;
    var dy = targetY - this.y;
    var angle = Math.atan2(dy, dx);
    var rotation = angle + Math.PI / 2;

    var ax = Math.cos(angle) * this.force;
    var ay = Math.sin(angle) * this.force;

    this.vx += ax;
    this.vy += ay;

    this.vx *= this.friction;
    this.vy *= this.friction;

    this.rotation = rotation;
    this.x += this.vx;
    this.y += this.vy;

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

    // 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 rect = canvas.getBoundingClientRect();
  var offsetTop = rect.top;
  var offsetLeft = rect.left;

  var onMove = function(evt) {
    evt.preventDefault();

    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('my-canvas');
var ctx = canvas.getContext('2d');
var targetX = canvas.width * 0.5;
var targetY = canvas.height * 0.5;

var arrow1 = new Polygon('arrow', 100, 100, 50, 50, 0.3, 0.97);
var arrow2 = new Polygon('arrow', 100, 100, 50, 50, 0.2, 0.97);
var arrow3 = new Polygon('arrow', 100, 100, 50, 50, 0.1, 0.97);

arrow1.render(ctx, targetX, targetY);
arrow2.render(ctx, targetX, targetY);
arrow3.render(ctx, targetX, targetY);

var mtt = new MouseTouchTracker(canvas, function(x, y) {
  targetX = x;
  targetY = y;
});

function loop() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  arrow1.render(ctx, targetX, targetY);
  arrow2.render(ctx, arrow1.x, arrow1.y);
  arrow3.render(ctx, arrow2.x, arrow2.y);

  requestAnimationFrame(loop);
}

loop();

The rest of the code involves setting up an event listener for mouse and touch events, instantiating arrows, and updating their rotation and position at 60fps.

Conclusion

We hope you find this guide useful.

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