eleqtriq

Ich hatte die letzten Tage endlich mal Zeit den längst überfälligen 3. Teil meiner Serie über CSS3 3D-Transformationen zu zu beenden. Sollte Ihr Teil 1 & Teil 2 noch nicht gelesen haben, rate ich das kurz nachzuholen.

In diesem Tutorial werden wir uns damit befassen, wie man einen Packshot in HTML und CSS erstellt, indem man einige CSS3 3D-Transformationen anwendet. Anschließend fügen wir noch etwas Javascript hinzu und zaubern ein frei rotierbares Objekt. Da wir direkt noch die notwendigen Touch-Events hinzufügen werden wird das Ergebnis eine Box sein, die nicht nur in Safari-Desktop sondern auch auf dem iPhone und dem iPad dargestellt und bewegt werden kann. Eine kleine Warnung vorab: Teil 2 ist recht mathelastig.

Beispiel öffnen

...oder öffnet www.eleqtriq.com/wp-content/static/demos/2010/rotation/ direkt in eurem mobilen Browser (sorry für die mobilsuboptimale URL).

Wie ist der Stand von CSS3 3D?

Seit meinem letzten Tutorial vor einigen Monaten hat die Zahl der Internetseiten die CSS 3D verwenden weiter zugenommen. Aber wir wissen: CSS 3D-Transformationen werden außerhalb von iOS derzeit fast überhaupt nicht unterstützt. Warum sollte man sich mit dieser Technik also befassen?

Genug geplaudert, legen wir also los:

1. Wir erstellen einen Packshot mit HTML and CSS:

Um CSS 3D unter realistischen Bedingungen zu demonstrieren werden wir einen virtuellen Packshot für Spritebaker entwickeln. Den Anfang macht ein einfaches HTML-Gerüst:

<!-- Wir brauchen erstmal etwas dem wir eine CSS 3D-Perspective zuweisen können. Deshalb bestimmen wir eine <section>, die als Bühne fungiert. --> <section id="viewport"> <!-- Der nächste Container ist die eigentliche Box. Er ist in 6 Sections für die einzelnen Seiten gegliedert: --> <article id="box"> <section id="front"> <!-- HTML für die Vorderseite--> </section> <section id="top"> <!-- HTML für die Oberseite--> </section> <section id="bottom"> <!-- HTML für die Unterseite--> </section> <section id="back"> <!-- HTML für die Rückseite--> </section> <section id="left"> <!-- HTML für die linke Seite--> </section;> <section id="right"> <!-- HTML für die Innenseite hier--> <!-- Ok, blöder Witz.--> </section> </article> </section>

Den Inhalt stylen wir mit einer handvoll konventioneller HTML und CSS-Techniken, deswegen spar ich es mir hier, Euch mit den Details zu langweilen, Ihr seid schließlich Profis, oder? Ein paar langweilige 2005 Image-Replacements und CSS-Sprites um einiges einigermaßen transparent und crawlbar zu machen (Ihr wisst, dass ich ein großer Anhänger von Data-URI-Sprites bin, aber in meinen Demos vermeide ich sie in der Regel damit der Code lesbar bleibt).

2. Die CSS 3D Transformationen:

Langsam fängt's an Spaß zu machen: nun bauen wir uns eine hübsche CSS-Box indem wir jede Seite drehen und an die passende Stelle verschieben. Die CSS-Transformationen sind nicht weiter kompliziert und leicht nachvollziehbar:

section#viewport{ -webkit-perspective: 700px; -webkit-perspective-origin: 50% 50%; } article#box{ -webkit-transform-style: preserve-3d; } #box section{ -webkit-transform-style: flat; /*webkit is extrem zickig was das z-Sorting angeht. Nutzt backface-visibility: hidden wann immer Ihr könnt! */ -webkit-backface-visibility: hidden; } #front, #back{ width:250px; height: 354px; -webkit-transform: translate3d(0px, 0px, 37px); } #back{ -webkit-transform: rotateY(180deg) translate3d(0px, 0px, 37px); } #top, #bottom{ width:250px; height: 74px; -webkit-transform: rotateX(90deg) translate3d(0px, 0px, 37px); } #bottom{ -webkit-transform: rotateX(-90deg) translate3d(0px, 0px, 317px); } #left, #right{ width: 74px; height: 354px; -webkit-transform: rotateY(-90deg) translate3d(0px, 0px, 37px); } #right{ -webkit-transform: rotateY(90deg) translate3d(0px, 0px, 213px); }

