Permalink
Browse files

Add support for data-rotate-order="xyz" attribute.

It turns out in CSS 3D, the order in which you specify for example
the rotateX(), rotateY() and rotateZ() transformations matter.
Each rotation is relative to the objects then-current position.
Impress.js being hardwired to always do rotateX->rotateY->rotateZ
was therefore limiting, and in fact there are some positions that
can never be reached with an xyz order. The new data-rotate-order=""
attribute allows to specify the order as a permutation of the 3
letters x, y, z, thus relaxing this limitation.

See http://openlife.cc/blogs/2016/october/3d-rotations-css-and-impressjs
for (much) more details.
  • Loading branch information...
1 parent 092b7b8 commit 9ab6b2b69a325fa088e89550d2b193286658f852 @henrikingo committed Oct 16, 2016
Showing with 90 additions and 22 deletions.
  1. +7 −0 DOCUMENTATION.md
  2. +3 −2 build.js
  3. +40 −10 js/impress.js
  4. +40 −10 src/impress.js
View
@@ -106,6 +106,13 @@ You can of course rotate it around Z axis with `data-rotate-z` - it has exactly
</div>
```
+#### 3D Rotation Order (data-rotate-order)
+
+The order in which the CSS `rotateX(), rotateY(), rotateZ()` transforms are applied matters. This is because each rotation is relative to the then current position of the element.
+
+By default the rotation order is `data-rotate-order="xyz"`. For some advanced uses you may need to change it. The demo presentation [3D rotations](examples/3D-rotations/index.html) sets this attribute to rotate some steps into positions that are impossible to reach with the default order.
+
+
## CSS
### 4D States (.past, .present and .future classes)
View
@@ -20,6 +20,7 @@ buildify()
'src/plugins/stop/stop.js',
'src/plugins/touch/touch.js',
'src/plugins/toolbar/toolbar.js'])
- .save('js/impress.js')
- .uglify()
+ .save('js/impress.js');
+/* .uglify()
.save('js/impress.min.js');
+*/
View
@@ -85,6 +85,26 @@
return isNaN(numeric) ? (fallback || 0) : Number(numeric);
};
+ var validateOrder = function ( order, fallback ) {
+ var validChars = "xyz";
+ var returnStr = "";
+ if ( typeof order == "string" ) {
+ for ( var i in order.split("") ) {
+ if( validChars.indexOf( order[i] >= 0 ) ) {
+ returnStr += order[i];
+ // Each of x,y,z can be used only once.
+ validChars = validChars.split(order[i]).join("");
+ }
+ }
+ }
+ if ( returnStr )
+ return returnStr;
+ else if ( fallback !== undefined )
+ return fallback;
+ else
+ return "xyz";
+ };
+
// `byId` returns element with given `id` - you probably have guessed that ;)
var byId = function ( id ) {
return document.getElementById(id);
@@ -121,11 +141,17 @@
// By default the rotations are in X Y Z order that can be reverted by passing `true`
// as second parameter.
var rotate = function ( r, revert ) {
- var rX = " rotateX(" + r.x + "deg) ",
- rY = " rotateY(" + r.y + "deg) ",
- rZ = " rotateZ(" + r.z + "deg) ";
-
- return revert ? rZ+rY+rX : rX+rY+rZ;
+ var order = r.order ? r.order : "xyz";
+ var css = "";
+ var axes = order.split("");
+ if ( revert ) {
+ axes = axes.reverse();
+ }
+
+ for ( var i in axes ) {
+ css += " rotate" + axes[i].toUpperCase() + "(" + r[axes[i]] + "deg)"
+ }
+ return css;
};
// `scale` builds a scale transform string for given data.
@@ -351,7 +377,8 @@
rotate: {
x: toNumber(data.rotateX),
y: toNumber(data.rotateY),
- z: toNumber(data.rotateZ || data.rotate)
+ z: toNumber(data.rotateZ || data.rotate),
+ order: validateOrder(data.rotateOrder)
},
scale: toNumber(data.scale, 1),
transitionDuration: toNumber(data.transitionDuration, config.transitionDuration),
@@ -440,7 +467,7 @@
// set a default initial state of the canvas
currentState = {
translate: { x: 0, y: 0, z: 0 },
- rotate: { x: 0, y: 0, z: 0 },
+ rotate: { x: 0, y: 0, z: 0, order: "xyz" },
scale: 1
};
@@ -512,7 +539,8 @@
rotate: {
x: -step.rotate.x,
y: -step.rotate.y,
- z: -step.rotate.z
+ z: -step.rotate.z,
+ order: step.rotate.order
},
translate: {
x: -step.translate.x,
@@ -687,9 +715,11 @@
rotate: {
x: interpolate(currentState.rotate.x, -nextStep.rotate.x, k),
y: interpolate(currentState.rotate.y, -nextStep.rotate.y, k),
- z: interpolate(currentState.rotate.z, -nextStep.rotate.z, k)
+ z: interpolate(currentState.rotate.z, -nextStep.rotate.z, k),
+ // Unfortunately there's a discontinuity if rotation order changes. Nothing I can do about it?
+ order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
},
- scale: interpolate(currentState.scale, nextScale)
+ scale: interpolate(currentState.scale, nextScale, k)
};
css(root, {
View
@@ -85,6 +85,26 @@
return isNaN(numeric) ? (fallback || 0) : Number(numeric);
};
+ var validateOrder = function ( order, fallback ) {
+ var validChars = "xyz";
+ var returnStr = "";
+ if ( typeof order == "string" ) {
+ for ( var i in order.split("") ) {
+ if( validChars.indexOf( order[i] >= 0 ) ) {
+ returnStr += order[i];
+ // Each of x,y,z can be used only once.
+ validChars = validChars.split(order[i]).join("");
+ }
+ }
+ }
+ if ( returnStr )
+ return returnStr;
+ else if ( fallback !== undefined )
+ return fallback;
+ else
+ return "xyz";
+ };
+
// `byId` returns element with given `id` - you probably have guessed that ;)
var byId = function ( id ) {
return document.getElementById(id);
@@ -121,11 +141,17 @@
// By default the rotations are in X Y Z order that can be reverted by passing `true`
// as second parameter.
var rotate = function ( r, revert ) {
- var rX = " rotateX(" + r.x + "deg) ",
- rY = " rotateY(" + r.y + "deg) ",
- rZ = " rotateZ(" + r.z + "deg) ";
-
- return revert ? rZ+rY+rX : rX+rY+rZ;
+ var order = r.order ? r.order : "xyz";
+ var css = "";
+ var axes = order.split("");
+ if ( revert ) {
+ axes = axes.reverse();
+ }
+
+ for ( var i in axes ) {
+ css += " rotate" + axes[i].toUpperCase() + "(" + r[axes[i]] + "deg)"
+ }
+ return css;
};
// `scale` builds a scale transform string for given data.
@@ -351,7 +377,8 @@
rotate: {
x: toNumber(data.rotateX),
y: toNumber(data.rotateY),
- z: toNumber(data.rotateZ || data.rotate)
+ z: toNumber(data.rotateZ || data.rotate),
+ order: validateOrder(data.rotateOrder)
},
scale: toNumber(data.scale, 1),
transitionDuration: toNumber(data.transitionDuration, config.transitionDuration),
@@ -440,7 +467,7 @@
// set a default initial state of the canvas
currentState = {
translate: { x: 0, y: 0, z: 0 },
- rotate: { x: 0, y: 0, z: 0 },
+ rotate: { x: 0, y: 0, z: 0, order: "xyz" },
scale: 1
};
@@ -512,7 +539,8 @@
rotate: {
x: -step.rotate.x,
y: -step.rotate.y,
- z: -step.rotate.z
+ z: -step.rotate.z,
+ order: step.rotate.order
},
translate: {
x: -step.translate.x,
@@ -687,9 +715,11 @@
rotate: {
x: interpolate(currentState.rotate.x, -nextStep.rotate.x, k),
y: interpolate(currentState.rotate.y, -nextStep.rotate.y, k),
- z: interpolate(currentState.rotate.z, -nextStep.rotate.z, k)
+ z: interpolate(currentState.rotate.z, -nextStep.rotate.z, k),
+ // Unfortunately there's a discontinuity if rotation order changes. Nothing I can do about it?
+ order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
},
- scale: interpolate(currentState.scale, nextScale)
+ scale: interpolate(currentState.scale, nextScale, k)
};
css(root, {

0 comments on commit 9ab6b2b

Please sign in to comment.