Goodbye HTML. Hello Canvas!
Part 4: The Static Architecture & Simple Mouse Event Demo
You can read the previous article here.

The Goals For The Library
I am constructing the library as I am writing the articles. I have some goals for the library and for the application made with it:
- the library must be free of bugs
- the library must be very easy to understand/remember/use
- the library must be flexible, easy to fix/improve/extend
- the library must be, at the same time, small and complete
- the application must have a good performance
Although small, the library will be complete in the sense that it will have all the basic stuff, not having to write much code to mount your interface.
Note: the library is being prototyped; this is the best moment for me to listen to your ideas/preferences.
Blah, Blah, Blah (or Rationale About The Changes)
BobSprite is made with many canvases because it started in HTML/CSS mode and slowly converged to canvas mode.
When I promised to release a library, I decided to start with a brand-new model: one big canvas (the body) and nothing more, everything is painted directly on it.
But I am very concerned about this model because
- I have no practice with it. And I must have a full domain on the subject because it is the base of a library that other people will use to create apps, which I have no clue about
- I am developing the library as I create the articles. I don’t want to risk having to say in the eleventh article, “Hey, folks, let’s restart everything from the third article! This model is not good enough.”
- I want to release the library soon, but I am basing it on a new/untested concept
- I think this model might have a (maybe imperceptible) worse performance in case it paints the whole canvas; OR might demand a much-more-complex /less-robust library code, in case it paints only segments
Therefore, I decided to abandon the one canvas-only model. And… NO! I am not going to betray my readers and dishonor my name by subtly replacing the big canvas with tens of HTML elements inside the library.
Another set of concerns is related to how plain simple this library should appear to developers.
- I don’t want (in your code or in your mind) name clashes with HTML (body leaves, stage enters; element leaves, widget enters)
- I don’t want misleading names (layer leaves, virtual layer enters)
- I don’t want redundancies (virtual layer will have only the absolutely necessary properties)
- I want a few simple/essential/obvious elements, working in a few simple/essential/obvious relationships
The Static Architecture

Please, forget about the basic architecture that I have presented in the previous article. Not only did I have to change it, as I had to change its name from basic to static; because I’ve realized that we have to talk about the Dynamic Architecture (not in this article). So the name static is more appropriate.
The Stage
The stage is the ultimate container. It is the div HTML element, under the hood. You can set its background as any normal HTML div, and that’s all. Only virtual layers may be attached to the stage.
The Virtual Layers
A virtual layer is basically a list of panels. It is virtual because it has no substance (size, position, opacity, color, background…). Its purpose is to group panels by their depths (which is over which). When a virtual layer is created, it is automatically attached to the stage. Only panels may be attached to a virtual layer.
There is no HTML element equivalent to the virtual layer. You can think of it as an easier version of the z-index CSS property.
Panels
A panel, under the hood, is a canvas (the HTML canvas element) plus a list of its widgets and additional info (id, background, mouse handlers…). Only widgets can be attached to panels. Panels in the same virtual layer must not overlap each other.
Widgets
A typical widget is a button, a box for text input, a range slider… something that looks like an object to the user. The list of widgets is not finished. A widget must not escape the limits of its panel. Widgets must not overlap each other.
NOTE: Since the beginning, the purpose of the present article series was to present and to prove some concepts. This hasn’t changed. I will explain the concepts, show some code snippets, and present simple/succinct demos. Don’t worry about the code when you read the articles/demos. All the functionality will be encapsulated in the intuitive functions of the library.
"use strict"function assureVirginId(id, kind) {
//
let existent = ""
//
if (allVirtualLayers[id] != undefined) {
existent = "virtual layer"
}
else if (allPanels[id] != undefined) {
existent = "panel"
}
else if (allWidgets[id] != undefined) {
existent = "widget"
}
//
if (existent == "") { return true }
//
const msg = "ERROR: cannot use [" + id + "] as id for " +
kind + " because it is being used by a " + existent
//
alert(msg)
//
return false
}//////////////////////////////////////////////const allVirtualLayers = { }const allVirtualLayersOrder = [ ]function createVirtualLayer(id) {
//
if (! assureVirginId(id, "virtual layer")) { return null }
//
const vl = new VirtualLayer(id)
Object.seal(vl)
//
allVirtualLayers[id] = vl
allVirtualLayersOrder.push(vl)
return vl
}function VirtualLayer(id) {
//
this.id = id
this.visible = true
this.panels = [ ]
}///////////////////////////////////////////////const allPanels = { }function createPanel(id) {
//
if (! assureVirginId(id, "panel")) { return null }
//
const panel = new Panel(id)
Object.seal(panel)
//
allPanels[id] = panel
return panel
}function configPanel(id, cfg) {
//
const panel = allPanels[id]
if (panel == undefined) {
alert("ERROR: unknown panel [" + id + "]")
return
}
//
if (cfg.left) {
if (! assureGoodDimension(cfg.left)) { return }
panel.left = cfg.left
}
//
// more code here...
}function Panel(id) {
//
this.id = id
this.widgets = [ ]
//
const cnv = createCanvas(width, height)
const ctx = cnv.getContext("2d")
//
cnv.style.position = "absolute"
cnv.style.left = "0px"
cnv.style.top = "0px"
cnv.style.backgroundColor = "tan"
//
cnv.onmouseenter = null
cnv.onmouseleave = null
cnv.onmouseup = null
cnv.onmousedown = function (e) { mouseDownOnPanelById(e, id) }
cnv.onclick = null
//
this.canvas = cnv
this.context = ctx
}/////////////////////////////////////////////////// more code here...
Above, you have a glimpse of something that resembles the library that is being constructed. The code is not encapsulated yet (the functions are not called with a library prefix, like lib.createPanel).
Before you complain that the event handling of the panels is pretty ordinary (indeed it is), let me say that the complexity is in the event handling of the widgets (not shown above). Don’t worry, the library is in charge of that.
One thing you might have observed is that the library will try its best to warn you about every possible error. It is the opposite of what HTML and especially CSS do! Silent errors are the worst part of CSS, in my opinion.
Simple Mouse Events Demo
This demo handles mouse events in the most possible simple form; it doesn’t care if the user moves the mouse too fast or clicks too fast.
If you want to go deep in handling mouse events, I recommend the article below, but only if you really want to go deep.
This demo is about creating
- 2 virtual layers
- 2 panels (one for each virtual layer); they overlap because the sum of their widths is greater than the width of the stage
- 2 surfaces (the simplest kind of widget) on the left panel
Mouse down on the first surface exchanges colors of both surfaces.
Mouse down on the second surface inverts the order of the virtual layers, inverting the z-order of their panels.
There is more interesting functionality, that I wanted to show in the demo; but this stuff belongs to the Dynamic Architecture, so we will have to wait for the next article.

The code (embedded in the HTML file) is split into two sections: the pseudo library section and the developer section. Above we see the code of the developer section: just a few lines of clear code!
Without further ado, let’s check our minimalistic demo here.
How It Works
There are no mouse event handlers for the stage or virtual layers.
But for each panel (for the canvas of each panel, properly speaking), the engine (internal functions of the library) creates mouse event handlers.
For each mouse event on a panel, the engine starts to check if there is a widget involved, considering the size and position of all the widgets that belong to that panel.
If there is no widget involved, the engine ends this process. Otherwise, the engine searches this widget for a handler that matches the mouse event. If such a handler exists (not null), it is activated (its callback function is executed).
You can read the next article here.
More content at PlainEnglish.io. Sign up for our free weekly newsletter. Follow us on Twitter and LinkedIn. Join our community Discord.