Layout
Layout in Famo.us is managed by Transforms and Modifiers. Transforms
encapsulate the CSS3 transform specification
which define the position, orientation and distortion of a node. While aTransform is a static object, representing a snapshot in time, a Modifier
encapsulates varying Transforms over time. Modifiers also bundle layout
properties like size, origin and align that allow for alignment and justification.Modifiers can be directly added to the Famo.us Scene Graph, acting on the nodes beneath them.
Overview
Transforms
Transforms correspond directly to a CSS3 transformation matrix.
A Transform combines translation, rotation, scale and skew components
into a 16-element array.
| Component | Description | Default Value |
|---|---|---|
| translation | [x,y,z] |
[0,0,0] |
| rotation | [x,y,z] |
[0,0,0] |
| scale | [x,y,z] |
[1,1,1] |
| skew | [x,y,z] |
[0,0,0] |
The Transform class provides methods to break down a Transform into these
components, and also to build up Transforms from them.
Transform Primitives
All Transforms are built up from these Transforms primitives
| Transform | Return Value | Components | Description |
|---|---|---|---|
Transform.identity |
[1, 0, 0, 0, |
translation : [0,0,0] |
The identity transformation. This has no affect on layout. |
Transform.translate(x,y,z) |
[1, 0, 0, 0, |
translation : [x,y,z] |
Translates along the x-, y- and z-axes. |
Transform.scale(x,y,z) |
[x, 0, 0, 0, |
translation : [0,0,0] |
Scales in the x-, y-, and z-axes relative to an origin. |
Transform.rotateX(θ) |
[1, 0, 0, 0, |
translation : [0,0,0] |
Rotates clock-wise in the y/z-plane (along the x-axis). |
Transform.rotateY(θ) |
[cos(θ), 0, sin(θ), 0, |
translation : [0,0,0] |
Rotates clock-wise in the x/z-plane (along the y-axis). |
Transform.rotateZ(θ) |
[cos(θ),-sin(θ), 0, 0, |
translation : [0,0,0] |
Rotates clock-wise in the x/y-plane (along the z-axis). |
Transform.skewX(θ) |
[1, 0, 0, 0, |
translation : [0,0,0] |
Skews along the x-axis. θ gives the angle between the top and left sides. |
Transform.skewY(θ) |
[1, tan(θ), 0, 0, |
translation : [0,0,0] |
Skews along the y-axis. θ gives the angle between the top and left sides. |
Transform.inFront |
[1, 0, 0, 0, |
translation : [0,0, 0.001] |
A small z-translation forwards toward the viewer. Useful for layering. |
Transform.behind |
[1, 0, 0, 0, |
translation : [0,0,-0.001] |
A small z-translation backwards away from the viewer. Useful for layering. |
Building Transforms
Complex Transforms can be built up from the above primitives in two ways
- Nesting
ModifierswithTransformsin the Scene Graph - Composing
Transforms
For this guide, we will focus on the latter. See the Scene Graph guide for the former.Transform composition is done through the .multiply method. Though .multiply
is useful for composing arbitrary transforms, it is heavyweight for simple compositions,
such as composing a translation on a rotation. Famo.us provides optimized methods for these
use cases.
| Method | Description |
|---|---|
Transform.multiply(T1,T2) |
Equivalent to applying a transform T1 on top of a transform T2. Note: Order matters! |
Transform.thenMove(T,p) |
Translates a transform T by p = [x,y,z]. Equivalent to Transform.multiply(Transform.translate(x,y,z), T). |
Transform.moveThen(p,T) |
Translates by p = [x,y,z] prior to applying the Transform T. Equivalent to Transform.multiply(T, Transform.translate(x,y,z)). |
Transform.thenScale(T,s) |
Scales a transform T by s = [x,y,z]. Equivalent to Transform.multiply(Transform.scale(x,y,z), T). |
Transform.aboutOrigin(T,p) |
Shifts the origin of a Transform to a new point p = [x,y,z] in pixels from the top/left. For instance, Transform.aboutOrigin(Transform.rotateZ(Math.PI/4), [50,100,0]) would rotate a renderable around a pivot at [50,100,0] instead of the top-left. |
Transform.average(T1,T2,w) |
Returns a weighted average between the two transforms with weight w by averaging their rotate, translate, scale and skew components. |
Breaking Down Transforms
Complex Transforms can be broken up into their individual components via the methods
| Method | Description |
|---|---|
Transform.getTransform(T) |
Returns the translate components of the Transform T. |
Transform.interpret(T) |
Returns an object with translate, rotate, scale, and skew keys. Note: This is a relatively expensive operation and its use is discouraged. |
Modifiers
Transforms by themselves can't be added to the Scene Graph. In order to apply
a Transform to a renderable, a Modifier is necessary. A Modifier is a node,
and can be directly added to the Scene Graph. A Modifier is best thought of as
a shell that accepts primitives, like Transforms that can modify the nodes (which
can be other Modifiers) below them either by applying a Transform, opacity
or alignment.
Below we demonstrate applying a translational Transform to a Surface 100px
in the x- and y- directions.
var surface = new Surface({
size: [50, 50],
properties: { background: 'red' }
});
var modifier = new Modifier({
transform : Transform.translate(100, 100, 0)
});
context.add(modifier).add(surface);
Any Transform can be applied this way. As mentioned above, Modifiers have
convenience properties to affect alignment as well. Before we introduce this
concept, we will need to understand Famo.us sizing primitives: size, origin
and align.
Alignment & Sizing
In addition to positioning, Modifiers can also align renderables relative to a
size context. This allows users to "center" objects, or "left-justify" them, etc.
To understand how this is accomplished in Famo.us, we introduce the concepts ofsize, align and origin in Modifier.
Size
Size defines a bounding-box for content. Nodes in the Scene Graph below sizedModifiers can use this bounding size to define their container's size. The
simplest example being a Surface with size = [undefined, undefined] that
takes the size of a parenting Modifier.
In the following example, the created surface will have a size of [200, 100],
even though its width was original set to undefined.
var sizeModifier = new Modifier({size: [200, 200]});
var surface = new Surface({
size: [undefined, 100],
properties: { background : 'red' }
});
context.add(sizeModifier).add(surface);
This concept extends beyond Surfaces to any Famo.us renderable, such as a View.
It is especially important in Views that handle layout, such as GridLayout orSequentialLayout. These layouts have no intrinsic notion of a boundind-box, and
need to have their size defined for them in a parenting Modifier.
Align
Layout is often easily described in terms of "top left", "bottom right", etc.Align is a way of defining an alignment relative to a bounding-box given by asize. Align is given by an array [x, y] of proportions between 0 and 1.
The default value for the align is top left, or [0, 0]. The following table
summarizes common alignment values.
| Align Values | Meaning |
|---|---|
[0, 0] |
Top Left |
[0.5, 0] |
Top Center |
[1, 0] |
Top Right |
[0.5, 0.5] |
Center Center |
[1, 0.5] |
Center Right |
[0.5, 1] |
Bottom Center |
[1, 1] |
Bottom Right |
For example, below is a way of aligning a Surface's top/left corner to the
left center of within a bounding-box of size [100, 100].
////////////////////////////////////
//
// 100 x 100 bounding box
// ↙
// ┌──────────────┐
// │ │
// │ │
// ├──────┐ │
// │ │ │
// ├──────┘ │
// └──────────────┘
//
////////////////////////////////////
var surface = new Surface({
size: [50, 30],
properties: { background: 'red' }
});
var sizeModifier = new Modifier({
size: [100, 100]
});
var alignModifier = new Modifier({
align: [0, 0.5]
});
context.add(sizeModifier).add(alignModifier).add(surface);
Origin
In the above example, we aligned the top left corner of the Surface. What if
we want to align a different location of the Surface, such as its center? This
is done by setting the origin property of a Modifier.
While alignment is relative to parenting size, origin is relative to the renderable.
The combination of the two places the renderable's origin at the location of the
parent's alignment. This is best described visually:
align point (+) origin point (○)
┌─────────────────┐ ┌───────┐
│ │ │ │
│ │ │ ○ │
│ + │ └───────┘
│ │ (renderable)
│ │
└─────────────────┘
(bounding box)
align & origin (⊕)
┌─────────────────┐
│ ┌───────┐ │
│ │ │ │
│ │ ⊕ │ │
│ └───────┘ │
│ │
└─────────────────┘
(renderable inside bounding box)
For example, if we want to have a Surface centered within some sized context,
we would need to place the Surface's center, on the sized context's center. In
code,
////////////////////////////////
//
// 100 x 100 bounding box
// ↙
// ┌────────────┐
// │ ┌──────┐ │
// │ │ │ │
// │ │ │ │
// │ └──────┘ │
// └────────────┘
//
////////////////////////////////
var surface = new Surface({
size: [70, 70],
properties: { background: 'red' }
});
var sizeModifier = new Modifier({
size: [100, 100]
});
var centerModifier = new Modifier({
align: [0.5, 0.5],
origin: [0.5, 0.5]
});
context
.add(sizeModifier)
.add(centerModifier)
.add(surface);
The default value for the origin is top left, or [0, 0]. Note: ThesizeModifier must come before the alignModifier as alignment must reference
a parent size. These modifiers can not be combined into one.
Dynamic Layout
Thus far, we have only considered setting the transform, size, origin andalign parameters as a static property of a Modifier. However, each of these
properties can animate over time. This can be done in one of two methods
Push-based animations
The StateModifier class found in Famous/modifiers/StateModifier.js is a push-based
implementation. Here, StateModifier has the methods
| method | description |
|---|---|
StateModifier.setTransform(T, definition, callback) |
Sets the transform state. |
StateModifier.setOpacity(opacity, definition, callack) |
Sets the opacity state. |
StateModifier.setSize(size, definition, callack) |
Sets the size state. |
StateModifier.setOrigin(origin, definition, callack) |
Sets the origin state. |
StateModifier.setAlign(align, definition, callack) |
Sets the align state. |
A push-based example of animating a renderable is
var surface = new Surface({
size: [70, 70],
properties: { background: 'red' }
});
var stateModifier = new StateModifier({
opacity: 1
});
context.add(stateModifier).add(surface);
// animate the opacity from 1 to 0 over 500ms using a linear easing curve
stateModifier.setOpacity(
0,
{curve: 'linear', duration : 500},
function() { console.log('animation finished!') }
);
Pull-based animations
The Modifier class found in Famous/core/Modifier.js is a pull-based
implementation. The Modifier itself doesn't keep any state (like what the
current Transform is); instead that state is provided externally by a getter,
which is either a function that returns the state, or an object with a .get
method that returns the state. Modifier has the methods
| method | description |
|---|---|
Modifier.transformFrom(transformGetter) |
The transform state is pulled from the transformGetter. |
Modifier.opacityFrom(opacityGetter) |
The opacity state is pulled from the opacityGetter. |
Modifier.sizeFrom(sizeGetter) |
The size state is pulled from the sizeGetter. |
Modifier.originFrom(originGetter) |
The origin state is pulled from the originGetter. |
Modifier.alignFrom(alignGetter) |
The align state is pulled from the alignGetter. |
The same example above can be recreated in a pull-based way as
var surface = new Surface({
size: [70, 70],
properties: { background: 'red' }
});
var modifier = new Modifier();
context.add(modifier).add(surface);
// the opacityGetter
var opacityState = new Transitionable(1);
modifier.opacityFrom(opacityState);
// animate the opacity from 1 to 0 over 500ms using a linear easing curve
opacityState.set(
0,
{curve : 'linear', duration : 500},
function(){ console.log('animation finished!'); }
);