Leistungsstarke Parallelisierung

Paul Lewis
Robert Flack
Robert Flack

Ob egal oder ob du sie ungemütlich magst – Entspannung pur. Bei pfiffiger Verwendung kann sie einer Webanwendung Tiefe und Raffinesse verleihen. Das Problem ist jedoch, dass die Implementierung von Parallaxen auf leistungsstarke Weise eine Herausforderung sein kann. In diesem Artikel besprechen wir eine Lösung, die sowohl leistungsstark als auch plattformübergreifend funktioniert.

Parallaxe-Illustration.

Zusammenfassung

  • Verwenden Sie keine Scroll-Ereignisse oder background-position, um Parallaxe-Animationen zu erstellen.
  • Verwenden Sie CSS-3D-Transformationen, um einen genaueren Parallaxeeffekt zu erzielen.
  • Verwenden Sie für Safari auf Mobilgeräten position: sticky, damit der Parallaxeeffekt übernommen wird.

Wenn Sie die Lösung einfügen möchten, rufen Sie das GitHub-Repository für UI-Element-Beispiele auf und laden Sie den Parallax-Hilfs-JS herunter. Im GitHub-Repository finden Sie eine Live-Demo des Parallaxe Scroller.

Problem-Parallaxener

Sehen wir uns zuerst zwei gängige Methoden zum Erzielen eines Parallaxeeffekts an und warum sie für unsere Zwecke ungeeignet sind.

Schlecht: Scroll-Ereignisse verwenden

Die Hauptanforderung an die Parallaxe ist, dass sie mit dem Scrollen gekoppelt sein sollte. Bei jeder Änderung der Scrollposition der Seite sollte sich die Position des Parallaxe-Elements aktualisieren. Das klingt zwar einfach, aber ein wichtiger Mechanismus moderner Browser ist ihre Fähigkeit, asynchron zu arbeiten. Das gilt in unserem speziellen Fall für Scroll-Ereignisse. In den meisten Browsern werden Scroll-Ereignisse auf Best-Effort-Basis gesendet und nicht garantiert für jeden Frame der Scroll-Animation.

Diese wichtige Information verrät uns, warum wir auf eine JavaScript-basierte Lösung verzichten müssen, die Elemente basierend auf Scroll-Ereignissen verschiebt: JavaScript garantiert nicht, dass das Parallaxeneffekt die Scrollposition der Seite einhält. In älteren Versionen von Safari für Mobilgeräte wurden Scroll-Ereignisse erst am Ende des Scrollvorgangs ausgeführt. Daher war es nicht möglich, einen JavaScript-basierten Scroll-Effekt zu erzeugen. In neueren Versionen werden während der Animation Scroll-Ereignisse gesendet, aber ähnlich wie in Chrome auf der Grundlage des Best-Effort-Prinzips. Wenn der Hauptthread mit anderen Aufgaben beschäftigt ist, werden Scroll-Ereignisse nicht sofort gesendet. Der Parallaxeeffekt geht verloren.

Fehler: background-position wird aktualisiert

Außerdem sollten wir nicht in jedem Frame malen. Bei vielen Lösungen wird versucht, background-position zu ändern, um den Parallax-Look zu erzielen. Dadurch werden die betroffenen Bereiche der Seite beim Scrollen vom Browser neu gemalt. Das kann so kostspielig sein, dass die Animation erheblich ruckelt.

Wenn wir das Versprechen der Parallaxe-Bewegung einhalten möchten, brauchen wir etwas, das als beschleunigte Eigenschaft angewendet werden kann (was heute bedeutet, dass wir uns auf Transformationen und Deckkraft beschränken müssen) und nicht auf Scroll-Ereignisse angewiesen ist.

CSS in 3D

Sowohl Scott Kellum als auch Keith Clark haben wichtige Beiträge zur Verwendung von CSS 3D zur Erstellung von Parallaxe-Bewegungen geleistet. Dabei wird folgende Technik verwendet:

  • Richten Sie ein enthaltendes Element ein, das mit overflow-y: scroll (und wahrscheinlich auch mit overflow-x: hidden) gescrollt werden kann.
  • Wenden Sie auf dasselbe Element einen perspective-Wert und ein perspective-origin-Attribut mit dem Wert top left oder 0 0 an.
  • Wenden Sie auf die untergeordneten Elemente dieses Elements eine Verschiebung in Z an und skalieren Sie sie wieder auf, um eine Parallaxenbewegung zu erzielen, ohne ihre Größe auf dem Bildschirm zu beeinflussen.

Das CSS für diesen Ansatz sieht so aus:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

Dabei wird davon ausgegangen, dass ein HTML-Snippet wie dieses verwendet wird:

<div class="container">
    <div class="parallax-child"></div>
</div>

Maßstab für Perspektive anpassen