Zum Abschluß noch ein kleiner webkit-box-reflect daruntergesetzt und voilá, die Box steht! Aufmerksame Leser werden bemerkt haben, dass die Reflektion falsch ist. Die Form der Box wird zwar korrekt gespiegelt, aber nicht die Typo darauf. Es handelt sich hier um einen Fehler in Webkit für MacOS. Wenn man eine 3D-Transformation auf ein Element anwendet, führt das dazu, dass ihr Inhalt nicht gespiegelt wird (ein Fehler, der im Safari für iOS merkwürdigerweise nicht auftritt).

3. Die Box rotierbar machen:

1: Der Virtuelle Trackball.

Nun wird's interessant: wir werden jetzt die Interaktivität mit Javascript hinzufügen, damit wir die Box mit der Maus frei rotieren können (nebenbei bemerkt: wir werden Javascript-Librarys wie jQuery oder Motools meiden und stattdessen direkt für Webkit coden, das ist ja unsere auschließliche Zielengine. Dadurch bleibt unser Script schlank und klein, <3kb gzipped, und der Mobilbrowser schlägt Purzelbäume vor Glück.

Ein paar Worte über Objektrotation im 3-dimensionalen Raum:

Objekte in drei Dimensionen zu rotien mag sich erstmal nach keiner großen Sache anhören: wir ändern einfach die Rotation abhängig von der Mausposition auf dem Bildschirm, oder? Leider ist das nicht so einfach. Die Bewegung wird sich unnatürlich und "holprig" anfühlen und wir werden dem Problem des "Gimbal-Lock" begegnen, welches auch die Spieleentwickler immer wieder zum Haareausreissen treibt.

Es gibt eine bessere Methode um eine natürliche, weiche Rotationsbewegung zu erzielen. Man nennt diese Technik den "Virtuellen Trackball". Es handelt sich dabei um eine virtuelle Kugel um das 3D-Objekt. Jeder Mausklick auf den Bildschirm wird auf diese virtuelle Kugel projiziert und jede Zugbewegung bewirkt eine Drehung des virtuellen Trackballs einschließlich des Objektes darinnen. Schauen wir mal, wie sich ein virtueller Trackball für unsere Box bauen lässt:

Die Mathematik des Virtuellen Trackball:

Wie man einen Virtuellen Trackball konstruiert:

2: Die Mauskoordinaten auf den Virtuellen Trackball mappen.

Der Mittelpunkt der Kugel und der Box müssen sich am Mittelpunkt des Koordinaten-Systems befinden (y=0, y=0, z=0), der auch den Mittelpunkt der Bühne darstellt. Da der einzige Sinn eines virtuellen Trackballes darin liegt, Winkel im Raum zu berechnen, sind uns Entfernungen und Strecken egal, es reicht ihr Verhältnis zueinander zu kennen. Wir übersetzen die Kugel deshalb in ein Korrdinatensystem mit dessen Maßen sich einfach rechnen läßt, in diesem Fall ein Koordinatensytem in dem der Radius einfach "1" beträgt. Die Maus-Koordinaten auf dem Bildschirm lassen sich leicht in das neue Koordinatensystem umrechnen indem wir alle Werte durch den ursprünglichen Radius teilen. Welchen Radius wir verwenden hängt von unserem Empfinden ab. Ich habe mich dafür entschieden, die Hälfte der kürzesten Seite des Viewport zu wählen, manchmal mag aber ein anderes Maß geeigneter sein.

2. Bei Mausdruck registrieren wir die Mauskoordinaten und mappen sie auf den Trackball:

3: Die Rotationsachse wird anhand des Vektors bei Mausdruck und während der Drehung bestimmt.

Wir übersetzen die Bühnenkoordinaten zu Trackball-Koordinaten...

kugelX*2=mausX/radius
kugelY*2=mausY/radius

...und bewegen anschließend den Mittelpunkt [0,0] zum Mittelpunkt des Trackball-Koordinatensystems:

x = x - 1;
y = y - 1;

Den Radius unserer Kugeln kennen wir ja bereits (Ihr erinnert Euch sicher, es war 1) somit ist es relativ einfach die z-Koordinate des übersetzten Punktes unter Zuhilfenahme von etwas guter alter Trigonometrie zu bestimmen:

2*Z2=1-x2-y2

