Skip to content

Lesson 33 - Layout engine

Browsers implement several layout systems, such as Flexbox, Grid, and Block, making it easy to achieve effects like “centering” without manually calculating node positions.

For infinite canvas-like applications operating outside the DOM, you must implement your own layout engine logic. Figma has implemented Auto Layout, where Grid is currently in beta, while Vertical and Horizontal correspond to CSS's flex-direction property. For details, see: Figma - Guide to auto layout

source: https://www.figma.com/community/file/1284819663700490015
source: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Flexible_box_layout/Basic_concepts

In this lesson, we will implement Flexbox layouts and support CSS properties with the same names on nodes:

ts
const parent = {
    id: 'parent',
    type: 'rect',
    x: 100,
    y: 10,
    width: 100,
    height: 100,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
};
const child = {
    id: 'child',
    parentId: 'parent',
    type: 'rect',
    width: '50%',
    height: '50%',
};

Yoga

Using the Yoga layout engine in the frontend is only possible via WASM. Currently, there are several available implementations:

It is worth noting that Yoga also applies to 3D space, provided that a plane is specified. For details, see:react-three-flex

Another important difference with DOM Flexbox is that you have to specify the plane of the container in 3D. The elements will be positioned in the 2D plane given by the two axes, using width and height calculated along the two axes.

axes_orientation

pixijs/layout

pixijs/layout is also implemented using Yoga. Similar implementations include: pixi-flex-layout

ts
const container = new Container({layout: {
    width: '80%',
    height: '80%',
    gap: 4,
    flexWrap: 'wrap',
    justifyContent: 'center',
    alignContent: 'center',
}});

troika-flex-layout

troika-flex-layout, computed in a WebWorker using yoga-layout-prebuilt:

ts
import { requestFlexLayout } from 'troika-flex-layout'

// Describe your layout style tree, with a unique id for each node:
const styleTree = {
  id: 'root',
  width: 100,
  height: 100,
  alignItems: 'center',
  justifyContent: 'center',
  children: [
    {
      id: 'child',
      width: '50%',
      height: '50%'
    }
  ]
}

// Initiate a layout request with a callback function:
requestFlexLayout(styleTree, results => {
  // The results are a mapping of node ids to layout boxes:
  // {
  //   root: { left: 0, top: 0, width: 100, height: 100 },
  //   child: { left: 25, top: 25, width: 50, height: 50 }
  // }
})

Alternatives to Yoga

Pure JS implementations:

Rust implementations:

  • stretch implements Flexbox and provides a stretch-layout WASM binding, but it has not been maintained for a long time.
  • taffy A high-performance UI layout library written in Rust, currently implementing several CSS layout algorithms including Flexbox, Grid, and Block. However, WASM bindings are not yet available. For details, see: taffy wasm bindings

[WIP] Our implementation

A layout tree parallel to the scene graph needs to be constructed.

Calculating layout in WebWorker

Extended reading

Released under the MIT License.