Rotating 3D Cube with Touch (webkit only)

I've been toying around with this for a little while --- and it's been a lot of fun. It's a pretty straightforward project: make a 3D cube with CSS and make it spin with javascript via touch and mouse interactions. Currently, the interactive part only works in webkit browsers as I'm using the WebkitCSSMatrix object to apply the transformations. In the future, I hope to come back to this and revise it to work non-webkit browsers. But that's a project for another day.

Swipe to rotate

Let's start with making the 3D cube with CSS using 3D transforms (this part should work in any browser that supports CSS 3D transformations):

<div class="cube">

The HTML really only requires a containing element and 6 children (one for each face of the 3d cube.) We'll apply 3D translation to each face:

.cube { background:#ccc;}
.cube div { 
    border:1px solid #fff;
.cube > div:first-child  {
	-webkit-transform: rotateX(90deg) translateZ(150px);
	-moz-transform: rotateX(90deg) translateZ(150px);
.cube > div:nth-child(2) {
	-webkit-transform: translateZ(150px);
	-moz-transform: translateZ(150px);
.cube > div:nth-child(3) {
	-webkit-transform: rotateY(90deg) translateZ(150px);
	-moz-transform: rotateY(90deg) translateZ(150px);
.cube > div:nth-child(4) {
	-webkit-transform: rotateY(180deg) translateZ(150px);
	-moz-transform: rotateY(180deg) translateZ(150px);
.cube > div:nth-child(5) {
	-webkit-transform: rotateY(-90deg) translateZ(150px);
	-moz-transform: rotateY(-90deg) translateZ(150px);
.cube > div:nth-child(6) {
	-webkit-transform: rotateX(-90deg) translateZ(150px);
	-moz-transform: rotateX(-90deg) translateZ(150px);

One thing to note is that the translateZ value is 1/2 of the width (or height) of the face you're transforming (depending on which axis you're rotating) because the default transform-origin is the center. I've given the containing element a background color in the example above so you can see the relationship between it and the translated faces of the cube.

Anyway, at this point our cube is looking pretty flat:

Let's give this thing some dimension:

.cube {
    -webkit-transform:rotateX(10deg) rotateY(45deg);
    -moz-transform:rotateX(10deg) rotateY(45deg);

The transform-style:preserve-3d basically means that child elements should maintain their 3d position with respect to their parent, which means that as we apply css transformations to the containing element, its children will transform accordingly.

Okay, now let's make this thing spin. In order to do that, we probably need to add one more element around the cube container. I'll explain why in a bit.

<div class="cube-outer-wrapper">
	<div class="cube">

Next, let's start adding some javascript. The first thing we need to do is normalize some variables.

hasTouch = 'ontouchstart' in window,
// normalize requestAnimationFrame
nextFrame = (function () {
	return window.requestAnimationFrame
        || window.webkitRequestAnimationFrame
	|| window.mozRequestAnimationFrame
	|| function (callback) { return setTimeout(callback, 1); }
// normalize cancelRequestAnimationFrame
cancelFrame = (function () {
	return window.cancelRequestAnimationFrame
	|| window.cancelWebkitRequestAnimationFrame
	|| window.cancelMozRequestAnimationFrame
	|| clearTimeout

// normalize touch and mouse events
START_EV = hasTouch ? 'touchstart' : 'mousedown',
MOVE_EV = hasTouch ? 'touchmove' : 'mousemove',
END_EV = hasTouch ? 'touchend' : 'mouseup',
CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup';

Next, let's stub out an object.

	// Constructor
	Cube = function () {};

	Cube.prototype = {
		// handle bound events
		handleEvent: function () {},

		// rotate the cube
		_rotate: function () {},

		// for animating with momentum
		_animate: function () {},

		// bind events
		_bind: function () {},

		// unbind events
		_unbind: function (eventName, element, bubble) {},

		// called on touchstart / mousedown
		_start: function () {},

		// called on touchmove / mousemove
		_move: function (e) {},

		// called on touchend / mouseup
		_end: function (e) {},

		// called on transition end
		_transitionEnd: function (e) {}

And now we'll start coloring everything in. Let's start with the constructor.

Cube = function (element) {
	this.container = element;
	this.outerWrapper = this.container.parentNode;


	if (!hasTouch) {
		this._bind('mouseout', this.outerWrapper);

Remember above when I was describing the markup and how I said we'd want to add another element around the cube? We're using that element (this.container.parentNode) to bind the touch events to. If we use the cube container (the element we're actually rotating) the active area in the 2D space decreases as the angle of rotation becomes more perpendicular to the plane you're interacting with. Binding the interaction to an element that does not rotate makes the whole thing considerably more fluid.

Next, we know there are some events we need to handle: touchstart, touchmove, touchend, and transitionend -- but let's hold off on talking about what we need to do on transitionEnd for right now.

On touchstart, we'll want to know what time the touch started (important for evaluating the speed of the swipe,) the starting X coordinate, and the starting Y coordinate. We'll also want to reset any currently running rotations and any inline styles applied by running animations.

_start: function (e) {
	var point = hasTouch ? e.changedTouches[0] : e,
	      self = this; = 0;

	this._unbind('webkitTransitionEnd', this.container);

	this.startTime = e.timeStamp;
	this.startX = point.screenX;
	this.lastX = point.screenX;


	this.animationDuration = 0.01;

The method called on move should evaluate the current X position of the touch / mouse event and compare it to the last recorded coordinate, then set the last recorded coordinate to the current value.

_move: function (e) {
	var point = hasTouch ? e.touches[0] : e;
	this._rotate((point.screenX - this.lastX) / 2;
	this.lastX = point.screenX;

The method called on touchend / mouseup should evaluate the speed of the movement and animate the cube accordingly. 300ms, while somewhat arbitrary, felt like a good delimiter between a gentle rotation and one that should apply some momentum. This isn't a perfect way to do this as we should also probably be evaluating acceleration here to distinguish between a somewhat steady pan and one whose speed is increasing significantly. For the sake of simplicity, we'll table that for now...

_end: function (e) {
	var self = this,
	    now = e.timeStamp,
	    speed = now - this.startTime;
	if (speed < 300) {
		this._animate((this.lastX - this.startX) * 2, speed);
		this.animationDuration = 0.01;


Since we've already referenced _rotate() and _animate() in out methods above, let's talk about them next.

Our _rotate() method is pretty straightforward. it takes some value as an argument and rotates the cube by that value (in degrees.) However, because we're applying the rotate using the WebKitCSSMatrix object, this demo only works in webkit (though it would be great if there was a moz equivalent.) Why use WebKitCSSMatrix? It's purely for the sake of simplicity as it makes getting and setting 3D Matrix values really easy.

_rotate: function (y) {
	var currRotation = window.getComputedStyle(this.container).getPropertyValue('-webkit-transform'),
	     m = new WebKitCSSMatrix(currRotation),
	newRotation = m.rotate(0, Math.floor(y), 0).toString(); = newRotation;

The _animate() method does a little bit more. If you recall from above, we only call this when the total duration of swipe is under the set threshold. What we want to do here is call _rotate() periodically, each time reducing the rotation and increasing the duration of the animation to create the effect of deceleration.

_animate: function (x, speed) {
	var target = x || this.targetX,
	     self = this,
	     deceleration = 0.85,
	     speed = speed || self.velocity,
	     duration = 0.01;

	this.targetX = x;
	this.velocity = speed;
	this.speed = this.velocity;
	animate = function (){ = '-webkit-transform ' + self.animationDuration + 's linear';
		self._bind('webkitTransitionEnd', self.container);

		self.targetX = (self.targetX) * (deceleration * deceleration);
		self.animationDuration = (self.animationDuration * (1.5 / deceleration)).toFixed(2);

	if (Math.abs(this.targetX) > 0.2) {
		this.animation = nextFrame(animate);
	} else {

Remember I said we'd talk about transitionEnd? Now seems like a good time. When we called the local animate function above, we bound transitionEnd event to the container, which simply unbinds itself and calls the _animate method again. if the target rotation is above the threshold, it continues the animation. It's as simple as that.

_transitionEnd: function (e) {
	this._unbind('webkitTransitionEnd', this.container);

Finally, let's wire this thing up.

handleEvent: function (e) {
	var self = this;
	switch (e.type) {
		case START_EV:
		case MOVE_EV:
		case END_EV:
		case 'mouseout':
		case 'webkitTransitionEnd':


_bind: function (eventName, element, bubble) {
	(element || this.outerWrapper).addEventListener(eventName, this, !!bubble);

_unbind: function (eventName, element, bubble) {
	(element || this.outerWrapper).removeEventListener(eventName, this, !!bubble);

So, that's it... now I just need to find practical application for this.


Watch for chewing, especially around items such as electric cords. Try to reward your animal whenever they respond or pay heed to your voice when you utter out their name. Children will love that Toffee the pony loves to be fussed over and pampered.

Add new comment