Leistungsstarke Parallelisierung

Paul Lewis
Robert Flack
Robert Flack

Ob egal oder ob du sie ungemütlich magst – Entspannung pur. Bei vernünftiger Anwendung kann dies einer Webanwendung Tiefe und Feinheiten verleihen. Das Problem ist jedoch, dass die leistungsstarke Parallaxe-Implementierung eine Herausforderung darstellen kann. In diesem Artikel beschäftigen wir uns mit einer Lösung, die sowohl leistungsstark als auch browserübergreifend funktioniert.

Parallaxe-Illustration.

Zusammenfassung

  • Verwende keine Scroll-Ereignisse oder background-position, um Parallaxe-Animationen zu erstellen.
  • Verwenden Sie CSS-3D-Transformationen, um einen genaueren Parallaxe-Effekt zu erzeugen.
  • Verwenden Sie für Mobile Safari position: sticky, damit der Parallaxe-Effekt übertragen wird.

Wenn Sie die Drop-in-Lösung verwenden möchten, rufen Sie das GitHub-Repository für UI Element Samples auf und laden Sie das Parallaxen-Hilfsprogramm-JS herunter. Im GitHub-Repository finden Sie eine Live-Demo des Parallaxe Scroller.

Problem Parallaxener

Sehen wir uns zuerst zwei gängige Methoden zum Erreichen eines Parallaxeneffekts an und insbesondere, warum sie für unsere Zwecke nicht geeignet sind.

Schlecht: Scroll-Ereignisse werden verwendet.

Die Hauptanforderung des Parallaxeneffekts besteht darin, dass es durch Scrollen gekoppelt ist. Bei jeder Änderung der Scrollposition der Seite sollte die Position des Parallaxe-Elements aktualisiert werden. 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 bereitgestellt. Es gibt also keine Garantie dafür, dass sie bei jedem Frame der Scroll-Animation ausgelöst werden.

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 Parallaxensystem mit der Scrollposition der Seite Schritt hält. In älteren Versionen von Mobile Safari wurden Scroll-Ereignisse erst am Ende des Scrollvorgangs ausgelöst. Daher war es nicht möglich, einen JavaScript-basierten Scroll-Effekt zu erzeugen. Neuere Versionen liefern während der Animation Scroll-Ereignisse, jedoch auf einer "Best-Effort-Basis", ähnlich wie bei Chrome. Wenn der Hauptthread mit anderen Aufgaben beschäftigt ist, werden Scroll-Ereignisse nicht sofort gesendet, wodurch der Parallaxe-Effekt verloren geht.

Fehlerhaft: background-position wird aktualisiert

Eine weitere Situation, die wir vermeiden möchten, ist das Malen auf jeden Frame. Viele Lösungen versuchen, background-position zu ändern, um den Parallaxe-Look zu schaffen. Dies führt dazu, dass der Browser die betroffenen Teile der Seite beim Scrollen neu gestaltet, was kostspielig genug sein kann, um die Animation erheblich zu verlangsamen.

Wenn wir das Versprechen der Parallaxe-Bewegung erfüllen möchten, benötigen wir etwas, das als beschleunigte Eigenschaft angewendet werden kann (was heute bedeutet, dass Transformationen und Deckkraft eingehalten werden) und die nicht auf Scroll-Ereignisse angewiesen ist.

CSS in 3D

Sowohl Scott Kellum als auch Keith Clark haben erhebliche Arbeit im Bereich der Verwendung von CSS 3D zur Umsetzung einer Parallaxe-Bewegung geleistet. Dabei setzen sie folgende Technik ein:

  • Richten Sie ein Element ein, das mit overflow-y: scroll (und wahrscheinlich overflow-x: hidden) scrollt.
  • Auf dasselbe Element wenden Sie einen perspective-Wert an und perspective-origin, der auf top left oder 0 0 gesetzt ist.
  • Wenden Sie auf die untergeordneten Elemente dieses Elements eine Übersetzung in Z an und skalieren Sie sie wieder nach oben, um eine Parallaxe-Bewegung zu erzeugen, ohne ihre Größe auf dem Bildschirm zu beeinträchtigen.

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 von einem HTML-Snippet wie diesem ausgegangen:

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

