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. Pingback by 35 Best HTML5 and CSS3 3D Examples with Demo Mi. 22. Aug 2012, 07:05

    […] Visit tutorial […]

  2. Kommentar by Wolden Di. 2. Okt 2012, 18:13

    Hi :)

    Nice stuff & nice effect! I’m trying to connect your magic 3d box with the gyroscope datas of the pad (x,y,z) instead of finger position to obtain something like like a pseudo cam (the box seems to stay fixed in space keeping its right perspectives when the pad moves) but I cant understand how to do this because the movement is waiting for a touch on screen to be initiated. I suppose that it would be easy to remove the touch events and replace xTouch/yTouch by the gyro datas, but I’m afraid that the object-supposed-to-be-clicked will be hard to define? any idea ?