Nachdem wir nun alles beieinander haben, können wir loslegen und die restliche Logik basteln. Ich gebe gleich einen Überblick in Pseudocode. Vorher noch eine Bemerkung: wir nutzen vorwiegend 3D-Transformationsmatrizen und Rotationen um 3D-Vektoren (rotation3d) aber wir vermeiden "rotateX, Y, Z". Würden wir Rotationen um einzelne Achsen einsetzen, müssten die Drehungen mittels Quaternionen berechnet werden um den erwähnten Gimbal-Lock zu vermeiden. Wenn wir rotate3D oder matrix3d verwenden nimmt Webkit uns die Sache mit den Quaternionen ab.

Nach Abschluss der Rotation bestimmen wir eine neue Matrix3D aus der letzten Rotationsachse und dem letzten Winkel. Während der nächsten Rotation wenden wir sowohl diese Startmatrix als auch die 3D-Rotation um die Achse in einer kombinierten Transformation auf unsere Box an. Dadurch verhindern wir, dass unsere Box jedesmal wenn wir eine neue Rotation einleiten in den ursprünglichen "unrotierten" Zustand zurückspringt.

function init(){ den virtuellen Trackball initiieren; Das Viewort-HTML-Element nach etwas Rotierbarem durchsuchen; Mousedown- oder Touchstart-Listener setzen, um die Rotation einzuleiten; Mouseup- oder Touchend-Listener setzen um die Rotation zu beenden; Mousemove- oder Touchmove-Listener für die Rotation setzen; Eine Matrix3d aus intialem Wikel und Rotationsachse bestimmen; } function startrotation(){ Click-Position ermitteln und auf den Trackball übersetzen; Den entsprechenden 3D-Vector in Variable "mouseDownVect" speichern; } function rotate(){ Maus-Position verfolgen und auf den Trackball übersetzen; Den entsprechenden 3d-Vector in Variable "mouseMoveVect" speichern; Die Rotationsachse ermitteln, indem die Normale auf mouseDownVect und mouseMoveVect bestimmt wird; Rotationswinkel zwischen mouseMoveVect und mouseDownVect bestimmen; Box mittels Startmatrix und rotate3d(axis, rotation-angle) transformieren; } function finishrotation(){ Neue Matrix3d aus dem letzen Winkel und Rotationsachse bestimmen; Die letzte Start-Matrix and die neue Matrix durch Bestimmen des Skalarprodukes zusammenfassen; Die zusammengefasste Matrix3d zur neuen Start-Matrix machen; }

Soweit ein Überblick der Logik. Wenn Ihr mehr herausfinden wollt, könnt Ihr Euch auch gerne direkt das Javascript anschauen, ich habe mein Bestes gegeben jeden Schritt zu kommentieren.

Das Script (ich habe es, nebenbei bemerkt, "traqball.js" getauft) steht unter MIT- und GPL-Lizenz. Ihr dürft es in Euren Projekten einsetzen solange Ihr die Lizenzbedingungen respektiert. Die Implementierung ist einfach:

Damit sind wir durch! Ich hoffe, diese kleine Lektion war für einige von Euch hilfreich. Wenn Ihr diese Methode bei einem Projekt anwenden konntet, freue ich mich natürlich über eine Mail mit einem Link.

Update; Ich habe traqball.js nach 2.0 geupdated. Source liegt nun auf Github. Mehr Infos hier..

Trackback

