calendar-blue

Drawing animations in ScheduleJS

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

Tuesday, May 2nd, 2023 - 4 minutes read

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 Glissez et déposez 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 nous contacter if you have any UX/UI challenges or ideas for ScheduleJS!

More implementation-related articles

This article showcases the integration of a ScheduleJS component into an external Ag-Grid table, to demonstrate the flexibility of ScheduleJS.

How to synchronize multiple graphics? Learn how to reuse your ScheduleJS timeline in multiple graphics to keep them synchronized.

Contextual menu

How to build an interactive context menu? A deep dive into ScheduleJS event handling and recommended practice to build your own context menu.

This article shows how to implement dynamic rendering depending on the current zoom-level of the graphics.

This article will cover a websocket implementation featuring real time data rendering using the ScheduleJS drawing engine.

This article will go through the implementation of a custom info column using an AG Grid component in place of the default one.

This article will show you how the parent-child tree Gantt architecture within the ScheduleJS Viewer was built.

This article will showcase how the main activity renderer of the ScheduleJS Viewer was built with code examples.

ScheduleJS allows you to build complex drag-and-drop interactions, including side effects. This article shows how to implement drag-and-drop.

S’abonner
Notification pour
guest
0 Commentaires
Commentaires
Show all comments
0
We would love to to have your toughts on this. Please leave a comment below!x