5202

ein Blog über technische Fragen zu Blogger

3D Galerie ohne Javascript

von
Könnt ihr euch an die endlosen Stunden im Matheunterricht erinnern, wo ihr euch mit Tangenten, Matrizen und Vektoren rumgequält habt? Habt ihr euch je gefragt, zu was das wohl nützlich sei?

Heute die Antwort: Das braucht ihr, um coole 3D-Galerien zu bauen - schaut euch die Demo an, öffnet die Galerie mit Click auf den goldenen Layer.

Vorbemerkung

Mir ist klar, dass es anspruchsvoll ist, 3D Modelle zu bauen. Im Webdesign spielt 3D auch [noch] keine große Rolle - dafür war bis vor kurzem die Browserunterstützung einfach zu schlecht.

Inzwischen können alle Browser 3D, sogar der Internet Explorer ab 9+. Man kann 3D also bedenkenlos zumindest für dekorative Sachen einsetzen ... wenn man es beherrscht. Die Technik dahinter ist allerdings viel schwieriger in den Griff zu bekommen als 2D - zumindest geht mir das so.

Die Skizze hier ist es so etwas wie 'ein-erstes-nachdenken-über-3D'. Ich mag ja immer andere dazu motivieren, sich tiefer mit CSS zu beschäftigen und da ist 3D meiner Ansicht nach ein wahnsinnig spannendes Feld - einfach weil es da bisher sehr wenig gibt.

Diese 3D Gallerie hat mich alles zusammen knapp drei Wochen beschäftig ... und ist noch nicht mal besonders 'spektakulär'. Aber das Sahnehäubchen bekommt nur wer seinen Grießbrei schön fleissig löffelt, oder?

Let's rock: HTML

Ich erkläre die Skizze Schritt für Schritt - starten wir mit dem HTML.

Mittlerweile habe ich das ja schon mehrmals verwendet - zum vorwärts und rückwärts 'rollen' benütze ich einen Schalter mit input type="radio" Elementen.

Nehmen wir also zuerst neun input Element:
  <input id="box9" name="nav" type="radio" />   
  <input id="box8" name="nav" type="radio" />   
  <input id="box7" name="nav" type="radio" />  
  <input id="box6" name="nav" type="radio" />   
  <input id="box5" name="nav" type="radio" />   
  <input id="box4" name="nav" type="radio" />  
  <input id="box3" name="nav" type="radio" />   
  <input id="box2" name="nav" type="radio" />   
  <input id="box1" name="nav" type="radio" /> 
Warum ich das in umgekehrter Reihenfolge schreibe, erkläre ich gleich.

Unterhalb der neun input Elemente liegen die neun dazugehörigen label Elemente - innerhalb der Label liegen die img Tags. Alles zusammen wrapper ich in einem 'Rahmen', dem holder.
  <div class="holder">
  <input id="box9" name="nav" type="radio" />   
  <input id="box8" name="nav" type="radio" />   
  <input id="box7" name="nav" type="radio" />  
  <input id="box6" name="nav" type="radio" />   
  <input id="box5" name="nav" type="radio" />   
  <input id="box4" name="nav" type="radio" />  
  <input id="box3" name="nav" type="radio" />   
  <input id="box2" name="nav" type="radio" />   
  <input id="box1" name="nav" type="radio" /> 

    <div id="carousel">
     <label for="box9"><img src="http://goo.gl/cbB1B"/></label>
     <label for="box8"><img src="http://goo.gl/vdJud"/></label>
     <label for="box7"><img src="http://goo.gl/Ot1nv"/></label>
     <label for="box6"><img src="http://goo.gl/NYJyu"/></label>
     <label for="box5"><img src="http://goo.gl/uX7NY"/></label>
     <label for="box4"><img src="http://goo.gl/f8o7t"/></label>
     <label for="box3"><img src="http://goo.gl/Z979b"/></label>
     <label for="box2"><img src="http://goo.gl/wTB34"/></label>
     <label for="box1">Russische Ikonen</label>
   </div> 
  </div>
