YOUR GUIDE TO

Interactivity on the Canvas with JavaScript

Including dragging, gestures, transforms, hit tests and more!

Welcome readers from ◎ Your Guide to Coding Creativity on the Canvas

We capture traditional events such as click, mousedown, mouseover, mouseout, pressmove, pressup, keydown, keyup with the on() method, a shortcut to the JavaScript addEventListener().

You are welcome to code with an Online Editor. Copy or type and TEST this:

const button = new Button().center();
button.on("click", ()=>{
button.mov(10);
stage.update();
});

Note: we are using the ZIM JavaScript Canvas Framework. For options, please see ◎ Your Guide to Selecting a JavaScript Canvas Library or Framework. You may want to review ◎ Your Guide to Components on the Canvas with JavaScript.

Image for post
Image for post
Traditional click event on Button

Unfortunately, the on() method should not be chained as it does not return the object it is placed on but rather an id for removing the event:

For seeing why would want to chain, here is ◎ Your Guide to Conveniences when Coding on the Canvas.

const button = new Button().center();
const id = button.on("click", ()=>{
button.mov(10);
stage.update();
button.off("click", id);
});
// here is how we can remove the listener
// there are a couple ways:
// 1. use the clear() method of the event object e
// (an event object gives us more info about the event)
button.on("click", e=>{
button.mov(10);
stage.update();
e.clear();
});
// 2. or use the fourth parameter of the on() method
// this is the version we usually use:
button.on("click", ()=>{
button.mov(10);
stage.update();
}, null, true); // capture only once

Tap and Change !

ZIM has provided a few chainable methods like tap() and change() to capture common events. (There is also hold() and movement().) These receive a single callback function that will run when the event happens. The methods return the object so they can be chained.

new Button().center().tap(e=>{
e.target.mov(10);
stage.update();
});
// CHANGE
// captures a change event
// CLEAR, copy or type and TEST this:
const back = new Rectangle(stageW, stageH, darker)
.alp(0)
.addTo();
new Dial({sound:true, max:1})
.sca(2)
.center()
.change(e=>{
back.alp(e.target.currentValue);
stage.update();
});
Image for post
Image for post
Using change() to change properties with a component

Wire and Wired!

ZIM also provides wire() to directly wire properties together. This is an alternative to events with the on() method and is new to ZIM Cat. The wire() method connects a source to a target and the wired() method connects a target to a source. Otherwise, they are the same.

Here is the previous example made with wire():

const back = new Rectangle(stageW, stageH, darker)
.addTo();
new Dial({sound:true, max:1})
.sca(2)
.center()
.wire(back, "alpha");
Image for post
Image for post
Connecting source and target properties with wire()

Interaction Beyond Events

Events are your basic way to capture interactivity. With ZIM, we have powerful built-in methods for interactivity. These include drag(), gesture(), transform(). There are also controls like MotionController, Swipe, Swiper, Parallax, Physics, etc. that provide interactivity. We look at these in ◎ Your Guide to Controling the Canvas with JavaScript.

Dragging on the Canvas

One-line drag and drop is fantastic! In the dawn of HTML 5, we kid you not, there was an 1800 character drag and drop script with lines that went off the screen. Of course, jQuery came along to help. On the canvas, we have ZIM.

ZIM drag() uses events in behind the scene. It is a mousedown to find out where the object starts relative to the mouse. Then we use a pressmove event to place the object as the mouse moves. When we pressup we remove the pressmove event, etc. But we also have to keep track of containers, rotation and scaling on containers, and a few other tricky bits.

Try this out in the Online Editor:

Now check this out:

Image for post
Image for post
The child of a container being dragged by default

Above, we see that drag() will by default drag whatever inside the container was pressed. We accomplish this by using the target of the event rather than the currentTarget (the object the event is placed on). Here is how to drag the whole container:

Image for post
Image for post
The whole container dragged with all:true
Image for post
Image for post
Parameters of ZIM drag()

ZIM drag() has other parameters. You can slide an object, drag it so it contains the bounds, etc. Here are two more basic options: a boundary and onTop. CLEAR, Type and TEST this:

// the circle can be dragged half way off the stage 
// because the boundary contains the registration point
// which for a circle is in the middle
Image for post
Image for post
The circle can be dragged half way off the stage
// the circle stays fully on the stage
// or within any Display Object specified
Image for post
Image for post
The circle can be dragged to the edge of the stage

When we drag, we usually want what we are dragging to come up to the top. ZIM drag() does this by default — or at least the object comes to the top of its container. Here is how you can prevent this. Try these:

Image for post
Image for post
The dragged object is on top by default
Image for post
Image for post
The dragged object is not on top with onTop:false

Gestures on the Canvas

Pinch, Pan and Rotate are common mobile gestures. ZIM provides support for this with the gesture() method. Try this on a multitouch screen or see https://zimjs.com/gesture. Parameters are available to choose which gestures to use as well as to set a boundary.

Image for post
Image for post

Transforming on the Canvas

PhotoShop has transformation tools with little handles to let you scale, move, stretch and rotate an object. ZIM has these as well with the transform() method. This is amazing! It means that people using your app on the canvas can transform objects. This is very empowering for things like meme makers, collage tools, planning apps, etc. CLEAR and TEST this:

Image for post
Image for post
ZIM transform() method with larger handles for demonstration
Image for post
Image for post
A transformed rectangle :: pressing off the rectangle hides transforms

