Leistungsstarke Parallelisierung

Paul Lewis
Robert Flack
Robert Flack

Ob parallaxiert – ob parallaxiert oder glamourös. Bei überlegter Verwendung kann eine Webanwendung mehr Tiefe und Subtilität verleihen. Das Problem besteht jedoch darin, dass die leistungsstarke Implementierung von Parallaxing eine Herausforderung sein kann. In diesem Artikel stellen wir eine Lösung vor, die sowohl leistungsstark als auch browserübergreifend funktioniert.

Parallele Illustration

Kurzfassung

  • Scroll-Ereignisse oder background-position dürfen nicht verwendet werden, um parallax-Animationen zu erstellen.
  • Verwenden Sie CSS-3D-Transformationen, um einen präziseren Parllax-Effekt zu erzielen.
  • Verwenden Sie für Mobile Safari position: sticky, damit der Parllax-Effekt übertragen wird.

Wenn Sie sich für die Drop-in-Lösung entscheiden möchten, rufen Sie das GitHub-Repository für UI-Elementbeispiele auf und laden Sie das Tool für die synchrone Darstellung von Elementen in JS herunter. Im GitHub-Repository können Sie sich eine Live-Demo des Parllax-Scrollers ansehen.

Parallaxer für Probleme

Sehen wir uns zunächst zwei gängige Methoden zum Erreichen eines Parllax-Effekts an und warum sie für unsere Zwecke ungeeignet sind.

Schlecht: Scroll-Ereignisse werden verwendet.

Die wichtigste Anforderung bei der Paraxierung ist, dass sie gescrollt werden muss. Bei jeder einzelnen Änderung der Scrollposition der Seite sollte die Position des parallaxing-Elements aktualisiert werden. Das klingt zwar einfach, doch 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 als Best-Effort-Ereignisse ausgeliefert, nicht bei jedem Frame der Scroll-Animation.

Diese wichtige Information verdeutlicht, warum wir eine auf JavaScript basierende Lösung vermeiden sollten, die Elemente basierend auf Scroll-Ereignissen verschiebt: JavaScript garantiert nicht, dass das Parallaxing mit der Scroll-Position der Seite mithalten kann. In älteren Versionen von Mobile Safari wurden Scroll-Ereignisse tatsächlich am Ende des Scrollvorgangs eingeblendet. Dadurch war es unmöglich, einen JavaScript-basierten Scrolleffekt zu erzielen. Neuere Versionen stellen während der Animation Scroll-Ereignisse bereit, jedoch, ähnlich wie bei Chrome, auf Best-Effort-Basis. Wenn der Hauptthread mit anderen Aufgaben überlastet ist, werden Scroll-Ereignisse nicht sofort ausgegeben, d. h., der Parllax-Effekt geht verloren.

Schlecht: background-position wird aktualisiert

Eine weitere Situation, die wir vermeiden möchten, ist das Streichen auf jedem Frame. Bei vielen Lösungen wird versucht, die background-position so zu ändern, dass sie parallax aussieht. Dies führt dazu, dass der Browser die betroffenen Teile der Seite beim Scrollen neu darstellt. Dies kann kostspielig sein und zu einer erheblichen Verzögerung der Animation führen.

Wenn wir das Versprechen der Parallaxbewegung erfüllen möchten, brauchen wir etwas, das als beschleunigte Eigenschaft verwendet werden kann (was heute bedeutet, dass wir an Transformationen und Deckkraft bleiben müssen), und die nicht auf Scroll-Ereignissen basiert.

CSS in 3D

Sowohl Scott Kellum als auch Keith Clark haben umfangreiche Arbeit in Bezug auf die Verwendung von CSS 3D zur Erzielung einer Parllax-Bewegung geleistet. Die von ihnen eingesetzte Technik ist effektiv:

  • Richten Sie ein Containerelement zum Scrollen mit overflow-y: scroll (und wahrscheinlich overflow-x: hidden) ein.
  • Wenden Sie auf dasselbe Element einen perspective-Wert und einen perspective-origin-Wert an, der auf top left oder 0 0 festgelegt ist.
  • Wenden Sie für die untergeordneten Elemente dieses Elements eine Übersetzung in Z an und skalieren Sie sie wieder nach oben, um eine Rectangleax-Bewegung zu erzeugen, ohne ihre Größe auf dem Bildschirm zu beeinflussen.

Der CSS-Code 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 vorausgesetzt, dass ein HTML-Snippet wie folgt gilt:

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

Skalierung für Perspektive anpassen