Das letzte Label wird das 'Deckblatt' für die Galerie - im 2D Ausgangszustand liegen alle Labels übereinander und das 'neunte' Blatt ist bei der Animation sozusagen das 'erste' Blatt ... deshalb das ganze in genau umgekehrter Reihenfolge.

An HTML wäre das ansonsten alles - ziemlich simpel, oder?

3D - kurze Vorüberlegung

Denkt mal an euren Zeichenunterricht zurück - eine dreidimensionale Perspektive braucht einen Standpunkt und einen Fluchtpunkt eines fiktive Beobachter, d.h. je nachdem, von wo ihr ein Objekt betrachtet, sieht es stark unterschiedlich aus, logisch oder?

Außerdem braucht jede dreidimensionale Darstellung eine Breite, eine Höhe und eine Tiefe. In mathematischer Sprache ist das die x-Achse, y-Achse und z-Achse.

CSS: Grundlage

Um das auf unsere Gallerie anzuwenden, geben wir zunächst dem 'Rahmen' - d.h. dem holder - eine perspective von 1000px - das wird der 'Abstand' des fiktiven Beobachters von der Gallerie.

Der Fluchtpunkt liegt in dem Fall - falls wir es nicht anders angeben - genau in der Mitte des 'Rahmens'.
.holder {
      width: 199px;
      height: 299px;
      position: absolute;
      left: 50%;
      perspective: 1000px;
    }
In dem Rahmen liegt das 'Karussell' - das machen wir mit preserve-3d fit für 3D, sprich, damit lassen sich die Elemente auch auf der Z-Achse (der Raumtiefe) transformieren.

Mit translateZ versetzen wir das 'Karussell'um 288px nach hinten - das gibt den 'Öffnungseffekt' beim anklicken des goldenen Layers.
  #carousel {
      width: 100%;
      height: 100%;
      position: absolute;
      transform: translateZ( -288px );
      transform-style: preserve-3d;
      transition: transform 1s;
    }
Zuletzt noch die Labels mit den img Tags - die bekommen a bisserl 2D Grundstyling:
  #carousel label {
      display: block;
      position: absolute;
      width: 200px;
      height: 300px;
      line-height: 300px;
      font-size: 20px;
      font-weight: bold;
      text-align: center;
      }
Das 'neunte' Element, d.h. das Deckblatt wird gesondert behandelt:
#carousel label:nth-child(9) { background: gold; color: #111 }
So weit so gut. Wenn ihr jetzt die Galerie anschaut, seht ihr im Grunde nur das 'Deckblatt' - alle anderen Blätter liegen darunter. Nicht besonders spektakulär, oder?

CSS: 3D Styling

Immer gut, wenn man sich ab und zu vor Augen hält, was man eigentlich will: Wir wollen eine 'Karussell' mit insgesamt neun Elementen. Dieses 'Karussell' soll sich auf der y-Achse bewegen, auf der x-Achse dagegen nicht.
y-Achse
Jedes dieser neun Elemente ist also auf einer Kreisbahn der y-Achse angeordnet. Ein ganzer Kreis hat einen Winkel von 360° - das geteilt durch neun ergibt 40°. Jedes Element steht zum nächsten Element also um 40° verschoben.
z-Achse
Die 'Raumtiefe', d.h. der Abstand der einzelnen Bilder zum Kreismittelpunkt bleibt immer gleich. Der Abstand lässt sich nun entweder pi mal Daumen und try und error bestimmen, oder ihr berechnet es.

Ich könnte es ausführlicher erklären oder ihr glaubt mir das, gesuchte Abstand r zum Kreismittelpunkt ist: r=(breite/2)/tan(20)