Maßstab für Perspektive anpassen

Wenn Sie das untergeordnete Element zurückschieben, wird es proportional zum Wert der Perspektive kleiner. Mit der folgenden Gleichung können Sie berechnen, wie hoch die Skalierung ist: (Perspektive – Entfernung) / Perspektive. Da wir höchstwahrscheinlich möchten, dass das Parallaxe-Element Parallaxe erzeugt, aber in der von uns erstellten Größe erscheint, müsste es auf diese Weise vergrößert werden, anstatt es so zu belassen.

Im Fall des obigen Codes beträgt die Perspektive 1px und der Z-Abstand von parallax-child -2px. Dies bedeutet, dass das Element um 3x hochskaliert werden muss. Wie Sie sehen, ist der Wert, der in den Code eingefügt wurde: scale(3).

Für alle Inhalte, auf die kein translateZ-Wert angewendet wurde, können Sie einen Wert von null ersetzen. Dies bedeutet, dass die Skala (perspective - 0) / perspective ist, was einen Wert von 1 ergibt, was bedeutet, dass sie weder nach oben noch nach unten skaliert wurde. Ziemlich praktisch.

Funktionsweise

Es ist wichtig, klar zu machen, warum dies funktioniert, da wir dieses Wissen in Kürze nutzen werden. Das Scrollen ist effektiv eine Transformation, weshalb es beschleunigt werden kann. Dabei werden hauptsächlich Schichten mit der GPU verschoben. Beim Scrollen, also beim Scrollen ohne Perspektive, findet beim Vergleich zwischen dem Scrollelement und seinen untergeordneten Elementen 1:1 statt. Wenn Sie ein Element um 300px nach unten scrollen, werden die untergeordneten Elemente um den gleichen Wert transformiert: 300px.

Das Anwenden eines Perspektivwerts auf das Scrollelement führt jedoch zu Problemen bei diesem Vorgang. Es werden die Matrizen geändert, die der Scroll-Transformation zugrunde liegen. Bei einem Scrollen von 300 Pixeln werden die untergeordneten Elemente jetzt abhängig von 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. Das Ergebnis: Parallaxe-Bewegung. 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: Safari auf dem Mobilgerät

Jeder Effekt hat Nachteile. Ein wichtiger Punkt bei Transformationen ist das Beibehalten von 3D-Effekten auf untergeordnete Elemente. Wenn es in der Hierarchie Elemente zwischen dem Element mit einer Perspektive und seinen Parallaxe-untergeordneten Elementen gibt, wird die 3D-Perspektive „abgeflacht“, d. h., der Effekt geht verloren.

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

Im obigen HTML-Code ist das .parallax-container neu und vereinfacht den perspective-Wert effektiv, wodurch der Parallaxe-Effekt verloren geht. Die Lösung ist in den meisten Fällen ziemlich einfach: Sie fügen dem Element transform-style: preserve-3d hinzu, wodurch alle 3D-Effekte (wie unser Perspektivwert) weitergegeben werden, die weiter oben in der Baumstruktur angewendet wurden.

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

Im Fall von Mobile Safari ist das Ganze jedoch etwas komplizierter. Das Anwenden von overflow-y: scroll auf das Containerelement funktioniert technisch, allerdings auf Kosten der Möglichkeit, das scrollbare Element zu betätigen. Die Lösung besteht darin, -webkit-overflow-scrolling: touch hinzuzufügen, aber dadurch wird auch perspective vereinfacht und es wird keine Parallaxe entstehen.

Im Hinblick auf Progressive Enhancement ist dies 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 Hilfe in Form von position: sticky. Damit können Elemente am oberen Rand des Darstellungsbereichs oder ein bestimmtes übergeordnetes Element fixiert werden, wenn der Nutzer scrollt. 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 Fixierung 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. Ähnlich wie im Beispiel für das Scrollen oben gibt es bei einer Berechnung des Offsets von 300 Pixeln also eine neue Möglichkeit, diesen 300-Pixel-Versatzwert mithilfe von Perspektiven (oder einer anderen Transformation) zu bearbeiten, bevor er auf fixierte Elemente angewendet wird.

