ZRender is a 2D graphics rendering engine based on Canvas/SVG/VML. It provides the following functions:

  • Graphics drawing and management (CRUD, group)
  • Graphics (including text) animation management
  • Graphical (including text) event management (implementing DOM events in Canvas)
  • Efficient frame rendering mechanism based on “responsive” (DIRTY flag)
  • Optional renderer mechanism (Canvas/SVG/VML(VML support dropped in 5.0))

Tips: Graphics refer specifically to 2D vector drawings

1. Overall architecture

1.1 Overall architecture based on MVC pattern

As shown in the figure above, ZRender is an object-oriented MVC mode with the overall design idea. The view layer is responsible for rendering, the control layer is responsible for user input interaction, and the data layer is responsible for the arrangement and storage of the data model. Its corresponding files and functions are as follows:

  • Storage.ts (data model) : Used to store all graphics data to be drawn, and provide LRU caching mechanism of relevant data, and provide CURD management of data;
  • Ts (View drawing) : PainterBase is the base class for drawing. Canvas, SVG and VML view drawing classes provided by the system all inherit from PainterBase class. Users can also inherit and realize drawing capabilities such as WebGL by themselves.
  • Handler.ts (interactive control) : event interactive control module, for graphic elements to achieve the same event interaction logic as HTMLDOMElement, such as graphic selection, click, touch and other events;

In addition to the MVC3 module, there are the following auxiliary function modules:

1.2 Auxiliary function modules

  • Animation: Manages the animation of graphics. Before drawing, the animation of objects will be calculated into frames and saved in the animation manager. With the trigger conditions of animation, frame data will be pushed to the view drawing module for animation drawing.
  • Tool class module (Tool, core) : provides independent auxiliary calculation functions or classes such as color conversion, path conversion, transformation matrix operation, basic event object encapsulation, vector calculation, basic data structure, etc.
  • Graphic: Provides the object class of elements (including Image, Group, Arc, Rect, etc.). The top layer of all elements is inherited from the Element class.
  • Graphics object support module (contain) : provide used to judge the algorithm of contain relation, such as: coordinate point is on the line, coordinate point is in the graph;

Tips: Above, “elements” contain groups and 2D graphics, while graphics contain only 2D graphics, not groups

2. Source file structure

2.1 Source directory structure

src/
  -|config.ts
  -|Element.ts
  -|Storage.ts
  -|Handler.ts
  -|PainterBase.ts
  -|zrender.ts // Import file
  -|export.ts -|animation/ -|Animation.ts -|Animator.ts -|Clip.ts ... -|canvas/ -|Painter.ts ... -|svg/ -|Painter.ts ... -|vml/ -|Painter.ts ... -|conatin/ -|arc.ts ... -|core/ -|LRU.ts -|matrix.ts ... -|dom/ -|graphic/ -|Group.ts -|Image.ts -|Path.ts -|shape/ -|Arc.ts -|Rect.ts -|Circle.ts ... . -|mixin/ -|Draggable.ts -|tool/ -|color.ts -|ParseSVG.ts -|parseXML.tsCopy the code

2.2 Directories and Files

  • Config. ts: global configuration file, which can be configured with debug mode, RETINA screen HD optimization, dark/light theme color values, etc
  • Element. Ts: the base class for all can draw graphic elements and Group, which defines the basic attributes (such as: id, name, type, isGroup, etc.), members of the basis of object method (hidden, show, the animate, animateTo, copyValue, etc.)
  • Storage.ts: M layer, object model layer/memory layer, stores and manages element object instances. Element instances are stored in the _displayableList array. Each drawing is sorted by zlevel-> Z -> insert order
  • Handler.ts: C layer, control layer/controller, used to bind events to elements and implement DOM-like event management mechanism
  • Ts: V layer, view layer/renderer layer. PainterBase is the base class of renderer. Version 5.0 provides Canvas and SVG renderers by default, and versions before version 5.0 also provide VML renderers
  • Zrender. Ts: zrender entry file, also the main compiler entry,
    • Expose global methods: Init is used to initialize ZRender instances, delInstance is used to delete ZRender instances, Dispose is used to unregister a ZRender instance, disposeAll is used to unregister all ZRender instances, and registerPainter is used to register new renderers
    • ZRender class: used to manage all element object instances, Storage instances, Painter instances, Handler instances, Animation instances in ZRender instances
  • Export. ts: called at compile time and used to export apis
  • Animation: Stores code files related to animation, such as animation, Animator, etc
  • Canvas: Stores code files related to the Canvas renderer
  • SVG: Stores program files related to SVG renderers
  • VML: Store VML renderer related program files
  • Contain: used to supplement special elements of the coordinate containing relation calculation method, such as bezier curve point containing relation calculation
  • Core: a hodgepodge of methods, which I’ve summarized here as a tool method file, containing LRU cache, bounding box calculation, browser environment judgment, transformation matrix, touch event implementation, and a hodgepodge of methods
  • Dom: handlerProxy. ts is a program file used to implement THE DOM event proxy. All events of the elements in the canvas are mediated from the events of the canvas DOM
  • Graphic: The entity object classes for all elements are stored in this folder, including groups, base classes for drawing objects Displayable, paths, arcs, rectangles, etc
  • Mixin: a file only for draggable. ts, which manages element drag events. Since Echarts does not use drag events, drag events are not implemented in ts versions (I’ll share my own version code later).
  • Tool: Tool method, providing color calculation, SVG path conversion and other tool classes

