Adding touch gestures and mouse controls to a 3D CSS cube

An update to the orig­i­nal 3D cube (from July 2009 no less), I’ve added touch ges­ture sup­port (iOS) and click-and-drag behav­iour. Ani­ma­tion is incred­i­bly smooth on the iPhone, even the 3G model. Arrow key presses will still rotate the cube, and ESC will reset it.

Exper­i­ment: 3D cube with touch ges­tures and click and drag
One year on and the cube still only works in Safari. Chrome, which says it sup­ports webkit-perspective and webkit-transform, still ren­ders dif­fer­ently. Fire­fox doesn’t sup­port 3D trans­forms yet.

Exper­i­ment updated for Fire­fox 10 which sup­ports 3D trans­forms. Although the per­spec­tive appears off, prob­a­bly due to a perspective-origin bug.

It works rel­a­tively sim­ply: on click the start co-ordinates are saved and on drag the dif­fer­ence between cur­rent drag posi­tion and start­ing co-ordinates are applied to the cube as a trans­form, which is com­pleted after the spec­i­fied tran­si­tion dura­tion. Many thanks to Remy Sharp and his rubik’s exper­i­ment, which got me started with a lot of the basics.

Touch tweaks

Pixel val­ues for touch posi­tions are found in event.originalEvent.touches[0].pageX, instead of event.pageX. Using ‘start minus cur­rent’ pixel val­ues led the cube to rotate more than intended on the iPhone. To cor­rect, and for intu­itive behav­iour, the dif­fer­ence is reduced by a fac­tor of four.

JavaScript pre­vents sin­gle touch default events — e.g. scrolling and text selec­tion, but if it detects more than one touch (event.originalEvent.touches.length) the cube won’t rotate, so pinch and zoom will still work. This is a compromise.

A 200ms tran­si­tion dura­tion suits the browser, but on touch devices it felt slug­gish, so I’ve upped it to 50ms so the cube is always at your finger-tips.

Bet­ter CSS

The cube is cre­ated exactly as before, but I’ve sim­pli­fied the markup a lit­tle — drop­ping the face and num­ber class names in favour of CSS3 selectors:

#cube > div:first-child  {
-webkit-transform: rotateX(90deg) translateZ(200px);
-moz-transform: rotateX(90deg) translateZ(200px);
}

#cube > div:nth-child(2) {
-webkit-transform: translateZ(200px);
-moz-transform: translateZ(200px);
}

#cube > div:nth-child(3) {
-webkit-transform: rotateY(90deg) translateZ(200px);
-moz-transform: rotateY(90deg) translateZ(200px);
text-align: center;
}

#cube > div:nth-child(4) {
-webkit-transform: rotateY(180deg) translateZ(200px);
-moz-transform: rotateY(180deg) translateZ(200px);
}

#cube > div:nth-child(5) {
-webkit-transform: rotateY(-90deg) translateZ(200px);
-moz-transform: rotateY(-90deg) translateZ(200px);
}

#cube > div:nth-child(6) {
-webkit-transform: rotateX(-90deg) rotate(180deg) translateZ(200px);
-moz-transform: rotateX(-90deg) rotate(180deg) translateZ(200px);
}

Any ques­tions?

This is quite a speedy write-up, if any­thing needs explain­ing I’m happy to go into a bit more detail.

Paul Hayes

Paul Hayes is a developer at Last.fm. You should follow him on Twitter, where he talks about UX, HTML, CSS and JavaScript, amongst other cool stuff.