Lassen Sie uns eine dieser schicken Scroll-Animationen erstellen, die auf Apple-Produktseiten verwendet werden

Avatar of Jurn van Wissen
Jurn van Wissen am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Apple ist bekannt für die eleganten Animationen auf seinen Produktseiten. Wenn Sie beispielsweise die Seite nach unten scrollen, können Produkte ins Bild gleiten, MacBooks aufklappen und iPhones drehen sich, während die Hardware präsentiert, die Software demonstriert und interaktive Geschichten über die Verwendung der Produkte erzählt werden.

Schauen Sie sich dieses Video der mobilen Web-Erfahrung für das iPad Pro an

Quelle: Twitter

Viele der Effekte, die Sie dort sehen, werden nicht nur in HTML und CSS erstellt. Was dann, fragen Sie? Nun, das kann ein wenig schwierig zu durchschauen sein. Selbst die Verwendung der DevTools des Browsers wird nicht immer die Antwort liefern, da sie oft nicht hinter einem <canvas>-Element sehen kann.

Werfen wir einen eingehenden Blick auf einen dieser Effekte, um zu sehen, wie er erstellt wird, damit Sie einige dieser magischen Effekte in unseren eigenen Projekten nachbilden können. Insbesondere replizieren wir die AirPods Pro Produktseite und den verschiebenden Lichteffekt im Hero-Bild.

Das Grundkonzept

Die Idee ist, eine Animation wie eine Bildsequenz in schneller Abfolge zu erstellen. Sie wissen schon, wie ein Daumenkino! Keine komplexen WebGL-Szenen oder fortschrittlichen JavaScript-Bibliotheken sind erforderlich.

Durch die Synchronisierung jedes Frames mit der Scroll-Position des Benutzers können wir die Animation abspielen, während der Benutzer die Seite nach unten (oder oben) scrollt.

Beginnen Sie mit dem Markup und den Stilen

Das HTML und CSS für diesen Effekt ist sehr einfach, da die Magie innerhalb des <canvas>-Elements stattfindet, das wir mit JavaScript steuern, indem wir ihm eine ID geben.

In CSS geben wir unserem Dokument eine Höhe von 100vh und machen unser <body> 5 Mal so hoch, um die notwendige Scroll-Länge zu erhalten, damit dies funktioniert. Wir passen auch die Hintergrundfarbe des Dokuments an die Hintergrundfarbe unserer Bilder an.

Das Letzte, was wir tun, ist, das <canvas> zu positionieren, zu zentrieren und die max-width und height zu begrenzen, damit es nicht über die Abmessungen des Viewports hinausgeht.

html {
  height: 100vh;
}


body {
  background: #000;
  height: 500vh;
}


canvas {
  position: fixed;
  left: 50%;
  top: 50%;
  max-height: 100vh;
  max-width: 100vw;
  transform: translate(-50%, -50%);
}

Im Moment können wir die Seite nach unten scrollen (auch wenn der Inhalt die Höhe des Viewports nicht überschreitet) und unser <canvas> bleibt oben im Viewport. Das ist alles HTML und CSS, was wir brauchen.

Lassen Sie uns nun mit dem Laden der Bilder fortfahren.

Die richtigen Bilder abrufen

Da wir mit einer Bildsequenz (wiederum wie ein Daumenkino) arbeiten werden, gehen wir davon aus, dass die Dateinamen sequenziell aufsteigend nummeriert sind (d. h. 0001.jpg, 0002.jpg, 0003.jpg usw.) im selben Verzeichnis.

Wir schreiben eine Funktion, die den Dateipfad mit der Nummer der gewünschten Bilddatei zurückgibt, basierend auf der Scroll-Position des Benutzers.

const currentFrame = index => (
  `https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/${index.toString().padStart(4, '0')}.jpg`
)

Da die Bildnummer eine Ganzzahl ist, müssen wir sie in eine Zeichenkette umwandeln und padStart(4, '0') verwenden, um Nullen vor unserem Index einzufügen, bis wir vier Ziffern erreichen, um unseren Dateinamen zu entsprechen. Wenn Sie beispielsweise 1 in diese Funktion übergeben, wird 0001 zurückgegeben.

Das gibt uns eine Möglichkeit, Bildpfade zu handhaben. Hier ist das erste Bild in der Sequenz, das auf dem <canvas>-Element gezeichnet wird