3. Source code analysis of imported files (zrender. Ts)

3.1 Method of ZRender global exposure

Global methods exposed in zrender. Ts (see code comments below). Global methods can be called with zrender. XXX, such as: zrender.

Global methods are used to manage ZRender instances (initialize, delete, find, unregister, etc.)

// To store renderers
const painterCtors: Dictionary<PainterBaseCtor> = {};

// used to store ZRender instances, collectively referred to as zr
let instances: { [key: number]: ZRender } = {};

/** * delete ZRender instance */ by id
function delInstance(id: number) {
  // code omitted
}

/** * Initializes the ZRender instance by passing in a DOM node as the canvas parent */
 export function init(dom: HTMLElement, opts? : ZRenderInitOpt) {
  const zr = new ZRender(zrUtil.guid(), dom, opts);
  instances[zr.id] = zr;
  return zr;
}

/** * unregister the zr instance. After unregister, all graphics in the ZR instance will be deleted
export function dispose(zr: ZRender) {
  zr.dispose();
}

/** * Unregister all zr instances managed in ZRender */
export function disposeAll() {
  // code omitted
}

/** * Get zr instance */ by instance id
export function getInstance(id: number) :ZRender {
  return instances[id];
}

/** * Register the renderer. The system will register the Canvas and SVG renderers by default on startup
export function registerPainter(name: string, Ctor: PainterBaseCtor) {
  painterCtors[name] = Ctor;
}

class ZRender {
  // More on that
}
Copy the code

3.2 ZRender object class

ZRender class is written in the entry file ZRender. Ts. This section analyzes the source code by means of code simplification and annotation, and simplifies the source code for readers to understand

class ZRender {
  // The container root node for canvas rendering must be an HTML element
  dom: HTMLElement
  // zr instance id
  id: number
  // Memory object instance
  storage: Storage
  // Renderer object instance
  painter: PainterBase
  // Controller object instance
  handler: Handler
  // Animation manager object instance
  animation: Animation

  constructor(id: number, dom: HTMLElement, opts? : ZRenderInitOpt) {
    // Initialize the container root node
    this.dom = dom;
    // The global init function generates the guID pass
    this.id = id;
    // New memory instance
    const storage = new Storage();
    The default renderer type is canvas
    let rendererType = opts.renderer || 'canvas';
    // Create renderer
    const painter = new painterCtors[rendererType](dom, storage, opts, id);
    // Assign a storage instance to a member variable
    this.storage = storage;
    // Assign the renderer to the member variable
    this.painter = painter;
    // Create event manager
    this.handler = new Handler(storage, painter, handerProxy, painter.root);
    // Create the animation manager and start the animation manager
    this.animation = new Animation({
      stage: {
        update: () = > this._flush(true)}});this.animation.start();
  }

  /** * Adds elements to the canvas and waits for the next frame to render */
  add(el: Element) {
    // code is omitted. Subsequent method body code is omitted unless otherwise specified
  }

  /** * removes the middle element from memory, the element will not be rendered in the next frame */
  remove(el: Element){}/** * Set the layer order, enable dynamic blur, etc. */
  configLayer(zLevel: number, config: LayerConfig){}/** * sets the canvas background color */
  setBackgroundColor(backgroundColor: string | GradientObject | PatternObject){}/** * gets the canvas background color */
  getBackgroundColor(){}/** * force zr to dark mode */
  setDarkMode(darkMode: boolean){}/** * Query whether the current zr is in dark mode */
  isDarkMode(){}/** * Performs a forcible refresh of the canvas */
  refreshImmediately(fromInside? :boolean){}/** * perform the next frame refresh canvas */
  refresh(){}/** * Perform all refresh operations */
  flush() {
    this._flush(false);
  }

  /** * Sets the number of frames for animation to rest. Animation will stop after the set number of frames */
  setSleepAfterStill(stillFramesCount: number) {
    this._sleepAfterStill = stillFramesCount;
  }

  /** * Wake up the animation and wait for the next render to execute */
  wakeUp(){}/** * Next frame shows the hover state */
  refreshHover(){}/** * Enforces mouse hover */
  refreshHoverImmediately(){}/** * Resize the canvas */
  resize(opts? : { width? :number | stringheight? :number | string
  }){}/** * force stop and empty animation */
  clearAnimation(){}/** * get the canvas width */
  getWidth(): number{}/** * get the canvas height */
  getHeight(): number{}/** * Draw paths as pictures to improve drawing performance */
  pathToImage(e: Path, dpr: number){}/** * Set the mouse style *@param CursorStyle ='default' for example crosshair */
  setCursorStyle(cursorStyle: string){}/** * Find the object instance of the current mouse position element */
  findHover(x: number.y: number) : {target: Displayable
    topTarget: Displayable
  } { }

  /** * mount global events, here is ts on method polymorphic */
  on<Ctx>(eventName: ElementEventName, eventHandler: ElementEventCallback<Ctx, unknown>, context? : Ctx):this
  on<Ctx>(eventName: string.eventHandler: EventCallback<Ctx, unknown>, context? : Ctx):this
  on<Ctx>(eventName: string.eventHandler: EventCallback<Ctx, unknown> | EventCallback<Ctx, unknown, ElementEvent>, context? : Ctx):this{}/** * unloads global events */
  off(eventName? :string, eventHandler? : EventCallback<unknown, unknown> | EventCallback<unknown, unknown, ElementEvent>){}/** * Manually trigger events by event name */
  trigger(eventName: string, event? : unknown){}/** * Empties the canvas and its drawn graphic elements */
  clear(){}/** * Cancel the ZRender object */
  dispose(){}}Copy the code