Wenn Sie das untergeordnete Element nach hinten schieben, wird es proportional zum Wert der Perspektive kleiner. Mit der folgenden Gleichung können Sie berechnen, wie stark es skaliert werden muss: (Perspektive – Entfernung) ÷ Perspektive. Da wir das Paralax-Element höchstwahrscheinlich in der Größe anzeigen möchten, in der wir es erstellt haben, muss es auf diese Weise vergrößert werden, anstatt es so zu lassen, wie es ist.

Im obigen Code ist die Perspektive 1px und der Z-Abstand von parallax-child ist –2px. Das Element muss also um 3 × skaliert werden. Das ist der Wert, der in den Code eingefügt wird: scale(3).

Für alle Inhalte, auf die kein translateZ-Wert angewendet wurde, können Sie einen Wert von null ersetzen. Das bedeutet, dass die Skalierung (Perspektive – 0) ÷ Perspektive ist, was zu einem Wert von 1 führt. Das Objekt wurde also weder vergrößert noch verkleinert. Ziemlich praktisch.

Funktionsweise

Es ist wichtig zu verstehen, warum das funktioniert, da wir dieses Wissen gleich anwenden werden. Das Scrollen ist im Grunde eine Transformation, weshalb es beschleunigt werden kann. Dabei werden hauptsächlich Ebenen mit der GPU verschoben. Bei einem typischen Scrollen ohne Perspektive geschieht das Scrollen im Verhältnis 1:1, wenn das Scrollelement und seine untergeordneten Elemente verglichen werden. Wenn Sie ein Element um 300px nach unten scrollen, werden seine untergeordneten Elemente um denselben Betrag nach oben transformiert: 300px.

Wenn Sie jedoch einen Perspektivwert auf das scrollbare Element anwenden, wird dieser Vorgang gestört. Die Matrizen, die der Scrolltransformation zugrunde liegen, werden geändert. Jetzt werden die untergeordneten Elemente bei einem Scrollen von 300 Pixeln je nach den ausgewählten Werten für perspective und translateZ möglicherweise nur um 150 Pixel verschoben. Wenn ein Element den translateZ-Wert 0 hat, wird es wie gewohnt bei 1:1 gescrollt, aber ein untergeordnetes Element, das in Z weg vom perspektivischen Ursprung geschoben wird, wird mit einer anderen Geschwindigkeit gescrollt. Endergebnis: Parallaxenbewegung. Das Wichtigste ist, dass dies automatisch als Teil der internen Scroll-Maschine des Browsers gehandhabt wird. Sie müssen also weder auf scroll-Ereignisse warten noch background-position ändern.

Eine Fliege: Mobile Safari

Bei jedem Effekt gibt es Einschränkungen. Eine wichtige Einschränkung für Transformationen betrifft die Beibehaltung von 3D-Effekten für untergeordnete Elemente. Wenn sich zwischen dem Element mit einer Perspektive und seinen Kindern mit Parallaxe Elemente in der Hierarchie befinden, wird die 3D-Perspektive „abgeflattet“, d. h., der Effekt geht verloren.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

In der obigen HTML-Datei ist .parallax-container neu. Dadurch wird der Wert perspective effektiv abgeflacht und der Parallaxeneffekt geht verloren. Die Lösung ist in den meisten Fällen recht einfach: Fügen Sie dem Element transform-style: preserve-3d hinzu, damit alle 3D-Effekte (z. B. der Wert für die Perspektive) übernommen werden, die weiter oben im Stammbaum angewendet wurden.

.parallax-container {
  transform-style: preserve-3d;
}

Bei Safari für Mobilgeräte ist es jedoch etwas komplizierter. Das Anwenden von overflow-y: scroll auf das Containerelement funktioniert zwar technisch, aber das Scrollelement kann dann nicht mehr „geschleudert“ werden. Die Lösung besteht darin, -webkit-overflow-scrolling: touch hinzuzufügen. Dadurch wird jedoch auch die perspective abgeflacht und es kommt zu keiner Parallaxe.

Aus Sicht der progressiven Verbesserung ist das wahrscheinlich kein allzu großes Problem. Wenn wir nicht in jeder Situation eine Parallaxe durchführen können, wird unsere App trotzdem funktionieren, aber es wäre schön, eine Behelfslösung zu finden.

position: sticky zur Rettung!

Es gibt tatsächlich eine gewisse Hilfe in Form von position: sticky, mit der Elemente beim Scrollen oben im Darstellungsbereich oder in einem bestimmten übergeordneten Element „angeklebt“ werden können. Die Spezifikation ist, wie die meisten von ihnen, ziemlich umfangreich, enthält aber ein nützliches kleines Juwel:

Auf den ersten Blick scheint dies nicht allzu viel zu bedeuten. In diesem Satz ist jedoch wichtig, wie genau die „Stickiness“ eines Elements berechnet wird: „Der Offset wird in Bezug auf den nächsten Vorgänger mit einem Bildlauffeld berechnet“. Mit anderen Worten: Die Entfernung, um das fixierte Element zu verschieben, damit es an ein anderes Element oder den Darstellungsbereich angehängt erscheint, wird berechnet, bevor andere Transformationen angewendet werden, nicht nach. Das bedeutet, dass Sie, ähnlich wie beim Scrollen oben, den Wert von 300 Pixeln, der für den Offset berechnet wurde, mithilfe von Perspektiven oder anderen Transformationen ändern können, bevor er auf anklickbare Elemente angewendet wird.

