Transform and Matrix4
Transform controls where a layer is placed, how it rotates and scales, and which local point acts as its pivot. Every layer has a required transform object, and the same model works for flat 2D motion and 3D motion.
The transform model
{
"frame": { "width": 480, "height": 270 },
"transform": {
"translate": { "x": 320, "y": 180, "z": 0 },
"rotate": { "x": 0, "y": 0, "z": 8 },
"scale": { "x": 1, "y": 1, "z": 1 },
"anchor": { "x": 240, "y": 135, "z": 0 },
"skew": { "x": 0, "y": 0 }
}
}
| Field | Meaning | Typical default |
|---|---|---|
translate | Position in the parent/world coordinate system | { x: 0, y: 0, z: 0 } |
rotate | Rotation around X, Y and Z axes, in degrees | { x: 0, y: 0, z: 0 } |
scale | Scale along X, Y and Z axes | { x: 1, y: 1, z: 1 } |
anchor | Local pivot point used by rotation and scale | { x: 0, y: 0, z: 0 } |
skew | 2D shear angles for X and Y | { x: 0, y: 0 } |
All values must be finite numbers. The transform does not change the layer frame itself; it changes how that frame is placed and projected.
Frame versus transform
The frame is local geometry. The transform is placement:
frame: 480 × 270 local rectangle
anchor: (240, 135) center of that rectangle
translate: (320, 180) position in the parent/world space
rotate.z: 8 degrees around the anchor
scale: (1, 1, 1) unchanged size
Changing frame.width or frame.height changes the layer's local geometry. Changing translate, rotate, scale or anchor keeps the geometry but changes its placement or appearance.
Translate
translate moves the layer in its parent coordinate system. X and Y use composition/world units. Z is used when the composition view or a parent group uses depth.
"translate": { "x": 320, "y": 180, "z": 0 }
For a child layer, the translation is evaluated relative to its parent group. For a root layer, it is evaluated relative to the composition world.
Scale
scale multiplies the local geometry around the anchor:
1leaves an axis unchanged.- A value between
0and1shrinks that axis. - A value greater than
1enlarges that axis. - A negative value mirrors the layer across that axis.
0collapses an axis and should be used deliberately.
"scale": { "x": 1.2, "y": 0.8, "z": 1 }
Rotation
Rotation values are degrees. Z rotation is the usual flat 2D rotation; X and Y rotation are useful with perspective projection or 3D layers.
"rotate": { "x": 0, "y": 0, "z": -12 }
Rotation happens around the local anchor. If the anchor is at the top-left, the layer swings around that corner. If the anchor is at the center, it rotates around its center.
Anchor and pivot
anchor.x and anchor.y are local units, not normalized 0–1 percentages. For a 480×270 frame, the center anchor is { x: 240, y: 135, z: 0 }. The pivot may be outside the frame for orbit-style motion.
{
"frame": { "width": 480, "height": 270 },
"transform": {
"translate": { "x": 540, "y": 405, "z": 0 },
"rotate": { "x": 0, "y": 0, "z": 20 },
"scale": { "x": 1, "y": 1, "z": 1 },
"anchor": { "x": 240, "y": 135, "z": 0 },
"skew": { "x": 0, "y": 0 }
}
}
To place the frame's center at a world position, use the frame center as the anchor and set translate to that world position. To rotate around a corner, use { x: 0, y: 0, z: 0 }.
Skew
skew.x and skew.y are 2D shear angles in degrees. They are useful for slanted cards, italic-style geometric motion and simple projection accents. Use an ordered operations list when the exact SVG transform sequence matters.
From CSS to Neonix JSON V2
HTML/CSS authors normally start with a CSS transform. The compiler converts the same intent into explicit JSON fields:
.card {
width: 480px;
height: 270px;
transform: translate(320px, 180px) rotate(8deg) scale(1.1, 0.9);
transform-origin: 50% 50%;
}
{
"frame": { "width": 480, "height": 270 },
"transform": {
"translate": { "x": 320, "y": 180, "z": 0 },
"rotate": { "x": 0, "y": 0, "z": 8 },
"scale": { "x": 1.1, "y": 0.9, "z": 1 },
"anchor": { "x": 240, "y": 135, "z": 0 },
"skew": { "x": 0, "y": 0 }
}
}
transform-origin becomes a local-unit anchor; percentages are resolved against the layer frame. translateX/Y/Z, scaleX/Y/Z, rotateX/Y/Z, skewX/Y and matrix/matrix3d become named fields or ordered operations.
Ordered operations
The component fields above provide the simple layer transform contract. An optional operations array preserves an explicit ordered transform list; its order is semantic and must not be rearranged.
"operations": [
{ "type": "translate", "x": 320, "y": 180 },
{ "type": "rotate", "angle": 8, "center": { "x": 240, "y": 135 } },
{ "type": "scale", "x": 1.1, "y": 1.1 },
{ "type": "skewX", "angle": 4 }
]
Supported 2D operations are translate, scale, rotate, skewX, skewY and six-value SVG matrix. Motion extensions add motion.translate3d, motion.scale3d, motion.rotateX, motion.rotateY, motion.rotateZ, motion.perspective and motion.matrix4.
When operations is present, it is the authored operation sequence. The transform anchor wraps that sequence as a pivot, and the layer translation places the result.
Matrix4
Neonix uses a canonical 4×4 matrix for the final 2D/3D transform. Most authors should use the named fields or ordered operations. Use motion.matrix4 only when importing an already-composed matrix or when a tool needs direct matrix control.
{
"type": "extension",
"name": "motion.matrix4",
"value": [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
320, 180, 0, 1
]
}
Do not mix a pre-composed matrix with equivalent translate, rotate and scale values unless the duplication is intentional. Otherwise the same motion can be applied twice.
Which transform form should I use?
| Form | Use it when | Important rule |
|---|---|---|
| Named fields | You are authoring ordinary 2D or 3D motion | Easiest to read and animate with tracks |
operations | You need to preserve the exact CSS/SVG transform order | Array order is semantic |
motion.matrix4 | You import a composed 4×4 matrix from another tool | Do not duplicate equivalent named transforms |
For animation, target one scalar or vector property such as transform.translate.x, transform.rotate.z, transform.scale.x or transform.anchor.y. Do not animate the whole matrix unless the source tool already owns matrix composition.
Composition order
With the named fields, the conceptual local transform is:
translate → pivot to anchor → rotate → skew → scale → pivot back
The exact matrix is evaluated consistently for preview and export. With operations, the listed operations replace the implicit ordered list; their order is not interchangeable. For example, scale-then-translate does not generally produce the same result as translate-then-scale.
Parent and child layers
A group transform affects its descendants. A child transform is evaluated in the coordinate system established by its parent, then the child is painted according to sibling order. To understand a misplaced layer, inspect the chain from the root composition through each parent group before changing the child values.
See Layers and hierarchy for parent relationships and Composition and view for the global view projection.
Animate a transform
Animation tracks target individual transform properties with dot-separated paths:
{
"path": "transform.translate.x",
"keyframes": [
{ "time": 0, "value": { "type": "number", "value": 120 } },
{ "time": 1000, "value": { "type": "number", "value": 520 } }
]
}
Other supported paths include transform.translate.y, transform.translate.z, transform.scale.x, transform.rotate.z, transform.skew.x and transform.anchor.x. Track times are milliseconds and each keyframe value must keep the property's type.
Common mistakes
- Using normalized anchor values such as
0.5instead of local units such asframe.width / 2. - Changing the layer frame when the intent is only to move or rotate the layer.
- Reordering
operationsand expecting the same result. - Applying both a pre-composed
motion.matrix4and equivalent named transform values. - Using X/Y rotation without a perspective view and expecting visible depth scaling.
- Forgetting that a parent group transform affects every descendant.
For the global camera, read Composition and view. For time-based changes, read Animation and timing.