Einführung
Parallax-Websites sind im Trend. Sehen Sie sich diese Beispiele an:
Bei diesen Websites ändert sich die visuelle Struktur der Seite beim Scrollen. Normalerweise werden Elemente auf der Seite proportional zur Scrollposition auf der Seite skaliert, gedreht oder verschoben.
Ob Sie Paralax-Websites mögen oder nicht, ist eine Sache. Eines ist jedoch ziemlich sicher: Sie sind ein Leistungs-Schwachpunkt. Das liegt daran, dass Browser in der Regel für den Fall optimiert sind, dass beim Scrollen neue Inhalte oben oder unten auf dem Bildschirm angezeigt werden (je nach Scrollrichtung). Generell funktionieren Browser am besten, wenn sich beim Scrollen visuell nur wenig ändert. Bei einer Website mit Paralax-Effekt ist das selten der Fall, da sich oft große visuelle Elemente auf der gesamten Seite ändern, was dazu führt, dass der Browser die gesamte Seite neu anzeigt.
Eine Website mit einem solchen Paralax-Effekt lässt sich so verallgemeinern:
- Hintergrundelemente, die beim Scrollen nach oben und unten ihre Position, Drehung und Skalierung ändern.
- Seiteninhalte wie Text oder kleinere Bilder, die auf typische Weise von oben nach unten gescrollt werden.
Wir haben bereits die Scrollleistung und die Möglichkeiten zur Verbesserung der Reaktionsfähigkeit Ihrer App behandelt. Dieser Artikel baut auf diesen Grundlagen auf. Es kann also sinnvoll sein, sich diesen Artikel anzusehen, falls Sie das noch nicht getan haben.
Die Frage ist also: Wenn Sie eine Website mit Paralax-Scrolling erstellen, sind Sie auf teure Neuzeichnungen angewiesen oder gibt es alternative Ansätze, mit denen Sie die Leistung maximieren können? Sehen wir uns die Optionen an.
Option 1: DOM-Elemente und absolute Positionen verwenden
Dies ist offenbar der Standardansatz, den die meisten Menschen verfolgen. Die Seite enthält viele Elemente. Jedes Mal, wenn ein Scroll-Ereignis ausgelöst wird, werden mehrere visuelle Änderungen vorgenommen, um sie zu transformieren.
Wenn Sie die Zeitachse in den DevTools im Frame-Modus starten und scrollen, werden Sie feststellen, dass es teure Vollbild-Malvorgänge gibt. Wenn Sie viel scrollen, sehen Sie möglicherweise mehrere Scroll-Ereignisse in einem einzelnen Frame, die jeweils Layoutarbeiten auslösen.
Wichtig ist, dass wir nur 16 ms Zeit haben, um alles zu erledigen, um 60 fps zu erreichen (entspricht der typischen Monitoraktualisierungsrate von 60 Hz). In dieser ersten Version führen wir unsere visuellen Aktualisierungen jedes Mal aus, wenn wir ein Scroll-Ereignis erhalten. Wie wir jedoch in früheren Artikeln zu schlankeren, leistungsfähigeren Animationen mit requestAnimationFrame und Scrollleistung besprochen haben, stimmt das nicht mit dem Aktualisierungszeitplan des Browsers überein. Daher werden entweder Frames verpasst oder es wird in jedem Frame zu viel Arbeit ausgeführt. Das kann leicht zu einem ruckeligen und unnatürlichen Erscheinungsbild Ihrer Website führen, was zu enttäuschten Nutzern und unglücklichen Kätzchen führt.
Verschieben wir den Aktualisierungscode aus dem Scroll-Ereignis in einen requestAnimationFrame
-Callback und erfassen wir den Scrollwert einfach im Callback des Scroll-Ereignisses.
Wenn Sie den Scrolltest wiederholen, stellen Sie möglicherweise eine leichte Verbesserung fest, aber nicht viel. Der Grund dafür ist, dass der Layoutvorgang, den wir durch Scrollen auslösen, nicht allzu teuer ist. In anderen Anwendungsfällen könnte das aber durchaus der Fall sein. Jetzt führen wir in jedem Frame mindestens einen Layoutvorgang aus.
Wir können jetzt ein oder hundert Scroll-Ereignisse pro Frame verarbeiten. Entscheidend ist jedoch, dass wir nur den jeweils aktuellen Wert speichern, der verwendet wird, wenn der requestAnimationFrame
-Callback ausgeführt wird und die visuellen Aktualisierungen durchführt. Der Punkt ist, dass Sie nicht mehr versuchen, visuelle Aktualisierungen jedes Mal zu erzwingen, wenn Sie ein Scroll-Ereignis erhalten, sondern den Browser bitten, Ihnen ein geeignetes Fenster dafür zur Verfügung zu stellen. Das ist wirklich nett von Ihnen.
Das Hauptproblem dieses Ansatzes besteht darin, dass wir im Grunde nur eine Ebene für die gesamte Seite haben und durch das Verschieben dieser visuellen Elemente große (und teure) Neumalereien erforderlich sind.requestAnimationFrame
Normalerweise ist das Zeichnen ein blockierender Vorgang (was sich aber ändert). Das bedeutet, dass der Browser keine anderen Aufgaben ausführen kann und wir oft weit über das Budget von 16 Millisekunden für den Frame hinausgehen, was zu Rucklern führt.
Option 2: DOM-Elemente und 3D-Transformationen verwenden
Anstatt absolute Positionen zu verwenden, können wir auch 3D-Transformationen auf die Elemente anwenden. In diesem Fall wird den Elementen mit den angewendeten 3D-Transformationen eine neue Ebene pro Element zugewiesen. In WebKit-Browsern führt dies häufig auch zu einem Wechsel zum Hardware-Compositor. Bei Option 1 gab es dagegen eine große Ebene für die Seite, die bei jeder Änderung neu gemalt werden musste. Das gesamte Malen und Compositing wurde von der CPU ausgeführt.
Bei dieser Option ist das anders: Wir haben potenziell eine Ebene für jedes Element, auf das wir eine 3D-Transformation anwenden. Wenn wir ab diesem Punkt nur noch weitere Transformationen an den Elementen vornehmen, müssen wir die Ebene nicht noch einmal neu zeichnen. Die GPU kann die Elemente verschieben und die endgültige Seite zusammenstellen.
Viele Nutzer verwenden einfach den -webkit-transform: translateZ(0);
-Hack und stellen magische Leistungsverbesserungen fest. Das funktioniert zwar derzeit, aber es gibt Probleme:
- Sie ist nicht plattformübergreifend kompatibel.
- Sie zwingt den Browser, für jedes transformierte Element eine neue Ebene zu erstellen. Viele Ebenen können zu anderen Leistungsengpässen führen. Verwenden Sie sie also sparsam.
- Sie wurde für einige WebKit-Ports deaktiviert (vierter Punkt von unten).
Wenn Sie sich für die 3D-Übersetzung entscheiden, ist Vorsicht geboten. Dies ist nur eine vorübergehende Lösung für Ihr Problem. Idealerweise sollten 2D-Transformationen ähnliche Rendering-Eigenschaften wie 3D-Transformationen haben. Browser entwickeln sich rasant, sodass wir das hoffentlich schon früher sehen werden.
Außerdem sollten Sie nach Möglichkeit keine Farben verwenden, sondern vorhandene Elemente einfach auf der Seite verschieben. Ein typischer Ansatz bei Parallaxen-Websites besteht beispielsweise darin, Divs mit fester Höhe zu verwenden und ihre Hintergrundposition zu ändern, um den Effekt zu erzielen. Leider bedeutet das, dass das Element bei jedem Durchlauf neu gerendert werden muss, was sich auf die Leistung auswirken kann. Stattdessen sollten Sie das Element erstellen (falls erforderlich in einem Div-Element mit overflow: hidden
einschließen) und es dann einfach übersetzen.
Option 3: Canvas mit fester Position oder WebGL verwenden
Die letzte Option, die wir betrachten, ist die Verwendung eines Canvas mit fester Position im hinteren Bereich der Seite, in den wir unsere umgewandelten Bilder zeichnen. Auf den ersten Blick mag das nicht die leistungsstärkste Lösung erscheinen, aber dieser Ansatz hat einige Vorteile:
- Da wir nur noch ein Element haben, nämlich den Canvas, ist weniger Compositor-Arbeit erforderlich.
- Es handelt sich also um eine einzige hardwarebeschleunigte Bitmap.
- Die Canvas2D API eignet sich hervorragend für die Art von Transformationen, die wir durchführen möchten. Das bedeutet, dass Entwicklung und Wartung einfacher sind.
Mit einem Canvas-Element erhalten wir eine neue Ebene, aber es ist nur eine Ebene. Bei Option 2 wurde dagegen für jedes Element mit einer angewendeten 3D-Transformation eine neue Ebene erstellt. Das bedeutet einen erhöhten Arbeitsaufwand, um all diese Ebenen zusammenzusetzen. Dies ist auch die derzeit kompatibelste Lösung angesichts der unterschiedlichen plattformübergreifenden Implementierungen von Transformationen.
/**
* Updates and draws in the underlying visual elements to the canvas.
*/
function updateElements () {
var relativeY = lastScrollY / h;
// Fill the canvas up
context.fillStyle = "#1e2124";
context.fillRect(0, 0, canvas.width, canvas.height);
// Draw the background
context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));
// Draw each of the blobs in turn
context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));
// Allow another rAF call to be scheduled
ticking = false;
}
/**
* Calculates a relative disposition given the page's scroll
* range normalized from 0 to 1
* @param {number} base The starting value.
* @param {number} range The amount of pixels it can move.
* @param {number} relY The normalized scroll value.
* @param {number} offset A base normalized value from which to start the scroll behavior.
* @returns {number} The updated position value.
*/
function pos(base, range, relY, offset) {
return base + limit(0, 1, relY - offset) * range;
}
/**
* Clamps a number to a range.
* @param {number} min The minimum value.
* @param {number} max The maximum value.
* @param {number} value The value to limit.
* @returns {number} The clamped value.
*/
function limit(min, max, value) {
return Math.max(min, Math.min(max, value));
}
Dieser Ansatz funktioniert wirklich gut, wenn es um große Bilder (oder andere Elemente geht, die einfach in ein Canvas geschrieben werden können). Große Textblöcke zu verarbeiten, ist sicherlich schwieriger, aber je nach Website kann dies die am besten geeignete Lösung sein. Wenn Sie wirklich Text auf dem Canvas verwenden müssen, müssen Sie die fillText
API-Methode verwenden. Das geht jedoch zu Lasten der Barrierefreiheit, da Sie den Text einfach in eine Bitmap gerastert haben. Außerdem müssen Sie sich jetzt mit Umbruch und einer ganzen Reihe anderer Probleme befassen. Wenn Sie es vermeiden können, sollten Sie es tun. Sie sind mit dem oben beschriebenen Ansatz für Transformationen wahrscheinlich besser bedient.
Da wir das Ganze so weit wie möglich vorantreiben, gibt es keinen Grund anzunehmen, dass die Parallaxe-Effekte in einem Canvas-Element implementiert werden sollten. Wenn der Browser es unterstützt, könnten wir WebGL verwenden. Der Schlüssel dabei ist, dass WebGL den direktesten Weg aller APIs zur Grafikkarte hat und daher am ehesten geeignet ist, 60 fps zu erreichen, insbesondere wenn die Effekte der Website komplex sind.
Ihre unmittelbare Reaktion könnte sein, dass WebGL übertrieben ist oder nicht überall unterstützt wird. Wenn Sie jedoch etwas wie Three.js verwenden, können Sie immer auf ein Canvas-Element zurückgreifen und Ihr Code wird auf konsistente und nutzerfreundliche Weise abstrahiert. Dazu müssen wir nur Modernizr verwenden, um die entsprechende API-Unterstützung zu prüfen:
// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
renderer = new THREE.CanvasRenderer();
}
Wenn Sie der Meinung sind, dass die Seite nicht zu viele zusätzliche Elemente enthalten sollte, können Sie sowohl in Firefox als auch in WebKit-basierten Browsern Canvas als Hintergrundelement verwenden. Das ist natürlich nicht überall der Fall. Wie immer sollten Sie also vorsichtig sein.
Die Entscheidung liegt ganz bei dir
Der Hauptgrund dafür, dass Entwickler standardmäßig absolut positionierte Elemente verwenden, anstatt eine der anderen Optionen, ist möglicherweise einfach die universelle Unterstützung. Das ist jedoch in gewissem Maße trügerisch, da die älteren Browser, auf die das Targeting erfolgt, wahrscheinlich eine extrem schlechte Darstellung bieten. Selbst in modernen Browsern führt die Verwendung absolut positionierter Elemente nicht unbedingt zu einer guten Leistung.
Mit Transformationen, insbesondere 3D-Transformationen, können Sie direkt mit DOM-Elementen arbeiten und eine stabile Framerate erzielen. Der Schlüssel zum Erfolg besteht darin, so wenig wie möglich zu malen und stattdessen einfach Elemente zu verschieben. Beachten Sie, dass die Art und Weise, wie WebKit-Browser Ebenen erstellen, nicht unbedingt mit anderen Browser-Engines übereinstimmt. Testen Sie die Lösung daher unbedingt, bevor Sie sich dafür entscheiden.
Wenn Sie nur die Top-Browser ansprechen und die Website mit Canvas-Elementen rendern können, ist dies möglicherweise die beste Option für Sie. Wenn Sie Three.js verwenden, sollten Sie je nach erforderlicher Unterstützung ganz einfach zwischen Renderern wechseln können.
Fazit
Wir haben einige Ansätze für Parallax-Websites bewertet, von absolut positionierten Elementen bis hin zur Verwendung eines Canvas mit fester Position. Die Implementierung hängt natürlich davon ab, was Sie erreichen möchten und mit welchem Design Sie arbeiten. Es ist aber immer gut zu wissen, dass Sie Optionen haben.
Und wie immer gilt: Welchen Ansatz Sie auch ausprobieren: Nicht raten, sondern testen.