Dabei ist 'breite' die Breite eines einzelnen Elements. Bezogen auf unser Beispiel wäre der Abstand (bei einem perfektem geschlossenem Kreis) zum Kreismittelpunkt also 274px.
Beispiel
Damit das alles nicht so abstrakt bleibt, hier das Beispiel für die ersten drei Elemente:
 #carousel label:nth-child(1) {
              transform: rotateY(   0deg ) translateZ( 274px );
    }
#carousel label:nth-child(2) {
              transform: rotateY(   40deg ) translateZ( 274px );
    }
#carousel label:nth-child(3) {
              transform: rotateY(   80deg ) translateZ( 274px );
    }
...
Das erste Element hat auf der y-Achse gar keine Verschiebung, das zweite Element zum ersten Element eine Verschiebung um 40° und so weiter.

Die 'Raumtiefe', d.h. der Abstand zum Kreismittelpunkt bleibt immer gleich, nämlich 274px. Das ganze könnt ihr jetzt schreiben und ihr habt ein 'statisches' Karussel - nicht schlecht, aber noch immer nicht, was wir eigentlich wollen. Wie kriegen wir das ganze jetzt beweglich?
3D Bewegung
Um zu sehen, wie die Elemente bewegt werden, schauen wir uns das erste Element im Ausgangszustand an - das Element steht auf der y-Achse bei 0°.
 #carousel label:nth-child(1) {
              transform: rotateY(   0deg ) translateZ( 274px );
    }
Mit klick auf das erste Element verschieben wir es nun um 40°:
#box1:checked ~ #carousel label:nth-child(1) {
              transform: rotateY(   40deg ) translateZ( 310px );
    }
Damit das ganze schön 'smooth' passiert, legen wir eine transition drauf:
input:checked ~ #carousel label{
        transition: all 1s
}
Und fertig ist die Bewegung - der Rest ist nur noch Fleißarbeit:=).

Mit Click auf das zweite Label würde sich etwa das erste Element um 80° verschieben:
#box2:checked ~ #carousel label:nth-child(1) {
              transform: rotateY(   80deg ) translateZ( 310px );
    }
und so weiter ... schaut es euch an. Wenn ihr einmal das dahinter liegende Prinzipe verstanden habt, ist es nicht mehr besonders kompliziert.
Transparenz-Effekt
In der Gallerie hat nur das jeweils oberste Bild eine Deckkraft von 100% - alle anderen Bildern sind transparent - ich benütze dazu die sehr nützliche Eigenschaft not.
#box9:checked ~ #carousel label:not(:nth-child(1)) {
opacity: .1
}
#box8:checked ~ #carousel label:not(:nth-child(2)) {
opacity: .1
}
#box7:checked ~ #carousel label:not(:nth-child(3)) {
opacity: .1
}
#box6:checked ~ #carousel label:not(:nth-child(4)) {
opacity: .1
}
#box5:checked ~ #carousel label:not(:nth-child(5)) {
opacity: .1
}
#box4:checked ~ #carousel label:not(:nth-child(6)) {
opacity: .1
}
#box3:checked ~ #carousel label:not(:nth-child(7)) {
opacity: .1
}
#box2:checked ~ #carousel label:not(:nth-child(8)) {
opacity: .1
}
#box1:checked ~ #carousel label:not(:nth-child(9)) {
opacity: .1
}
Alle Bilder außer dem angeklickten Bild bekommen damit die Transparenz ".1".

Zusammenfassung

Das ist relativ harter Stoff - nix mal für so zwischendurch zum ausprobieren :=). Man muss sich einarbeiten, viel rumprobieren und vor allem selber machen, selber machen, selber machen.

Ich kann hier nur ein paar allgemeine Hinweise geben - eher grobe Brocken, nichts 'mundgerechtes'. Ich hoffe, das ist mal für jemand vom Nutzen ...

Anmerkungen

Fragen und Anmerkungen sind hier immer gerne gesehen ...