Deep Dive into LeaferJS: Architecture, Rendering Performance, Update Mechanism, and Event Picking
This article analyses the LeaferJS canvas rendering engine, covering its lightweight node creation, core architecture, rendering pipeline, partial‑render optimization, and event‑picking mechanism, while providing code examples and performance insights for front‑end developers.
1. Introduction
A new Chinese canvas rendering engine claims to render one million rectangles in 1.5 seconds; the author investigates its architecture and performance.
2. Architecture Design
Flame‑graph analysis shows that leaferjs creates nodes with minimal work, mainly calling setAttr . Most time is spent on node creation and layout, while actual rendering takes only about 3 ms.
The library consists of two repositories: leafer (core rendering) and ui (high‑level components such as Image, Line, etc.). A typical usage example is:
import { App, Leafer, Rect } from 'leafer-ui'
const app = new App({ view: window, type: 'user' })
const backgroundLayer = new Leafer()
const contentLayer = new Leafer({ type: 'design' })
const wireframeLayer = new Leafer()
app.add(backgroundLayer)
app.add(contentLayer)
app.add(wireframeLayer)
const background = new Rect({ width: 800, height: 600, fill: 'gray' })
const rect = new Rect({ x: 100, y: 100, fill: '#32cd79', draggable: true })
const border = new Rect({ x: 200, y: 200, stroke: 'blue', draggable: true })
backgroundLayer.add(background)
contentLayer.add(rect)
wireframeLayer.add(border)Each Leafer instance creates its own canvas, similar to Konva layers, enabling canvas‑layer optimization.
2.1 Leafer
The Leafer class is decorated with @registerUI() , uses data processors, and defines properties such as pixelRatio . The decorator registers the class in UICreator.list , allowing creation via Leafer.one(data) . Property access is intercepted by boundsType , which uses Object.defineProperty to call __setAttr .
Key internal modules:
interaction : handles DOM events and redispatches them to nodes.
canvasManager : manages canvas creation, destruction, and reuse.
imageManager : downloads and caches image resources.
During initialization, init creates a canvas based on the provided view configuration, supporting canvas‑layer management.
2.2 Leaf
The Rect class demonstrates how shapes are defined. It uses the @useModule(RectRender) decorator to mix rendering methods, extends UI , and implements __drawPathByData to draw either a rounded rectangle or a plain rectangle.
@useModule(RectRender)
@registerUI()
export class Rect extends UI implements IRect {
public get __tag() { return 'Rect' }
@dataProcessor(RectData)
public __: IRectData
constructor(data?: IRectInputData) { super(data) }
public __drawPathByData(drawer: IPathDrawer, _data: IPathCommandData): void {
const { width, height, cornerRadius } = this.__
if (cornerRadius) {
drawer.roundRect(0, 0, width, height, cornerRadius)
} else {
drawer.rect(0, 0, width, height)
}
}
}The class relies on several decorators and mixins:
useModule mixes RectRender methods.
@opcityType , @positionType intercept set operations via LeafDataProxy.__setAttr .
Rendering ultimately calls __render from the Branch mixin, which traverses child nodes.
3. Update Mechanism
When a property changes, __setAttr emits a CHANGE event. The Watcher captures this event, adds the node to an update queue, and dispatches a RenderEvent.REQUEST . Rendering is then scheduled via requestAnimateFrame to batch updates.
Two rendering paths exist:
Full render ( fullRender ) – renders the entire scene tree, used on first paint or when usePartRender is false.
Partial render ( partRender ) – merges pre‑ and post‑update bounding boxes into a Block , clips to that region, and redraws only intersecting nodes.
Example of partial rendering for a rectangle moved 100 px to the right:
const rect = new Rect({ x: 0, y: 0, width: 100, height: 100 })
rect.x = 100The update block becomes { x: 0, y: 0, width: 200, height: … } , and clipRender clears and redraws only the affected area.
4. Event Picking
Canvas elements cannot natively report which shape was clicked, so LeaferJS implements a hit‑testing system. The interaction module listens to DOM events on the root Leafer node and forwards them to leaf nodes via a selector.
For a mouse click, selector.getByPoint invokes FindPath.eachFind , which recursively traverses branches and leaves. Branches are first filtered with hitRadiusPoint ; leaves are tested with __hitWorld , which draws the node onto an off‑screen hitCanvas and uses isPointInPath or isPointInStroke to determine a hit.
This approach avoids custom geometric collision code but still requires a second canvas draw, similar to Konva’s color‑key method, though LeaferJS delays the extra draw until an event occurs, improving initial render performance.
5. Summary
LeaferJS is a Chinese‑authored canvas rendering library that achieves high performance through lightweight node creation, efficient partial rendering, and a pragmatic event‑picking strategy. While still early in its lifecycle, its architecture suggests strong potential to become a competitive alternative to existing canvas frameworks.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.