|
2 | 2 |
|
3 | 3 | # React CSS Transform
|
4 | 4 |
|
5 |
| -A helper library to handle nested 2D and 3D CSS transforms using matrix multiplication, |
| 5 | +A React helper library to handle nested 2D and 3D CSS transforms using matrix multiplication, |
6 | 6 | drastically reducing the number of nested DOM elements required and making complex
|
7 | 7 | combinations of nested transformations trivial.
|
8 | 8 |
|
9 | 9 | `<Transform2d>` was initially developed while I was working at [Pest Pulse](https://www.pestpulse.com/)
|
10 | 10 | for a zoomable/pannable map with markers.
|
11 | 11 |
|
12 |
| -Get started: |
| 12 | +## Install |
| 13 | + |
| 14 | +`react-css-transform` uses the [gl-matrix library](https://github.com/toji/gl-matrix) |
| 15 | +under the hood. It's a peer dependency so install like so: |
13 | 16 |
|
14 | 17 | `npm install gl-matrix react-css-transform --save`
|
15 | 18 |
|
16 |
| -More docs coming soon! |
| 19 | +## Examples |
17 | 20 |
|
18 | 21 | [3D Cubes Example](https://baseten.github.io/react-css-transform/3d-cubes/index.html)
|
19 | 22 |
|
20 | 23 | 
|
| 24 | + |
| 25 | +## Usage |
| 26 | + |
| 27 | +In general it is best to make sure that the HTML elements you are transforming have styles |
| 28 | +set like so: |
| 29 | + |
| 30 | +```css |
| 31 | +position: absolute; |
| 32 | +left: 0; |
| 33 | +top: 0; |
| 34 | +``` |
| 35 | + |
| 36 | +If you are using 3D Transforms you also most likely want to set the containing HTML Element |
| 37 | +to have `preserve-3d` and some `perspective`: |
| 38 | + |
| 39 | +```css |
| 40 | +transform-style: preserve-3d; |
| 41 | +perspective: 500px; |
| 42 | +``` |
| 43 | + |
| 44 | +### <Transform2d /> |
| 45 | + |
| 46 | +The styles provided to the `<div />` below will be merged with the computed CSS `matrix` transform |
| 47 | + |
| 48 | +```javascript |
| 49 | +<Transform2d translate={{x:10, y:20}} scale={2} rotate={Math.PI} /> |
| 50 | + <Transform2d translate={{x: -50, y:-50}}> |
| 51 | + // ... as much nesting as you like |
| 52 | + <div style={{width: 100, height: 100, margin: -50, background: '#f00'}} /> |
| 53 | + </Transform2d> |
| 54 | +</Transform2d> |
| 55 | +``` |
| 56 | + |
| 57 | +#### Props |
| 58 | + |
| 59 | +##### - multiplicationOrder |
| 60 | + |
| 61 | +**Optional**. An enum: either `MULTIPLICATION_ORDER.PRE` or `MULTIPLICATION_ORDER.POST`. |
| 62 | +This determines the order an object's local matrix is multiplied with it's parent's matrix world. |
| 63 | +The default is `MULTIPLICATION_ORDER.POST`. You can only set this at the most outer `Transform2d` |
| 64 | +component. `PRE` will mimic how the transforms would be applied if you were doing them as |
| 65 | +actual nested DOM elements. `POST` is much more natural mathematically and way more useful. |
| 66 | +You should use `POST` :) |
| 67 | + |
| 68 | +##### - translate |
| 69 | + |
| 70 | +**Optional**. An object describing translation. Either a plain JS object or a gl-matrix |
| 71 | +`vec2`. If you pass in a JS object without all dimensions, missing dimensions will be |
| 72 | +given `0` as the default value. If nothing is supplied no translation occurs. |
| 73 | + |
| 74 | +##### - scale |
| 75 | + |
| 76 | +**Optional**. The transform's scale. either a number, a plain JS object or a gl-matrix |
| 77 | +`vec2`. A number will apply the same scale to `x` and `y`. If you pass in a JS object |
| 78 | +without all dimensions, missing dimensions will be given `1` as the default value. If |
| 79 | +nothing is supplied no scaling occurs. |
| 80 | + |
| 81 | +##### - rotate |
| 82 | + |
| 83 | +**Optional**. The transform's rotation. A number provided in **radians**. If nothing is |
| 84 | +supplied no rotation occurs. |
| 85 | + |
| 86 | +### <Transform3d /> |
| 87 | + |
| 88 | +The styles provided to the `<div />` below will be merged with the CSS `matrix3d` transform |
| 89 | + |
| 90 | +```javascript |
| 91 | +const translateToCentre = { |
| 92 | + x: window.innerWidth / 2, |
| 93 | + y: window.innerHeight / 2 |
| 94 | +}; |
| 95 | + |
| 96 | +const theta = Math.PI / 2; |
| 97 | +const yAxis = { x: 0, y: 1, z: 0 }; |
| 98 | +const zAxis = { x: 0, y: 0, z: 1 }; |
| 99 | + |
| 100 | +<Transform3d translate={translateToCentre} rotate={theta} rotateAxis={yAxis}> |
| 101 | + <Transform3d rotate={theta} rotateAxis={zAxis}> |
| 102 | + // ... as much nesting as you like |
| 103 | + <div style={{width: 100, height: 100, margin: -50, background: '#f00'}} /> |
| 104 | + </Transform3d> |
| 105 | +<Transform3d translate={cubeGroup1Translate}> |
| 106 | +``` |
| 107 | + |
| 108 | +#### Props |
| 109 | + |
| 110 | +##### - multiplicationOrder |
| 111 | + |
| 112 | +**Optional**. An enum: either `MULTIPLICATION_ORDER.PRE` or `MULTIPLICATION_ORDER.POST`. |
| 113 | +This determines the order an object's local matrix is multiplied with it's parent's matrix world. |
| 114 | +The default is `MULTIPLICATION_ORDER.POST`. You can only set this at the most outer `Transform2d` |
| 115 | +component. `PRE` will mimic how the transforms would be applied if you were doing them as |
| 116 | +actual nested DOM elements. `POST` is much more natural mathematically and way more useful. |
| 117 | +You should use `POST` :) |
| 118 | + |
| 119 | +##### - translate |
| 120 | + |
| 121 | +**Optional**. An object describing translation. Either a plain JS object or a gl-matrix |
| 122 | +`vec3`. If you pass in a JS object without all dimensions, missing dimensions will be |
| 123 | +given `0` as the default value. If nothing is supplied no translation occurs. |
| 124 | + |
| 125 | +##### - scale |
| 126 | + |
| 127 | +**Optional**. The transform's scale. either a number, a plain JS object or a gl-matrix |
| 128 | +`vec3`. A number will apply the same scale to `x`, `y` and `z`. If you pass in a JS object |
| 129 | +without all dimensions, missing dimensions will be given `1` as the default value. If |
| 130 | +nothing is supplied no scaling occurs. |
| 131 | + |
| 132 | +##### - rotate |
| 133 | + |
| 134 | +**Optional**. The transform's rotation. A number provided in **radians**. If nothing is |
| 135 | +supplied no rotation occurs. |
| 136 | + |
| 137 | +##### - rotateAxis |
| 138 | + |
| 139 | +**Optional**. The axis to rotate around. Either a plain JS object or a gl-matrix `vec3`. |
| 140 | +It can be any arbitrary axis, but it must be normalized (a unit vector). If nothing is |
| 141 | +supplied it defaults to the z-axis: `{x: 0, y: 0, z: 1}`. |
| 142 | + |
| 143 | +## Gotchas |
| 144 | + |
| 145 | +### 2D and 3D Transforms together |
| 146 | + |
| 147 | +Make sure you don't mix `Transform2d` and `Transform3d` components together! You can use |
| 148 | +`Transform3d` almost in exactly the same way as `Transform2d` if you want to force a 3D |
| 149 | +CSS transform, or combine with other `Transform3d`s. The only caveat here is it's better |
| 150 | +to pass scale in as an object rather than a number (otherwise z will be scaled too). |
| 151 | +`Transform3d` sets the default `rotateAxis` as the z axis, so it will rotate like a |
| 152 | +`Transform2d`: |
| 153 | + |
| 154 | +`<Transform3d translate={{x:10, y:10}} scale={{x:2, y:2}} rotate={Math.PI} />` |
| 155 | + |
| 156 | +### Performance |
| 157 | + |
| 158 | +With standard React apps you've probably got used to declaring lambdas and object literals |
| 159 | +inline in your render method. People sometimes talk about how this is a performance hit, |
| 160 | +but realistically for standard UI where renders are mostly limited to user interaction, |
| 161 | +it's going to make no noticeable difference and the readability is worth it. |
| 162 | + |
| 163 | +If you're gonna do 3D transformations every frame, or more specifically call `render` as |
| 164 | +the result of a `requestAnimationFrame` callback, you *may* want to reconsider this. |
| 165 | +Modern browsers can create and dispose of objects and arrays very quickly, but when they're |
| 166 | +disposed of the garbage collector *may* cause a janky animation. As with all |
| 167 | +performance optimization don't do it unless you need to. Just sayin' :) |
| 168 | + |
| 169 | +### IE10 and IE11 |
| 170 | + |
| 171 | +IE10 and IE11 famously don't support `preserve-3d`. To a certain extent this library can |
| 172 | +help with issues here because it won't create any nested DOM elements, but will compute |
| 173 | +the correct matrices to use on a single set of children. Realistically doing complex 3D |
| 174 | +transformations in these browsers is not possible - but is nothing to do with this library :) |
| 175 | + |
| 176 | +## How it works |
| 177 | + |
| 178 | +Under the hood `Transform2d` and `Transform3d` work in pretty much exactly the same way. |
| 179 | +The `gl-matrix` library is used to handle all the vector and matrix maths, calculating |
| 180 | +an object's local matrix based on the supplied props (`translate`, `scale`, `rotate` and |
| 181 | +for `Transform3d` `rotateAxis`). These are multiplied together in the standard `T * R * S` |
| 182 | +order. This local matrix is multiplied with the `parentMatrixWorld` (which is automatically |
| 183 | +passed down from any parent Transforms) to produce the object's `matrixWorld`. This |
| 184 | +`matrixWorld` can then be used in an HTML element's [CSS transform property](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix), either as |
| 185 | +`matrix` or `matrix3d` depending on the transform. The `matrixWorld` is then passed down |
| 186 | +to any nested Transforms as the next `parentMatrixWorld`. |
| 187 | + |
| 188 | +`Transform2d` and `Transform3d` handle passing down these props internally as well as |
| 189 | +setting the `style` property (while merging in any predefined styles) on their child HTML |
| 190 | +elements. But if you need to add any custom React Components in between them and the HTML |
| 191 | +element you wish to transform, you will need to manually pass the props through, as you |
| 192 | +can see [here](https://github.com/baseten/react-css-transform/blob/master/examples/3d-cubes/src/components/CubeGroup.js), |
0 commit comments