Wie Sie sehen, ist das erste Bild auf der Seite. An diesem Punkt ist es nur eine statische Datei. Was wir wollen, ist, es basierend auf der Scroll-Position des Benutzers zu aktualisieren. Und wir wollen nicht nur eine Bilddatei laden und sie dann durch das Laden einer anderen Bilddatei austauschen. Wir wollen die Bilder auf dem <canvas> zeichnen und die Zeichnung mit dem nächsten Bild in der Sequenz aktualisieren (aber dazu kommen wir gleich).

Wir haben bereits die Funktion erstellt, um den Bilddateipfad basierend auf der Zahl zu generieren, die wir übergeben. Was wir jetzt tun müssen, ist, die Scroll-Position des Benutzers zu verfolgen und den entsprechenden Bild-Frame für diese Scroll-Position zu bestimmen.

Bilder mit dem Scroll-Fortschritt des Benutzers verknüpfen

Um zu wissen, welche Nummer wir übergeben müssen (und somit welches Bild geladen werden muss) in der Sequenz, müssen wir den Scroll-Fortschritt des Benutzers berechnen. Wir erstellen einen Event-Listener, um das zu verfolgen und einige Berechnungen durchzuführen, um zu ermitteln, welches Bild geladen werden soll.

Wir müssen wissen

  • Wo das Scrollen beginnt und endet
  • Der Scroll-Fortschritt des Benutzers (d. h. ein Prozentsatz, wie weit der Benutzer auf der Seite nach unten gescrollt hat)
  • Das Bild, das dem Scroll-Fortschritt des Benutzers entspricht

Wir verwenden scrollTop, um die vertikale Scroll-Position des Elements zu erhalten, die in unserem Fall die Oberseite des Dokuments ist. Dies dient als Startwert. Den End- (oder Maximal-)Wert erhalten wir, indem wir die Fensterhöhe von der Scroll-Höhe des Dokuments subtrahieren. Von dort aus dividieren wir den scrollTop-Wert durch den maximalen Wert, den der Benutzer nach unten scrollen kann, was uns den Scroll-Fortschritt des Benutzers ergibt.

Dann müssen wir diesen Scroll-Fortschritt in eine Indexnummer umwandeln, die mit der Bildnummerierungssequenz übereinstimmt, damit wir das richtige Bild für diese Position zurückgeben können. Dies können wir tun, indem wir die Fortschrittszahl mit der Anzahl der Frames (Bilder) multiplizieren, die wir haben. Wir verwenden Math.floor(), um diese Zahl abzurunden, und packen sie in Math.min() mit unserer maximalen Frame-Anzahl, damit sie nie die Gesamtzahl der Frames überschreitet.

window.addEventListener('scroll', () => {  
  const scrollTop = html.scrollTop;
  const maxScrollTop = html.scrollHeight - window.innerHeight;
  const scrollFraction = scrollTop / maxScrollTop;
  const frameIndex = Math.min(
    frameCount - 1,
    Math.floor(scrollFraction * frameCount)
  );
});

<canvas> mit dem richtigen Bild aktualisieren

Wir wissen jetzt, welches Bild wir zeichnen müssen, wenn sich der Scroll-Fortschritt des Benutzers ändert. Hier kommt die Magie von  <canvas> ins Spiel. <canvas> bietet viele coole Funktionen zum Erstellen von allem, von Spielen und Animationen bis hin zu Design-Mockup-Generatoren und allem dazwischen!

Eine dieser Funktionen ist eine Methode namens requestAnimationFrame, die mit dem Browser zusammenarbeitet, um <canvas> auf eine Weise zu aktualisieren, die wir nicht könnten, wenn wir mit reinen Bilddateien arbeiten würden. Deshalb habe ich mich für einen <canvas>-Ansatz entschieden und nicht z. B. für ein <img>-Element oder ein <div> mit einem Hintergrundbild.

requestAnimationFrame passt sich der Bildwiederholrate des Browsers an und ermöglicht Hardwarebeschleunigung durch die Verwendung von WebGL, um es mit der Grafikkarte oder der integrierten Grafik des Geräts zu rendern. Mit anderen Worten, wir erhalten super flüssige Übergänge zwischen den Frames – keine Bildflackern!