Wenn Sie das untergeordnete Element zurückschieben, wird es proportional zum Perspektivewert kleiner. Mit der folgenden Gleichung können Sie berechnen, wie hoch der Wert hochskaliert werden muss: (Perspektive – Entfernung) ÷ Perspektive. Da wir höchstwahrscheinlich möchten, dass das parallaxierende Element in der von uns erstellten Größe erscheint, würde es auf diese Weise vergrößert werden, anstatt unverändert zu bleiben.

Im Fall des obigen Codes ist die Perspektive 1px und der Z-Abstand der parallax-child beträgt -2px. Das Element muss also um 3x vertikal skaliert werden. Wie Sie sehen, ist der Wert in den Code eingebunden: scale(3).

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

Funktionsweise

Es ist wichtig, zu verdeutlichen, warum das funktioniert, da wir dieses Wissen bald nutzen werden. Das Scrollen ist im Grunde eine Transformation, die beschleunigt werden kann. Meistens werden dafür Ebenen mit der GPU verschoben. Beim Scrollen, also bei einem Scrollen ohne Perspektive, erfolgt das Scrollen 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 Wert nach oben umgewandelt: 300px.

Wenn Sie jedoch einen perspektivischen Wert auf das Scroll-Element anwenden, beeinträchtigt dies den Prozess. Dadurch werden die Matrizen geändert, die der Scroll-Transformation zugrunde liegen. Jetzt werden die untergeordneten Elemente beim Scrollen mit 300 Pixeln möglicherweise nur um 150 Pixel verschoben, abhängig von den von Ihnen ausgewählten perspective- und translateZ-Werten. Wenn ein Element einen translateZ-Wert von 0 hat, wird es wie zuvor mit 1:1 gescrollt, aber ein untergeordnetes Element, das in Z vom perspektivischen Ursprung weggeschoben wird, wird mit einer anderen Geschwindigkeit gescrollt. Nettoergebnis: parallaxförmige Bewegung. Das ist besonders wichtig, da dies als Teil des internen Scrollingsystems des Browsers automatisch gehandhabt wird, d. h., es ist nicht nötig, auf scroll-Ereignisse zu warten oder background-position zu ändern.

Eine Fliege in der Salbe: Mobile Safari

Jeder Effekt hat Nachteile. Ein wichtiger Punkt für Transformationen ist die Beibehaltung von 3D-Effekten für untergeordnete Elemente. Wenn sich in der Hierarchie zwischen dem Element mit einer Perspektive und den zugehörigen untergeordneten Elementen Elemente befinden, 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 HTML-Code oben ist .parallax-container neu. Er vereinfacht den perspective-Wert effektiv, wodurch der Parllax-Effekt verloren geht. Die Lösung ist in den meisten Fällen ziemlich einfach: Fügen Sie dem Element transform-style: preserve-3d hinzu, damit alle 3D-Effekte (wie unser perspektivischer Wert) wirksam werden, die weiter oben in der Baumstruktur angewendet wurden.

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

Im Fall von Mobile Safari sieht die Sache jedoch ein wenig komplizierter aus. Das Anwenden von overflow-y: scroll auf das Containerelement funktioniert zwar technisch, allerdings auf Kosten dafür, dass das Scrollelement verschoben werden kann. Die Lösung besteht darin, -webkit-overflow-scrolling: touch hinzuzufügen. Dabei wird perspective aber auch reduziert und es gibt keine Parallierung.

Im Hinblick auf Progressive Enhancement ist dies wahrscheinlich kein allzu großes Problem. Auch wenn wir nicht in jeder Situation die Parillax-Funktion verwenden können, funktioniert unsere App zwar, es wäre jedoch schön, eine Behelfslösung zu finden.

position: sticky kommt zur Rettung!

Tatsächlich gibt es auch Hilfe in Form von position: sticky. Damit können Elemente beim Scrollen oben im Darstellungsbereich oder an einem bestimmten übergeordneten Element „fixiert“ werden. Wie die meisten von ihnen ist auch die Spezifikation ziemlich umfangreich, enthält aber ein nützliches kleines Edelstein:

Auf den ersten Blick mag dies nicht sehr bedeuten. Ein wichtiger Punkt in diesem Satz ist jedoch, wie genau die Fixierung eines Elements berechnet wird: "Der Offset wird unter Bezug auf den nächsten Ancestor mit einem Bildlauffeld berechnet". Mit anderen Worten: Die Entfernung zum Verschieben des fixierten Elements (damit es an ein anderes Element oder den Darstellungsbereich angehängt erscheint) wird vor der Anwendung anderer Transformationen und nicht nach berechnet. Wenn der Offset bei 300 Pixeln berechnet wird, bedeutet dies, wie im obigen Scroll-Beispiel, eine neue Möglichkeit, den 300-Pixel-Offset-Wert mithilfe von Perspektiven (oder einer anderen Transformation) zu bearbeiten, bevor er auf fixierte Elemente angewendet wird.

