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

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

// we see STYLE in an upcoming guide
// this is optional - just tired of rounded orange buttons
STYLE = {corner:0, backgroundColor:blue, scale:2};
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.

// Do not chain on() 
// because it returns an id for removing the listener
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 !

// TAP
// captures a click event
// we could assign the button to a variable
// or here we use the target of the event object
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!

Here is the previous example made with wire():

// WIRE
// connects properties
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

Dragging on the Canvas

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:

new Circle().center().drag();

Now check this out:

new Tile().center().drag();
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:

new Tile().center().drag({all:true});
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:

new Circle().center().drag({
boundary:new Boundary(0,0,stageW,stageH)
});
// 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
new Circle().center().drag({
boundary: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:

new Tile(new Circle(50, yellow, dark, 10))
.center()
.drag(); // the dragged object is on top
Image for post
Image for post
The dragged object is on top by default
new Tile(new Circle(50, yellow, dark, 10))
.center()
.drag({onTop:false}); // not on top
Image for post
Image for post
The dragged object is not on top with onTop:false

Gestures on the Canvas

new Rectangle(300,300).center().gesture();
Image for post
Image for post

Transforming on the Canvas

new Rectangle(300,300, green)
.center()
.transform();
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.

const rect = new Rectangle(300,300, green)
.center()
.transform();
// 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

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 circle = new Circle(100, red).center();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 circle = new Circle(100, red).center();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:

Ticker.add(()=>{
if (circle.hitTestCircleRect(rect)) {
score.text = Number(score.text) + 1;
} else {
score.text = Number(score.text) - 1;
}
stage.update();
});
Image for post
Image for post
The ZIM hit tests in the ZIM Docs under Methods > Hit Tests

Last Words on Conditionals

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 (rect) {} // does rect hold an object?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

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!

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