Lassen Sie uns diese Funktion in unserem Scroll-Event-Listener aufrufen, um Bilder zu wechseln, während der Benutzer die Seite nach oben oder unten scrollt. requestAnimationFrame nimmt ein Callback-Argument, also übergeben wir eine Funktion, die die Bildquelle aktualisiert und das neue Bild auf dem <canvas> zeichnet.

requestAnimationFrame(() => updateImage(frameIndex + 1))

Wir erhöhen den frameIndex um 1, denn während die Bildsequenz bei 0001.jpg beginnt, beginnt unsere Scroll-Fortschrittsberechnung tatsächlich bei 0. Dies stellt sicher, dass die beiden Werte immer übereinstimmen.

Die Callback-Funktion, die wir übergeben, um das Bild zu aktualisieren, sieht so aus

const updateImage = index => {
  img.src = currentFrame(index);
  context.drawImage(img, 0, 0);
}

Wir übergeben den frameIndex an die Funktion. Das setzt die Bildquelle mit dem nächsten Bild in der Sequenz, das auf unserem <canvas>-Element gezeichnet wird.

Noch besser mit Bild-Preloading

Technisch gesehen sind wir an diesem Punkt fertig. Aber mal ehrlich, wir können es besser machen! Wenn Sie zum Beispiel schnell scrollen, kommt es zu einer leichten Verzögerung zwischen den Bild-Frames. Das liegt daran, dass jedes neue Bild eine neue Netzwerkanfrage sendet und einen neuen Download erfordert.

Wir sollten versuchen, die Bilder im Voraus zu laden und neue Netzwerkanfragen zu senden. So ist jeder Frame bereits heruntergeladen, was die Übergänge noch schneller und die Animation noch flüssiger macht!

Alles, was wir tun müssen, ist, die gesamte Sequenz von Bildern zu durchlaufen und sie zu laden

const frameCount = 148;


const preloadImages = () => {
  for (let i = 1; i < frameCount; i++) {
    const img = new Image();
    img.src = currentFrame(i);
  }
};


preloadImages();

Demo!

Ein kurzer Hinweis zur Performance

Obwohl dieser Effekt ziemlich raffiniert ist, sind es auch viele Bilder. Genauer gesagt 148.

Egal, wie sehr wir die Bilder optimieren oder wie schnell das CDN sie liefert, das Laden von Hunderten von Bildern führt immer zu einer aufgeblähten Seite. Nehmen wir an, wir haben mehrere Instanzen davon auf derselben Seite. Dann könnten wir Leistungsstatistiken wie diese erhalten

1,609 requests, 55.8 megabytes transferred, 57.5 megabytes resources, load time of 30.45 seconds.

Das mag für eine Hochgeschwindigkeits-Internetverbindung ohne strenge Datenlimits in Ordnung sein, aber das Gleiche können wir nicht für Benutzer ohne solche Annehmlichkeiten sagen. Es ist eine knifflige Balance zu finden, aber wir müssen uns der Erfahrung aller bewusst sein – und wie sich unsere Entscheidungen auf sie auswirken.

Einige Dinge, die wir tun können, um diese Balance zu finden, sind:

  • Laden eines einzelnen Fallback-Bildes anstelle der gesamten Bildsequenz
  • Erstellen von Sequenzen, die für bestimmte Geräte kleinere Bilddateien verwenden
  • Ermöglichen des Benutzers, die Sequenz zu aktivieren, vielleicht mit einer Schaltfläche, die die Sequenz startet und stoppt

Apple verwendet die erste Option. Wenn Sie die AirPods Pro Seite auf einem mobilen Gerät mit einer langsamen 3G-Verbindung laden, und siehe da, die Leistungsstatistiken sehen schon viel besser aus

8 out of 111 requests, 347 kilobytes of 2.6 megabytes transferred, 1.4 megabytes of 4.5 megabytes resources, load time of one minute and one second.

Ja, es ist immer noch eine schwere Seite. Aber sie ist viel leichter als das, was wir ohne jegliche Leistungsüberlegungen bekommen würden. So ist Apple in der Lage, so viele komplexe Sequenzen auf eine einzige Seite zu bringen.


Weitere Lektüre

Wenn Sie daran interessiert sind, wie diese Bildsequenzen generiert werden, ist die Lottie-Bibliothek von AirBnB ein guter Ausgangspunkt. Die Dokumentation führt Sie durch die Grundlagen der Erstellung von Animationen mit After Effects und bietet eine einfache Möglichkeit, diese in Projekte zu integrieren.