Durch Anwenden von position: -webkit-sticky auf das Parallaxing-Element kann der Abflachungseffekt von -webkit-overflow-scrolling: touch effektiv "umgekehrt" werden. Dadurch wird sichergestellt, dass das parallaxing-Element auf den nächsten Ancestor mit einem Bildlauffeld 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 Parllax-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 in Mobile Safari der Parllax-Effekt wiederhergestellt, der immer wieder tolle Neuigkeiten bietet.

Einschränkungen bei der Positionierung

Allerdings gibt es hier einen Unterschied: position: sticky ändert die Parallelmechanik. Bei der fixierten Position wird versucht, das Element am Scroll-Container zu fixieren, im Gegensatz zu einer nicht fixierten Version. Dies bedeutet, dass die parallaxe Platte mit einer fixierten Fläche die Umkehrung derjenigen ohne:

  • Bei position: sticky gilt: Je näher das Element an z=0 liegt, desto weniger wird es verschoben.
  • Ohne position: sticky gilt: Je näher das Element an z=0 liegt, desto mehr wird es verschoben.

Wenn Ihnen dies ein wenig abstrakt erscheint, sehen Sie sich diese Demo von Robert Flack an, die zeigt, wie sich Elemente mit und ohne fixierte Positionierung unterscheiden. Um den Unterschied zu sehen, benötigen Sie Chrome Canary (zum Zeitpunkt der Erstellung dieses Dokuments Version 56) oder Safari.

Screenshot der Parallelperspektive

Ein Demo von Robert Flack, in dem gezeigt wird, wie sich position: sticky auf das Parllax-Scrollen auswirkt.

Verschiedene Programmfehler und Behelfslösungen

Wie immer gibt es jedoch noch Knoten und Erhebungen, die geglättet werden müssen:

  • Die dauerhafte Unterstützung ist inkonsistent. Chrome wird noch unterstützt, Edge wird nicht vollständig unterstützt und in Firefox treten Fehler beim Malen auf, wenn „Sticky“ mit perspektivischen Transformationen kombiniert wird. In solchen Fällen empfiehlt es sich, ein wenig Code hinzuzufügen, um position: sticky (die Version mit dem -webkit--Präfix) nur dann einzufügen, wenn es erforderlich ist. Dies gilt nur für Mobile Safari.
  • Der Effekt funktioniert nicht in Edge. Edge versucht, das Scrollen auf Betriebssystemebene auszuführen, was im Allgemeinen eine gute Sache ist. In diesem Fall wird jedoch verhindert, dass die Perspektivenänderungen während des Scrollens erkannt werden. Um dies zu beheben, können Sie ein Element mit fester Position hinzufügen, da dies Edge zu einer Scrollmethode ohne Betriebssystem zu wechseln scheint und dafür sorgt, dass perspektivische Änderungen berücksichtigt werden.
  • „Die Seite ist riesig.“ Viele Browser berücksichtigen bei der Entscheidung, wie groß der Inhalt einer Seite ist, die Größenordnung, aber leider berücksichtigen Chrome und Safari die Perspektive nicht. Wenn also beispielsweise eine Skala von 3x auf ein Element angewendet wird, sehen Sie möglicherweise Bildlaufleisten und Ähnliches, auch wenn sich das Element nach dem Anwenden von perspective bei „1x“ befindet. 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" (normalerweise oben links) des scrollbaren Bereichs erweitert werden. In scrollbaren Regionen können Sie niemals Inhalte im negativen Bereich sehen oder dorthin scrollen.

Fazit

Wenn Sie mit Bedacht vorgehen, ist die Parallelisierung ein witziger Effekt. Es ist möglich, sie leistungsfähig, scrollbar und browserübergreifend zu implementieren. Da hierfür ein gewisses Maß an mathematischem Wriggling und eine geringe Menge an Textbausteinen erforderlich sind, um den gewünschten Effekt zu erzielen, haben wir eine kleine Hilfsbibliothek und ein Beispiel zusammengefasst, die Sie in unserem GitHub-Repository für UI-Elementbeispiele finden.

Spiele und teile uns mit, wie es dir ergangen ist.