Flexbox

Published: September 03, 2019
Edited: September 03, 2019

Sometimes absolute positioning is not enough. You may want to position elements stacked horizontally or vertically, or wrap items when there are too much to fit in an area. This process is called layouting and Lightning has a simple solution for it.

Lightning does not contain the bloated HTML CSS toolbox. Instead, it only contains the useful Flexbox layout methodology. The Lightning layout engine has some smart performance optimizations. However it is still rather cpu-intensive so is best used for situations in which the flex items do not often change dimensions.

Flexbox allows for dynamically stacked items of rows and columns. In Lightning, the flex and flexItem properties define the flex-related layout settings. Notice that it's also possible to nest flex containers, making them both flex containers and flex items.

    MyFlexBox:{ x: 50, y: 50, w: 250, flex:{ direction: 'row', padding: 20, wrap: true }, rect: true,
        MyFlexItem: { w: 50, h: 100, flexItem:{ margin: 10 }, rect: true, color: 0xFF797979 },
        MyFlexBoxItemWithFlexItemChildren:{flex: {direction: 'column', padding: 20 }, flexItem: { margin: 10 }, rect: true,
            children: [
                { text: {text: "line 1"} },
                { text: {text: "line 2"} }
            ]
        }
    }

Instead of repeating everything there is to know about Flexbox, we advice you to have a look at this Flexbox Guide.

Differences with CSS Flexbox

Lightning's version of flexbox is almost completely identical to the one you'll find in Chrome and Firefox, though we made some intentional exceptions for improving consistency and usability. The tables below describe the major differences between the two.

Flex container

Specified in the element property: flex: {}.

LightningtypeCSSDifference (if any)
directionstringflex-direction
wrapbooleanflex-wraponly wrap (true) and nowrap (false) are supported
alignItemsstringalign-itemsbaseline not supported
alignContentstringalign-items
justifyContentstringjustify-content
paddingnumber (px)padding
paddingLeftnumberpadding-left
paddingTopnumberpadding-top
paddingRightnumberpadding-right
paddingBottomnumberpadding-bottom

Flex item

Specified in the element property: flexItem: {}.

LightningtypeCSSDifference (if any)
grownumberflex-grow
shrinknumberflex-shrinkNon-containers are not shrinkable by default
alignSelfstringalign-self
orderNot supported
flex-basisNot supported (behaves as set to 'auto')
minWidthnumber (px)min-width
minHeightnumber (px)min-height
maxWidthnumber (px)max-width
maxHeightnumber (px)max-height
marginnumber (px)margin
marginLeftnumbermargin-left
marginTopnumbermargin-top
marginRightnumbermargin-right
marginBottomnumbermargin-bottom

Disable flex item

All children of a flex container are by default flex items. However, it is possible to make one of the children behave as an absolutely positioned element by specifying flexItem: false. In this case, the item will not affect the flex layout and will be positioned absolutely.

Invisible elements

When an element is invisible (visible: false) it will be ignored in the flex layout. In contrast, when an element is fully transparent (alpha: 0) it will take space in the layout as normal.

Auto sizing

A flex container can have a fixed w and/or h specified. This may affect the positioning of the items within it. It is also possible to not specify it (or set it to 0). In that case the flex container will always fit to the contents on those axes. This is deliberately inconsistent to HTML CSS, where it depends on the axis (horizontal axis will usually fallback to the parent width), which feels as odd behavior.

Offsets

When enabling flexbox, the x and y properties act as relative positions to the positions calculated by the layout engine.

Final coordinates

After the layout has been done, you can find the element's coordinates and size by using the finalX, finalY, finalW, finalH element properties.

To ensure a full stage layout (without rendering), you can use this.stage.update(). After that the finalX etc. properties will contain the correct value.

Live demo

Below an visualization of how flexbox works when you change the width of the flex-wrapper.

class FlexExample extends lng.Application {
    static _template() {
        return {
            Wrapper:{ x: 50, y: 50, w: 250,  flex:{ direction: 'row', padding: 20, wrap: true }, rect: true, color: 0xFF2D2D2D, paddingLeft: 200,
                Item1: { w: 50, h: 100, flexItem:{ margin: 10 }, rect: true, color: 0xFF797979 },
                Item2: { w: 50, h: 100, flexItem:{ margin: 10 }, rect: true, color: 0xFFA7A7A7 },
                Item3: { w: 50, h: 100, flexItem:{ margin: 10, alignSelf: 'stretch', grow: 1, maxWidth: 190, maxHeight: 100 }, rect: true, color: 0xFFD3D3D3 },
                Item4: { w: 90, h: 50, flexItem:{ margin: 10, alignSelf: 'stretch', grow: 1, maxWidth: 230, maxHeight: 100 }, rect: true, color: 0xFF74B4A7 },
               
                Sub: {flex: {direction: 'column', padding: 20}, flexItem: { margin: 10, alignSelf: 'stretch', grow: 1, maxWidth: 380 }, rect: true, color: 0xFF486f67,
                    children: [
                        { text: {text: "line 1"} },
                        { text: {text: "line 2"} },
                        { text: {text: "line 3"} },
                        { text: {text: "line 4"} }
                    ]
                }
            }
        }
     }
     _init(){
         this._myFlexAnimation = this.tag('Wrapper').animation({
             duration: 4, repeat: -1, stopMethod: 'immediate',
             actions: [{ p: 'w', v: { 0: 250, 0.5: 430, 1: 250 } }]
         });
         this._myFlexAnimation.start();
     }
}


const options = {stage: {w: window.innerWidth, h: window.innerHeight, useImageWorker: false}};
const app = new FlexExample(options);
document.body.appendChild(app.stage.getCanvas());