4. Case analysis of ZRender workflow

4.1 case

The following code draws a rose-red circle with a radius of 30px (#FF6EBE) and animates the circle by moving it left and right.

// 1. Declare the DOM container to draw the ZRender instance
let container = document.getElementsById('example-container') [0];
// 2. Initialize ZRender instance zr, which draws the canvas with the same width and height as the container
let zr = zrender.init(container);

// 3. Get the width and height of zr canvas
let w = zr.getWidth();
let h = zr.getHeight();

// 4. Set the radius of the circle to 30px
let r = 30;

// 5. Create a circular object instance cr
let cr = new zrender.Circle({
  shape: {
    cx: r,
    cy: h / 2.r: r
  },
  style: {
    fill: 'transparent'.stroke: '#FF6EBE'
  },
  silent: true
});

// 6. Animate the cicle binding shape with the true parameter indicating loop execution
cr.animate('shape'.true)
  .when(5000, {
    cx: w - r
  })
  .when(10000, {
    cx: r
  })
  .start();

// 7. Add the circular object instance cricle to the zr instance for rendering
zr.add(cr);
Copy the code

4.2 ZRender drawing process

This section focuses on how ZRender draws and runs the animation process based on the case in 2.1

  1. Create ZRender instances: use const zr = ZRender. Init () to create multiple zr instances, each with its own canvas
  2. Create an instance of the graph to draw. The name of the graph class can be obtained from zrender. XXX, where XXX is the name of the graph class
  3. The zr.add method adds the graphic instance to storage
// zrender.ts
add(el: Element) {
  // Add EL (in this case, cr instance) to storage
  this.storage.addRoot(el);

  // And put the animation into the animation manager
  el.addSelfToZr(this);

  // Start the drawing program
  this.refresh();
}
Copy the code

3B, C, D. Add the animation bound on the graph to the animation manager, generate the animation frame, and start the animation drawing

  1. The frame-by-frame scan is started when zr is instantiated, but the render action is not performed until there are renderable elements captured in memory
// zrender.ts
class Zrender {
  constructor() {
    this.animation = new Animation({
      stage: {
        // Bind the renderer to the frame rendering strategy
        update: () = > this._flush(true)}});// Start animation Manager, start frame render scan rAF program
    this.animation.start();
  }

  // Render next frame
  _flush() {
    this.refreshHoverImmediately();
  }

  // Force render
  refreshHoverImmediately() {

    // Call the renderer
    this.painter.refresh(); }}Copy the code
  1. The previous step this.panter.refresh() will ask storage to fetch the render list
// canvas/Painter.ts
class Painter {
  refresh() {
    // Get the render list
    const list = this.storage.getDisplayList(true); }}// Storage.ts
class Storage {
  /** * Update the drawing queue of the graph. * This method is called before each draw, which iterates through the tree depth-first, updates all Group and Shape transformations and saves all visible Shapes into an array, and finally gets the draw queue sorted by the priority of the draw (zlevel > z > insert order)
  getDisplayList() {
    // Return to render list
    return this._displayList
  }
}
Copy the code
  1. 6, 7 Start and execute the rendering program to draw the path of the graph by doPaintList->doPaintEl

END this chapter.

ZRender source code analysis to continue:

  • Element object source code parsing
  • Event manager source parsing
  • Animation manager source code parsing
  • Renderer source code parsing

About the author:

Lei Ting is a front-end architect at Nobeltech in Beijing. He has been engaged in front-end development and architecture for 17 years. He is good at front-end development in the field of visualization and front-end communication