42 Kommentare zu „Natürliche Objektrotation mit CSS3 3D“

  1. Kommentar by waqas Sa. 23. Apr 2011, 01:48

    Sigh, what I wouldn’t give to stop people from using stupid browser sniffing. Fails on chrome because Chrome versions are no longer single digit…

  2. Kommentar by Dirk So. 24. Apr 2011, 13:00

    Actually you have 3 options here:

    1. You are a smart kid. Shouldn’t be too hard for you to figure out how to set up the browser sniffing for current versions of chrome.

    2. Wait until I put a new version online. Should happen within the next 2 weeks if I find the time.

    3. Build a version that works without browser sniffing (which isn’t that easy, I can tell you) and post the solution on your blog or here. This is much more constructive than hanging around on other peoples blogs and proclaim. And as a side efffect the community will actually profit from your input. Read Paul’s comment above for an example how to give constructive feedback the right way.

  3. Kommentar by Derek Fr. 29. Jul 2011, 23:08

    How do you use the „traqball.js“? Can you make a tutorial of it?

  4. Kommentar by Derek Fr. 29. Jul 2011, 23:46

    I don’t really understand what should put in the [a,b,c] and the „initial rotation-angle“… Please help…

  5. Kommentar by Dirk Sa. 30. Jul 2011, 00:34

    Hi Derek,
    the first is an array representing the x-, y-, z-axis components, the last is an angle in radian. E.g. [0,1,0], 1.5707 will rotate the object 90deg around the y-axis. If you are undecided leave them out and default vals ([1,0,0], 0 ) will kick in.

  6. Kommentar by Derek Sa. 30. Jul 2011, 20:26

    @Dirk,
    Thank you for your explanation! And I spent some time, and I figured out that the „traqball.js“ is not working if I use „margin:0 auto“ to center the cube.

  7. Kommentar by Derek So. 31. Jul 2011, 02:32

    CORRECTION: Maybe it is not the “margin:0 auto” that causing it not working. I don’t know but when I add a „width“ and „height“ to the container, it started to work.
    http://derek1906.site50.net/experiment/css3d/

  8. Kommentar by ZhouQi Do. 11. Aug 2011, 14:57

    it’s magic!

  9. Pingback by CSS Summit 2011 – Notes from Day 1 So. 14. Aug 2011, 07:29

    […] Natural object rotations with CSS3 3D – very cool effect (click on image & use your mouse to move it around). […]

  10. Kommentar by Javery Fr. 14. Okt 2011, 06:33

    You were just featured during the Web Directions 2011 in Sydney. You’ve got a room of over 100 people who are VERY impressed.

    Nice work!

  11. Pingback by New CSS Techniques and Tools | W Design Love | Graphic and Web Design Blog | Coding | tutorials | freebies | Wordpress | inspiration | Blog So. 16. Okt 2011, 21:40

    […] Natural Object-Rotation with CSS3 3D A tutorial by Dirk Weber that teaches us how to build a 3D packshot in HTML and CSS by applying some CSS 3D-transforms. By adding some Javascript, we can make the object freely rotatable in 3D space. And as we will enhance our Javascript with some touch-interactivity, the packshot will also work nicely in Safari for iOS-platforms like iPhone or iPad. […]

  12. Pingback by Weekly Update 4 | Justin Avery Web Site Analysis Design Development So. 23. Okt 2011, 17:33

    […] rotations…. it’s got most of what you might want (cause you never really need it) 3D Image transformation is one of the cool bits I learned while at the Web Directions South.  This is an awesome […]

  13. Kommentar by Paul Irish Sa. 5. Nov 2011, 22:46

    Nice rewrite! Sexy conditional loading action. :)

    Unfortunately the new code has a bug. Looks like Chrome doesn’t like you not passing an argument to cancelRequestAnimationFrame().. I get TypeError: Not enough arguments

    Fix is easy enough. Capture the return value of rAF and then shoot it into your cRAF call. Already tested it out and it’s groovy. :)

    Btw we caught this because the demo broke onstage during @garazi’s talk at StarTechConf in santiago Chile. :p

  14. Kommentar by Dirk Mo. 7. Nov 2011, 14:24

    *cough* – how embarrrrrassing o_O. Should work again now :-) Thanks Paul!

  15. Pingback by Robert’s read: links and suggestions November 16th 2011 - Robert's talk Mi. 16. Nov 2011, 15:06

    […] Natural Object-Rotation with CSS3 3D […]

  16. Kommentar by Forkoff Fr. 3. Feb 2012, 13:08

    Firefox now supports 3D transforms as of Version 10

    x

  17. Kommentar by Stefan Fr. 2. Mär 2012, 14:00

    Hi there,

    We are currently using the cube and are trying to make the images clickable for browsers AND the iPad.
    It works for the browsers only not on the iPad, perhaps I’m doing it wrong. We’ve used the code:

    It seems the only thing clickable on the iPad is the borders, we’ve also removed the images but still you can’t click on the planes. It there anything that we can do to make the images clickable on the iPad?

    Cheers (from Holland)

  18. Kommentar by mauritius Mo. 19. Mär 2012, 05:32

    Awesome would really want to integrate this css 3d in one of my website but the browser compatibility issue really make this difficulf

  19. Pingback by GNC Designstudio Sa. 23. Jun 2012, 15:27

    […] Link: Dirk’s rotation example […]

  20. Kommentar by Star Mi. 4. Jul 2012, 03:51

    the coolest thing i’ve seen in my life .I think this the future of web technology.But ie please support on ie7 as well as ie8.I hope in the near future all browsers will automatically updates.With or without the consent from the owner.that would be awesome.I will apply your ideas into my website.hahah
    hope you will find it amusing
    http://linesforme.blogspot.com