Wenn position: -webkit-sticky auf das Parallaxenelement angewendet wird, kann der Abflachungseffekt von -webkit-overflow-scrolling: touch effektiv "umgekehrt" werden. Dadurch wird sichergestellt, dass das Parallaxe-Element mit einem Bildlauffeld auf den nächstgelegenen Ancestor verweist. In diesem Fall ist das .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 tolle Neuigkeiten ist.

Hinweise zur fixierten Positionierung

Es gibt hier jedoch einen Unterschied: position: sticky ändert die Parallaxenmechanik. Bei der fixierten Positionierung wird versucht, das Element am scrollbaren Container zu fixieren. Bei einer nicht fixierten Version ist dies nicht der Fall. Das bedeutet, dass die Parallaxe mit „Sticky“ auch der Kehrwert der ohne:

  • Bei position: sticky ist z=0 dem Element näher, umso weniger wird es verschoben.
  • Ohne position: sticky ist z = 0, je näher das Element z = 0, desto mehr wird es verschoben.

Wenn Ihnen das alles etwas abstrakt erscheint, sehen Sie sich diese Demo von Robert Flack an. Sie zeigt, wie sich Elemente mit und ohne fixierte Position verhalten. Um den Unterschied zu sehen, benötigen Sie Chrome Canary (zum Zeitpunkt der Erstellung dieses Artikels Version 56) oder Safari.

Screenshot: Parallaxe-Perspektive

Eine Demo von Robert Flack, die zeigt, wie position: sticky das Parallaxen-Scrollen beeinflusst.

Verschiedene Programmfehler und Behelfslösungen

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

  • Fixierte Unterstützung ist inkonsistent. In Chrome gibt es noch Unterstützung, in Edge gibt es keine Unterstützung und in Firefox werden Fehler gezeichnet, wenn Fixierung mit Perspektivumwandlung 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.
  • In Edge funktioniert der Effekt nicht einfach. Edge versucht, das Scrollen auf Betriebssystemebene zu verarbeiten, was im Allgemeinen eine gute Sache ist. In diesem Fall verhindert es jedoch, dass die Perspektiveänderungen während des Scrollens erkannt werden. Um dies zu beheben, können Sie ein Element mit fester Position hinzufügen, da dadurch Edge zu einer Scrollmethode außerhalb des Betriebssystems zu wechseln scheint. Dadurch werden Änderungen der Perspektive berücksichtigt.
  • „Der Inhalt der Seite ist einfach riesig!“ Viele Browser berücksichtigen diese Größenordnung bei der Entscheidung, wie groß der Seiteninhalt ist. Leider berücksichtigen Chrome und Safari die Perspektive jedoch nicht. Wenn also z. B. eine Skala von 3x auf ein Element angewendet wird, werden möglicherweise Bildlaufleisten und Ähnliches angezeigt, selbst wenn das Element nach Anwendung von perspective bei 1x ist. Sie können dieses Problem umgehen, indem Sie Elemente von der unteren rechten Ecke aus skalieren (mit transform-origin: bottom right). Dies funktioniert, da übergroße Elemente in den "negativen Bereich" (in der Regel oben links) des scrollbaren Bereichs wachsen. Bei scrollbaren Regionen können Sie niemals Inhalte im negativen Bereich sehen oder dorthin scrollen.

Fazit

Die Parallaxe macht Spaß, wenn sie wohlüberlegt eingesetzt wird. Wie Sie sehen, ist es möglich, sie so zu implementieren, dass sie leistungsfähig, mit Scroll-Unterstützung und browserübergreifend genutzt werden kann. Da dies ein wenig mathematisches Wriggling und ein wenig Boilerplate erfordert, um den gewünschten Effekt zu erzielen, haben wir eine kleine Hilfsbibliothek und ein Beispiel eingebunden, die Sie in unserem GitHub-Repository für UI-Elemente-Beispiele finden.

Lass uns wissen, wie es dir gefällt.