Drawing animations in ScheduleJS
Gaetan Gasoline
Posted on July 10, 2024
ScheduleJS uses the HTML Canvas rendering engine to draw the grid, activities, additional layers and links. This article explains how to design a simple ScheduleJS rendering animation using the HTML Canvas API
A few words on HTML Canvas
Have you ever been using the Canvas technology before? The canvas element is an HTML container used to draw programmatically using JavaScript at a surprisingly low cost for the browser.
The most significant feature of the canvas element is that its possibilities are endless in terms of design and interactions. The only limit to what’s on the screen is our imagination.
If you want to wrap your head around the canvas element, you can compare it to a blank drawing paper.
According to the MDN web documentation:
MDN: The Canvas API provides a means for drawing graphics via JavaScript and the HTML canvas element. Among other things, it can be used for animation, game graphics, data visualization, photo manipulation, and real-time video processing.
The Canvas API largely focuses on 2D graphics. The WebGL API, which also uses the canvas element, draws hardware-accelerated 2D and 3D graphics.
How does ScheduleJS use it?
Once you draw your activities on the canvas, the only way to get it to change is to clean it up and start drawing again.
Under the hood, ScheduleJS implements multiple tools to handle this behavior in the context of a scheduling application. These APIs are sometimes exposed to the developer and sometimes they work silently to let the developer focus on the core features of the application, like the following:
- The System Layers API handle successive drawings and stacking order.
- The Viewport API optimizes the rendering and navigation.
- The Renderer API defines how activities and layers are drawn.
- The Redraw API can be called at will to trigger redraws, operations on the rows, etc…
The most significant item for the developer in this architecture is the ScheduleJS Renderer API. Renderers use overridable functions allowing the developer to create his or her unique way of drawing specific parts of the application:
- The Background
- The Grid
- The Activities
- The Activity Links
- The Drag and Drop layer
- And every additional System Layers
Although this may seem complicated to some people, it is a workflow that developers will get used to quickly. The flexibility of the Renderer architecture and its well-thought implementation allows endless design and interaction scenarios.
Animating your ScheduleJS activities
To animate your Canvas, you have to break down your animation in frames and tell the renderer how to draw each frame. The main ingredient required to create a simple linear animation is the time at which the animation started. If we want to animate every activity on its own, a good place to store this information is the activity data structure.
// A simple activity class storing the animation start date as a timestamp
export class MyActivity extends MutableActivityBase {
animationStart: number = undefined;
}
Let’s create a simple animated renderer to draw every frame based on animation progression. This simple width animation will animate our activity on creation (from 0% width to full-width).
// Create our activity renderer for our simple animation
export class MyActivityRenderer extends ActivityBarRenderer<MyActivity, Row> {
// Our animation takes 250ms
private _animationDurationMs: number = 250;
// Override the drawActivity method of the ActivityBarRenderer
protected drawActivity(activityRef: ActivityRef<Action>, position: ViewPosition, ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, selected: boolean, hover: boolean, highlighted: boolean, pressed: boolean): ActivityBounds {
// What time is it? :)
const now = Date.now();
// Access your activity in the renderer
const activity = activityRef.getActivity();
// Set animationStart timestamp
if (activity.animationStart === undefined) {
activity.animationStart = now;
}
// The animationTimer tells us the current frame
const animationTimer = now - activity.animationStart;
// Calculate the sequence: 0 = animation starts, 1 = animation ends
const sequence = animationTimer / this._newActionAnimationDurationMs;
// Let's play with the width: starts at 0%, ends at 100%
w *= sequence > 1 ? 1 : sequence;
// Note: Calling directly graphics.redraw() will cause an infinite loop
requestAnimationFrame(() => {
// Force a redraw on every animation frame
this.getGraphics().redraw();
// Our custom drawing method
this.drawMyActivity(activity, x, y, w, h, selected, hover, highlighted, pressed);
});
return new ActivityBounds(activityRef, x, y, w, h);
}
This example focuses on the code required to run the animation. As you can see, we created a ratio (from 0 to 1) describing the duration of our animation and we simply multiply the width by this ratio. As a result, the activity width will expand in a smooth 250ms animation (see below).
Much more can be done using the same principle as every drawing layer in ScheduleJS uses the renderer architecture and implements similar drawing methods. On top of this, the same result can be achieved using many different approaches. Anyways, no matter what graphics animation you want to build, the ScheduleJS renderers will let you design and customize the user experience at a pixel level.
Feel free to contact us if you have any UX/UI challenges or ideas for ScheduleJS!
Posted on July 10, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.