Ob Sie sie mögen oder nicht: Parallaxeneffekte sind nicht mehr wegzudenken. 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.
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 Paralax-Scrollers.
Problemparallaxen
Sehen wir uns zuerst zwei gängige Methoden zum Erzielen eines Parallaxeeffekts an und warum sie für unsere Zwecke nicht geeignet 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. In unserem Fall gilt das 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 erklärt, warum wir eine JavaScript-basierte Lösung vermeiden müssen, die Elemente basierend auf Scroll-Ereignissen bewegt: JavaScript kann nicht garantieren, dass die Parallaxe mit der Scrollposition der Seite Schritt hält. In älteren Versionen von Mobile Safari wurden Scroll-Ereignisse erst am Ende des Scrollens gesendet, was einen JavaScript-basierten Scrolleffekt unmöglich machte. 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 ressourcenintensiv 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 das 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 auchoverflow-x: hidden
) gescrollt werden kann. - Wenden Sie auf dasselbe Element einen
perspective
-Wert und einperspective-origin
-Attribut mit dem Werttop left
oder0 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 dreimal vergrößert werden. Das ist der Wert, der in den Code eingefügt wird: scale(3)
.
Für Inhalte, für die kein translateZ
-Wert festgelegt ist, können Sie den Wert „0“ einfügen. 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. Das ist wirklich praktisch.
Funktionsweise dieses Ansatzes
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 mit seinen untergeordneten Elementen verglichen wird.
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 Wert „0“ für translateZ
hat, wird es wie gewohnt mit einem Seitenverhältnis von 1:1 gescrollt. Ein untergeordnetes Element, das in Z vom Ursprung der Perspektive weggeschoben wurde, wird jedoch mit einer anderen Geschwindigkeit gescrollt. Endergebnis: Parallaxenbewegung. Und was sehr wichtig ist: Dieser Vorgang wird automatisch als Teil der internen Scrollmechanismen des Browsers ausgeführt. Es ist also nicht erforderlich, auf scroll
-Ereignisse zu warten oder background-position
zu ändern.
Ein Wermutstropfen: Safari auf Mobilgeräten
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 der Perspektive und seinen Kindern mit Parallaxe Elemente in der Hierarchie befinden, wird die 3D-Perspektive „zusammengefaltet“, 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 die Parallaxe nicht in jeder Situation nutzen können, funktioniert unsere App trotzdem. Es wäre aber schön, eine Lö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 anderen ziemlich umfangreich, enthält aber ein hilfreiches kleines Juwel:
Auf den ersten Blick mag das nicht viel bedeuten, aber ein wichtiger Punkt in diesem Satz bezieht sich darauf, wie genau die Haftbarkeit eines Elements berechnet wird: „Der Offset wird bezogen auf den nächsten übergeordneten Knoten mit einem scrollbaren Feld berechnet.“ Mit anderen Worten: Der Abstand, um das anpinnende Element zu verschieben, damit es an einem anderen Element oder am Darstellungsbereich angedockt erscheint, wird vor dem Anwenden anderer Transformationen berechnet, nicht danach. 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 wird, ähnlich wie zuvor, über .parallax-container
ein perspective
-Wert angewendet, wodurch sich der berechnete Scroll-Offset ändert und ein Parallaxeeffekt entsteht.
<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 Parallaxeeffekt für Safari auf Mobilgeräten wiederhergestellt.
Hinweise zur fixierten Positionierung
Es gibt jedoch einen Unterschied: position: sticky
ändert die Parallaxemechaniken. 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.
Demo von Robert Flack, die zeigt, wie sich position: sticky
auf das Parallax-Scrolling auswirkt
Verschiedene Fehler und Problemumgehungen
Wie bei allem gibt es jedoch noch einige Stolpersteine, die ausgeräumt werden müssen:
- Die Unterstützung für angepinnte Elemente 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 lohnt es sich, einen kleinen Code hinzuzufügen, um
position: sticky
(die Version mit dem Präfix-webkit-
) nur dann hinzuzufügen, wenn sie benötigt wird. Das gilt nur für die 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. Leider berücksichtigen Chrome und Safari die Perspektive nicht. 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 (mittransform-origin: bottom right
) skalieren. Dadurch werden übergroß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 leistungsstark, scrollbar und plattformübergreifend zu implementieren. 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.