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

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 listenerconst 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.
// TAP
// captures a click event
// we could assign the button to a variable
// or here we use the target of the event objectnew 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();
});

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():
// WIRE
// connects propertiesconst back = new Rectangle(stageW, stageH, darker)
.addTo();new Dial({sound:true, max:1})
.sca(2)
.center()
.wire(back, "alpha");

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:
new Circle().center().drag();
Now check this out:
new Tile().center().drag();

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});


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

new Circle().center().drag({
boundary:stage
});// the circle stays fully on the stage
// or within any Display Object specified

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

new Tile(new Circle(50, yellow, dark, 10))
.center()
.drag({onTop:false}); // not on top

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

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


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 idnew TransformManager(rect, "medium");
With one line of code the user transforms are saved
There is also the ZIM Layer() class which handles 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 anif else
statement, or anif 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.

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();
}
});

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.

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();
}
});

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();
});

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

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.

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