Wenn wir position: -webkit-sticky auf das Element mit dem Parallaxeneffekt anwenden, können wir den flachen Effekt von -webkit-overflow-scrolling: touch effektiv „umkehren“. So wird sichergestellt, dass das Paralax-Element auf den nächsten übergeordneten Knoten mit einem scrollbaren Feld verweist, in diesem Fall .container. Dann wendet .parallax-container ähnlich wie zuvor einen perspective-Wert an, der den berechneten Scroll-Offset ändert und einen Parallaxe-Effekt erzeugt.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

Dadurch wird der Parallaxe-Effekt für Mobile Safari wiederhergestellt, was für immer eine hervorragende Nachricht ist.

Hinweise zur fixierten Positionierung

Es gibt jedoch einen Unterschied: position: sticky ändert die Parallaxemechanik. Bei der fixen Positionierung wird versucht, das Element am scrollbaren Container zu fixieren. Bei einer nicht fixierten Version ist das nicht der Fall. Das bedeutet, dass die Parallaxe mit fixierten Enden das Gegenteil der Parallaxe ohne fixierte Enden ist:

  • Bei position: sticky bewegt sich das Element je weniger, je näher es an z=0 liegt.
  • Ohne position: sticky bewegt sich das Element je mehr, je näher es an z=0 liegt.

Wenn Ihnen das alles etwas abstrakt erscheint, sehen Sie sich diese Demo von Robert Flack an. Darin wird gezeigt, wie sich Elemente mit und ohne „Sticky“-Positionierung verhalten. Um den Unterschied zu sehen, benötigen Sie Chrome Canary (derzeit Version 56) oder Safari.

Screenshot mit Parallaxe-Perspektive

Demo von Robert Flack, die zeigt, wie sich position: sticky auf das Parallax-Scrolling auswirkt

Verschiedene Fehler und Problemumgehungen

Wie bei jedem anderen müssen jedoch auch Unebenheiten beseitigt werden:

  • Fixierte Unterstützung ist inkonsistent. In Chrome wird die Unterstützung noch implementiert, in Edge gibt es keine Unterstützung und in Firefox treten Fehler beim Zeichnen auf, wenn „sticky“ mit perspektivischen Transformationen kombiniert wird. In solchen Fällen empfiehlt es sich, einen kleinen Code hinzuzufügen, um position: sticky (die Version mit dem Präfix -webkit-) nur dann hinzuzufügen, wenn dies erforderlich ist. Dies gilt nur für Mobile Safari.
  • Der Effekt funktioniert nicht einfach so in Edge. Edge versucht, das Scrollen auf Betriebssystemebene zu steuern. Das ist in der Regel eine gute Sache, verhindert aber in diesem Fall, dass die Perspektive während des Scrollens erkannt wird. Sie können das Problem beheben, indem Sie ein Element mit fester Position hinzufügen. Dadurch wird Edge auf eine nicht vom Betriebssystem bereitgestellte Scrollmethode umgestellt und es werden Perspektivänderungen berücksichtigt.
  • „Der Inhalt der Seite ist jetzt riesig!“ Viele Browser berücksichtigen die Skalierung bei der Entscheidung, wie groß die Inhalte der Seite sein sollen. Chrome und Safari berücksichtigen jedoch nicht die Perspektive. Wenn also beispielsweise eine 3-fache Skalierung auf ein Element angewendet wird, sehen Sie möglicherweise Scrollbalken und ähnliches, auch wenn das Element nach der Anwendung von perspective eine 1-fache Skalierung hat. Sie können dieses Problem umgehen, indem Sie Elemente rechts unten (mit transform-origin: bottom right) skalieren. Dadurch werden zu große Elemente in den „negativen Bereich“ (in der Regel oben links) des scrollbaren Bereichs verschoben. In scrollbaren Bereichen können Sie Inhalte im negativen Bereich nie sehen oder dorthin scrollen.

Fazit

Parallaxe-Effekte können bei entsprechender Verwendung sehr ansprechend sein. Wie Sie sehen, ist es möglich, sie so zu implementieren, dass sie leistungsfähig, mit Scrollen kompatibel und browserübergreifend funktioniert. Da es ein wenig mathematisches Einfallsreichtum und ein wenig Boilerplate-Code erfordert, um den gewünschten Effekt zu erzielen, haben wir eine kleine Hilfsbibliothek und ein Beispiel zusammengestellt, die Sie in unserem GitHub-Repository für UI-Element-Beispiele finden.

Probieren Sie es aus und lassen Sie uns wissen, wie es funktioniert.