Again, parameters are available as to what to transform. Transforms are easily saved with the TransformManager() class. The TransformManager also works with Blob() and Squiggle() which have their own transforms.

// pass a reference to the object to remember
// or pass in an array of objects
// then pass a string for the persist id
new TransformManager(rect, "medium");

With one line of code the user transforms are saved

There is also the ZIM Layer() class which handles nested transforms.

Image for post
Image for post
ZIM Layer() class for nested transforms

Hit Tests and Conditionals

As you drag something or when you drop it, you may want to find out if that something is hitting something else. We call this a hitTest. We use a conditional to do the test in some event function. We might in general want to see if things are hitting and we usually use a ZIM Ticker for that.

A conditional is a Programming Basic available in all languages that is basically an if statement. Or an if else statement, or an if else if statement. The condition goes in the round brackets () of the if and the block of code to run if true goes in the curly brackets {}. See the ZIM Kids Magic page for a quick overview or the ZIM Skool Lesson 06 for more details.

Image for post
Image for post
ZIM Tips for HitTests

There are many nuances to hitTest and so we will guide you to the ZIM Tips Page on Hit Tests. However, let’s try a couple and point out the basics.

const rect = new Rectangle().loc(100,100).drag();

const score = new Label(0).sca(2).pos(100,100,RIGHT);

rect.on("pressmove", ()=>{
if (rect.hitTestBounds(circle)) {
score.text = Number(score.text) + 1;
stage.update();
}
});
Image for post
Image for post
HitTestBounds and conditional to test if rectangle hits circle

Above we use a conditional in a pressmove event and the hitTestBounds() method to see if the bounds of these two shapes are hitting. The bounds are a rectangle around the object. This makes the test very fast as it is a simple equation but the hit test is not accurate. The rectangle in the picture below will be considered to be hitting.

Image for post
Image for post
Rectangle hitting bounds of circle — boo

Another issue is that the points only go up when the rectangle is being moved. Maybe we want them to go up (or down) all the time as long as the two shapes are hitting. Or perhaps they are two shapes that are moving on their own, so there is no pressmove event, and we want to know if they are hitting. We can solve this problem with a Ticker.

A Ticker is a ZIM construct that runs constantly at the frame rate (60 frames per second on desktop and 30 frames per second on mobile). You pass a function to the Ticker to run the function at this rate. The ticker will update the stage. ZIM animate, drag, etc. make use of the Ticker so there is only ever one frame update which conserves power and processing.

const rect = new Rectangle().loc(100,100).drag();

const score = new Label(0).sca(2).pos(100,100,RIGHT);

Ticker.add(()=>{
if (rect.hitTestCircle(circle)) {
score.text = Number(score.text) + 1;
stage.update();
}
});
Image for post
Image for post
Hit test in a Ticker with hitTestCircle() is not hitting — yay!

Above, we use a Ticker so the score goes up even when the rectangle is not moving. We also use hitTestCircle() which is a better hit test for these shapes.

hitTestCircle()

tests any shape against points on a circle

Testing if two odd shapes are hitting one another is too processor intensive. We can, however, test if a point is hitting an odd shape. So what we can do to test an odd shape against a rectangle or circle is put points around the rectangle or circle and test the shape against each of these points.

In the case above, we can programmatically test if a rectangle is hitting a circle or if two circles are hitting one another using hitTestCircleRect() or hitTestCircles(). So a faster, more reliable hit test would be as follows:

Image for post
Image for post
The ZIM hit tests in the ZIM Docs under Methods > Hit Tests

Last Words on Conditionals

In the code above, we have thrown in an if () {} else {} statement. This would reduce the score unless you are hitting the object. Wow… we would just have to make the circle move and we have a game! You will have to read ◎ Your Guide to Animation on the Canvas with JavaScript.

A conditional will run the first block of code if the expression in the round brackets is true. So, what can we put in the round brackets? Here are some examples:

if (x > 1) {} // a greater than comparison operator if (x <= 20) {} // a less than or equal comparison operator // we can use && (and) and || (or)
// and note that the equality comparison operator
// is a double equal == not a single equal =
// the single = is the assignment operator (different)
if (a == "hello" && b == "bye") {} // we can test for negativesif (a != "hello") {}
if (!rect) {}

CONCLUSION

This has been a guide on special interactivity. That is to say, interactivity beyond the traditional events found in ◎ Your Guide to Components on the Canvas with JavaScript but not as far as the controls found in ◎ Your Guide to Controling the Canvas with JavaScript.

Further Reading

Image for post
Image for post
Interactive Media tends towards Creation, Web sites tend towards Consumption

As you can see, Interactive Media has a lot that relates to interactivity! How about that. We love to empower the people and allow them to create not just consume. Please see ◎ Your Guide to When to Use a Canvas Library or Framework in which the different types of interactivity are discussed.

If you would like to see what coding is like on a Canvas Library or Framework here is ◎ Your Guide to Coding Creativity on the Canvas.

At any time in the Your Guide to series for the canvas, you are welcome to go to the ZIM Site and look through the Learn Section or the ten banners on the front page.

All the best, Dr Abstract.

Image for post
Image for post

Follow us on Twitter at ZIM Learn and here is ZIM Learn on YouTube!

Written by

Inventor, Founder of ZIM JavaScript Canvas Framework and Nodism, Professor of Interactive Media at Sheridan, Canadian New Media Awards Programmer and Educator

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store