A React component to help handle complex nested 2d and 3d css transformations
npm install react-css-transform
!dependencies
!travis build

A React helper library to handle nested 2D and 3D CSS transforms using matrix multiplication,
drastically reducing the number of nested DOM elements required and making complex
combinations of nested transformations trivial.
was initially developed while I was working at Pest Pulse
for a zoomable/pannable map with markers.
react-css-transform uses the gl-matrix library
under the hood. It's a peer dependency so install like so:
npm install gl-matrix react-css-transform --save
While it might be a little niche, there have been several projects over the last few years
where I've thought about writing this and ended up going with a "faster", hackier
solution using nested divs and manual inline calculations. Often the nested divs caused
me problems with mouse and UI events. I thought I'd do it properly this time and thanks to
Pest Pulse for letting me open source it.
If you're just doing basic transforms on a single element, you probably don't need this
library. It is super useful for doing nested transformations and it can be useful on a
single element if you want to ensure consistent application of the transform, scale and
rotate transformations. If you hadn't noticed the order is important!
In general it is best to make sure that the HTML elements you are transforming have styles
set like so:
``css`
position: absolute;
left: 0;
top: 0;
If you are using 3D Transforms you also most likely want to set the containing HTML Element
to have preserve-3d and some perspective:
`css`
transform-style: preserve-3d;
perspective: 500px;
The styles provided to the
below will be merged with the computed CSS matrix transform`javascript
// ... as much nesting as you like
`#### Props
##### - multiplicationOrder
Optional. An enum: either
MULTIPLICATION_ORDER.PRE or MULTIPLICATION_ORDER.POST.
This determines the order an object's local matrix is multiplied with it's parent's matrix world.
The default is MULTIPLICATION_ORDER.POST. You can only set this at the most outer Transform2d
component. PRE will mimic how the transforms would be applied if you were doing them as
actual nested DOM elements. POST is much more natural mathematically and way more useful.
You should use POST :)##### - translate
Optional. An object describing translation. Either a plain JS object or a gl-matrix
vec2. If you pass in a JS object without all dimensions, missing dimensions will be
given 0 as the default value. If nothing is supplied no translation occurs.##### - scale
Optional. The transform's scale. either a number, a plain JS object or a gl-matrix
vec2. A number will apply the same scale to x and y. If you pass in a JS object
without all dimensions, missing dimensions will be given 1 as the default value. If
nothing is supplied no scaling occurs. ##### - rotate
Optional. The transform's rotation. A number provided in radians. If nothing is
supplied no rotation occurs.
$3
The styles provided to the
below will be merged with the CSS matrix3d transform`javascript
const translateToCentre = {
x: window.innerWidth / 2,
y: window.innerHeight / 2
};const theta = Math.PI / 2;
const yAxis = { x: 0, y: 1, z: 0 };
const zAxis = { x: 0, y: 0, z: 1 };
// ... as much nesting as you like
`#### Props
##### - multiplicationOrder
Optional. An enum: either
MULTIPLICATION_ORDER.PRE or MULTIPLICATION_ORDER.POST.
This determines the order an object's local matrix is multiplied with it's parent's matrix world.
The default is MULTIPLICATION_ORDER.POST. You can only set this at the most outer Transform2d
component. PRE will mimic how the transforms would be applied if you were doing them as
actual nested DOM elements. POST is much more natural mathematically and way more useful.
You should use POST :)##### - translate
Optional. An object describing translation. Either a plain JS object or a gl-matrix
vec3. If you pass in a JS object without all dimensions, missing dimensions will be
given 0 as the default value. If nothing is supplied no translation occurs.##### - scale
Optional. The transform's scale. either a number, a plain JS object or a gl-matrix
vec3. A number will apply the same scale to x, y and z. If you pass in a JS object
without all dimensions, missing dimensions will be given 1 as the default value. If
nothing is supplied no scaling occurs. ##### - rotate
Optional. The transform's rotation. A number provided in radians. If nothing is
supplied no rotation occurs.
##### - rotateAxis
Optional. The axis to rotate around. Either a plain JS object or a gl-matrix
vec3.
It can be any arbitrary axis, but it must be normalized (a unit vector). If nothing is
supplied it defaults to the z-axis: {x: 0, y: 0, z: 1}. Gotchas
$3
Make sure you don't mix
Transform2d and Transform3d components together! You can use
Transform3d almost in exactly the same way as Transform2d if you want to force a 3D
CSS transform, or combine with other Transform3ds. The only caveat here is it's better
to pass scale in as an object rather than a number (otherwise z will be scaled too).
Transform3d sets the default rotateAxis as the z axis, so it will rotate like a
Transform2d:$3
The library is generally very performant, mainly as it leverages the brilliant
gl-matrix
library and obviously React's virtual DOM. There are a couple of issues it's worth being
aware of though. These are mainly applicable when doing 3D transformations and/or when
calling render in a requestAnimationFrame callback:* When running in development mode,
propTypes checking can cause performance bottlenecks.
Chrome seems to suffer from this more than Firefox and Safari. If you're seeing
performance issues, try running a production build first before going down any other
rabbit holes.* With standard React apps you've probably got used to declaring lambdas and object literals
inline in your render method. For standard UI where renders are normally limited to user
interaction, you most likely won't notice a performance hit doing this. If you're gonna do
3D transformations or just a complex
render every frame, you may want to reconsider
this. Modern browsers can create and dispose of objects and arrays very quickly, but when
they're disposed of the garbage collector may cause a janky animation. As with all
performance optimization don't do it unless you need to. Just sayin' :) $3
IE10 and IE11 famously don't support
preserve-3d. To a certain extent this library can
help with issues here because it doesn't create any nested DOM elements, but computes
matrices to use on a single set of children. However, you will still very likely run into
z order issues as IE will maintain DOM / z-index order over 3D z position. This
restriction makes doing complex 3D transformations in these browsers impossible.
Ultimately it depends on your use case and whether you have to support them.How it works
Under the hood
Transform2d and Transform3d work in pretty much exactly the same way.
The gl-matrix library is used to handle all the vector and matrix maths, calculating
an object's local matrix based on the supplied props (translate, scale, rotate and
for Transform3d rotateAxis). These are multiplied together in the standard T R S
order. This local matrix is multiplied with the parentMatrixWorld (which is automatically
passed down from any parent Transforms) to produce the object's matrixWorld. This
matrixWorld can then be used in an HTML element's CSS transform property, either as
matrix or matrix3d depending on the transform. The matrixWorld is then passed down
to any nested Transforms as the next parentMatrixWorld.Transform2d and Transform3d handle passing down these props internally as well as
setting the style` property (while merging in any predefined styles) on their child HTML