Native-Like Animations für Seitenübergänge im Web

Avatar of Sarah Drasner
Sarah Drasner am

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

Einige der inspirierendsten Beispiele für Frontend-Entwicklung, die ich gesehen habe, beinhalteten eine Art von Seitenübergängen, die so geschmeidig aussehen, wie in mobilen Apps. Obwohl die Vorstellungskraft für diese Art von Interaktionen scheinbar im Überfluss vorhanden ist, ist ihre Präsenz auf tatsächlichen Websites, die ich besuche, nicht vorhanden. Es gibt eine Reihe von Möglichkeiten, diese Art von Bewegungen zu realisieren!

Das werden wir bauen

Wir werden die einfachste mögliche Destillation dieser Konzepte bauen, damit Sie sie auf jede Anwendung anwenden können, und dann werde ich auch den Code für diese komplexere App bereitstellen, falls Sie tiefer eintauchen möchten.

Heute werden wir besprechen, wie man sie mit Vue und Nuxt erstellt. Es gibt viele bewegliche Teile bei Seitenübergängen und Animationen (lol, ich bin so witzig), aber keine Sorge! Alles, wofür wir im Artikel keine Zeit haben, werden wir mit anderen Ressourcen verlinken.

Warum?

Das Web wurde in den letzten Jahren dafür kritisiert, im Vergleich zu nativen iOS- und Android-App-Erlebnissen „veraltet“ zu wirken. Der Übergang zwischen zwei Zuständen kann die kognitive Belastung für Ihren Benutzer reduzieren, da jemand, der eine Seite scannt, eine mentale Karte von allem erstellen muss, was darauf enthalten ist. Wenn wir von Seite zu Seite wechseln, muss der Benutzer diesen gesamten Raum neu abbilden. Wenn ein Element auf mehreren Seiten wiederholt wird, aber leicht verändert ist, ahmt es die Erfahrung nach, die wir biologisch erwartet haben – niemand platzt einfach in einen Raum oder verändert sich plötzlich; sie wechseln aus einem anderen Raum in diesen. Ihre Augen sehen jemanden, der im Verhältnis zu Ihnen kleiner ist. Wenn sie sich Ihnen nähern, werden sie größer. Ohne diese Übergänge können Veränderungen schockierend sein. Sie zwingen den Benutzer, die Platzierung und sogar das Verständnis desselben Elements neu abzubilden. Aus diesem Grund werden diese Effekte zu einem kritischen Bestandteil einer Erfahrung, die dem Benutzer hilft, sich im Web zu Hause zu fühlen und schnell Informationen zu sammeln.

Die gute Nachricht ist, dass die Implementierung dieser Art von Übergängen vollständig machbar ist. Lassen Sie uns eintauchen!

Vorausgesetztes Wissen

Wenn Sie mit Nuxt und dessen Verwendung für die Erstellung von Vue.js-Anwendungen nicht vertraut sind, habe ich einen weiteren Artikel zu diesem Thema geschrieben. Wenn Sie mit React und Next.js vertraut sind, ist Nuxt.js das Vue-Äquivalent. Es bietet serverseitiges Rendering, Code-Splitting und vor allem Hooks für Seitenübergänge. Obwohl die von ihm angebotenen Hooks für Seitenübergänge ausgezeichnet sind, werden wir damit nicht den Großteil unserer Animationen in diesem Tutorial realisieren.

Um zu verstehen, wie die Übergänge, mit denen wir heute arbeiten, funktionieren, benötigen Sie außerdem grundlegende Kenntnisse der Komponente <transition /> und des Unterschieds zwischen CSS-Animationen und Übergängen. Beides habe ich hier im Detail behandelt. Sie benötigen auch grundlegende Kenntnisse der Komponente <transition-group /> und dieser Snipcart-Beitrag ist eine großartige Ressource, um mehr darüber zu erfahren.

Obwohl Sie alles im Detail verstehen werden, wenn Sie diese Artikel lesen, werde ich Ihnen die grundlegende Essenz dessen, was vor sich geht, vermitteln, wenn wir im Laufe des Beitrags darauf stoßen.

Erste Schritte

Zuerst wollen wir unser Projekt starten:

# if you haven’t installed vue cli before, do this first, globally:
npm install -g @vue/cli
# or
yarn global add @vue/cli

# then
vue init nuxt/starter my-transitions-project
npm i
# or
yarn 

# and
npm i vuex node-sass sass-loader
# or
yarn add vuex node-sass sass-loader

Großartig! Als Erstes werden Sie feststellen, dass wir ein `pages`-Verzeichnis haben. Nuxt nimmt alle `.vue`-Dateien in diesem Verzeichnis und richtet automatisch das Routing für uns ein. Ziemlich genial. Wir können einige Seiten zum Arbeiten erstellen, in unserem Fall: `about.vue` und `users.vue`.

Einrichtung unserer Hooks

Wie bereits erwähnt, bietet Nuxt einige Seiten-Hooks, die für Seitenübergänge sehr nützlich sind. Mit anderen Worten, wir haben Hooks für das Ein- und Ausblenden einer Seite. Wenn wir also eine Animation erstellen wollten, die uns einen schönen Übergang von Seite zu Seite ermöglicht, könnten wir das tun, da die Klassen-Hooks bereits für uns verfügbar sind. Wir können sogar neue Übergänge pro Seite benennen und JavaScript-Hooks für fortgeschrittenere Effekte verwenden.

Aber was ist, wenn wir Elemente haben, die wir nicht verlassen und neu eintreten lassen wollen, sondern ihre Position ändern sollen? In mobilen Anwendungen verlassen Dinge nicht immer den Zustand, wenn sie sich von Zustand zu Zustand bewegen. Manchmal gehen sie nahtlos von einem Punkt zum anderen über und lassen die gesamte Anwendung sehr flüssig wirken.

Schritt Eins: Vuex Store

Das Erste, was wir tun müssen, ist die Einrichtung eines zentralisierten State-Management-Stores mit Vuex, da wir speichern müssen, auf welcher Seite wir uns gerade befinden.

Nuxt geht davon aus, dass diese Datei im Verzeichnis `store` liegt und `index.js` heißt.

import Vuex from 'vuex'

const createStore = () => {
  return new Vuex.Store({
    state: {
      page: 'index'
    },
    mutations: {
      updatePage(state, pageName) {
        state.page = pageName
      }
    }
  })
}

export default createStore

Wir speichern sowohl die Seite als auch eine Mutation, die es uns erlaubt, die Seite zu aktualisieren.

Schritt Zwei: Middleware

Dann benötigen wir in unserer Middleware ein Skript, das ich `pages.js` genannt habe. Dies gibt uns Zugriff auf die Route, die sich ändert und aktualisiert wird, bevor alle anderen Komponenten geladen werden, was es sehr effizient macht.

export default function(context) {
  // go tell the store to update the page
  context.store.commit('updatePage', context.route.name)
}

Wir müssen die Middleware auch in unserer `nuxt.config.js`-Datei registrieren.

module.exports = {
  ...
  router: {
    middleware: 'pages'
  },
  ...
}

Schritt Drei: Registrieren unserer Navigation

Nun gehen wir in unsere Datei `layouts/default.vue`. Dieses Verzeichnis ermöglicht es Ihnen, verschiedene Layouts für verschiedene Seitenstrukturen festzulegen. In unserem Fall werden wir kein neues Layout erstellen, sondern dasjenige ändern, das wir für jede Seite wiederverwenden. Unsere Vorlage wird zunächst so aussehen.

<template>
  <div>
    <nuxt/>
  </div>
</template>

Und dieses `nuxt/`-Tag fügt alles ein, was in den Vorlagen unserer verschiedenen Seiten steht. Aber anstatt eine Navigationskomponente auf jeder Seite wiederzuverwenden, können wir sie hier einfügen und sie wird auf jeder Seite konsistent dargestellt.

<template>
  <div>
    <app-navigation />
    <nuxt/>
  </div>
</template>
<script>
import AppNavigation from '~/components/AppNavigation.vue'

export default {
  components: {
    AppNavigation
  }
}
</script>

Das ist auch großartig für uns, da es nicht bei jeder Neuweiterleitung neu gerendert wird. Es wird auf jeder Seite konsistent sein, und deshalb können wir uns *nicht* in unsere Seitenübergangs-Hooks einklinken, aber stattdessen können wir unsere eigenen mit dem bauen, was wir zwischen Vuex und der Middleware erstellt haben.

Schritt Vier: Erstellen unserer Übergänge in der Navigationskomponente

Jetzt können wir die Navigation aufbauen, aber ich werde auch dieses SVG verwenden, um eine kleine Demo der grundlegenden Funktionalität zu geben, die wir für eine größere Anwendung implementieren werden.

<template>
  <nav>
    <h2>Simple Transition Group For Layout: {{ page }}</h2>
    <!--simple navigation, we use nuxt-link for routing links-->
    <ul>
      <nuxt-link exact to="/"><li>index</li></nuxt-link>
      <nuxt-link to="/about"><li>about</li></nuxt-link>
      <nuxt-link to="/users"><li>users</li></nuxt-link>
    </ul>
    <br>
    <!--we use the page to update the class with a conditional-->
    <svg :class="{ 'active' : (page === 'about') }" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 447 442">
      <!-- we use the transition group component, we need a g tag because it’s SVG-->
      <transition-group name="list" tag="g">
        <rect class="items rect" ref="rect" key="rect" width="171" height="171"/>
        <circle class="items circ" key="circ" id="profile" cx="382" cy="203" r="65"/>
        <g class="items text" id="text" key="text">
          <rect x="56" y="225" width="226" height="16"/>
          <rect x="56" y="252" width="226" height="16"/>
          <rect x="56" y="280" width="226" height="16"/>
        </g>
        <rect class="items footer" key="footer" id="footer" y="423" width="155" height="19" rx="9.5" ry="9.5"/>
      </transition-group>
    </svg>
  </nav>
</template>
<script>
import { mapState } from 'vuex'

export default {
  computed: mapState(['page'])
}
</script>

Wir tun hier ein paar Dinge. Im Skript holen wir den Seitennamen aus dem Store als berechneten Wert. `mapState` ermöglicht es uns, alles andere aus dem Store zu holen, was später praktisch sein wird, wenn wir mit vielen Benutzerinformationen umgehen.

In der Vorlage haben wir eine normale Nav mit `nuxt-link`s, was wir für Routing-Links in Nuxt verwenden. Wir haben auch eine Klasse, die bedingt auf der Seite aktualisiert wird (sie wird zu `.active`, wenn es die About-Seite ist).

Wir verwenden auch die Komponente `<transition-group>` um eine Reihe von Elementen, die ihre Position ändern werden. Die Komponente `<transition-group>` ist ein bisschen magisch, da sie die Konzepte von FLIP unter der Haube anwendet. Wenn Sie schon einmal von FLIP gehört haben, werden Sie begeistert sein zu hören, dass dies eine sehr performante Methode ist, um auf dem Web zu animieren, aber normalerweise viel Berechnung erfordert. Wenn Sie noch nie von FLIP gehört haben, ist es auf jeden Fall gut, sich zu informieren, um zu verstehen, wie es funktioniert, und vielleicht noch wichtiger, wie viel Zeug Sie nicht mehr tun müssen, um diese Art von Effekt zu erzielen! Kann ich ein "Hell yeah!" bekommen?

Hier ist der CSS-Code, der das ermöglicht. Wir geben grundsätzlich an, wie alle Elemente auf dem "aktiven" Hook positioniert sein sollen. Dann weisen wir den Elementen einen Übergang zu, falls sich etwas ändert. Sie werden bemerken, dass ich 3D-Transformationen verwende, selbst wenn ich etwas nur entlang einer X- oder Y-Achse bewege, da Transformationen für die Leistung besser sind als `top`/`left`/`margin` zur Reduzierung von Painting und ich Hardware-Beschleunigung aktivieren möchte.

.items,
.list-move {
  transition: all 0.4s ease;
}

.active {
  fill: #e63946;
  .rect {
    transform: translate3d(0, 30px, 0);
  }
  .circ {
    transform: translate3d(30px, 0, 0) scale(0.5);
  }
  .text {
    transform: rotate(90deg) scaleX(0.08) translate3d(-300px, -35px, 0);
  }
  .footer {
    transform: translateX(100px, 0, 0);
  }
}

Hier ist ein reduzierter Pen ohne die Seitenübergänge, aber nur um die Bewegung zu zeigen.

Ich möchte darauf hinweisen, dass alle hier von mir verwendeten Implementierungen *Entscheidungen* sind, die ich für Platzierung und Bewegung getroffen habe – Sie können wirklich jeden gewünschten Effekt erzielen! Ich wähle SVG, weil es das Konzept des Layouts in wenig Code kommuniziert, aber Sie müssen SVG nicht verwenden. Ich verwende auch Übergänge statt Animationen, wegen ihrer deklarativen Natur – Sie sagen im Wesentlichen: „Ich möchte, dass dieses Element hier neu positioniert wird, wenn diese Klasse in Vue umgeschaltet wird“, und dann ist die einzige Aufgabe des Übergangs, die Bewegung zu beschreiben, wenn sich etwas ändert. Das ist großartig für diesen Anwendungsfall, da es sehr flexibel ist. Ich kann dann entscheiden, es zu einer anderen bedingten Platzierung zu ändern und es wird trotzdem funktionieren.

Super! Das gibt uns jetzt den Effekt, glatt wie Butter zwischen den Seiten, und wir können dem Seiteninhalt trotzdem einen schönen kleinen Übergang geben.

.page-enter-active {
  transition: opacity 0.25s ease-out;
}

.page-leave-active {
  transition: opacity 0.25s ease-in;
}

.page-enter,
.page-leave-active {
  opacity: 0;
}

Ich habe auch eines der Beispiele von der Nuxt-Website hinzugefügt, um zu zeigen, dass Sie auch interne Animationen innerhalb der Seite durchführen können.

Okay, das funktioniert für eine kleine Demo, aber wenden wir es jetzt auf etwas Realeres an, wie unser Beispiel von zuvor. Wieder ist die Demo-Website hier und das Repo mit dem gesamten Code ist hier.

Es ist das gleiche Konzept.

  • Wir speichern den Namen der Seite im Vuex-Store.
  • Middleware committet eine Mutation, damit der Store weiß, dass die Seite gewechselt hat.
  • Wir wenden eine spezielle Klasse pro Seite an und verschachteln Übergänge für jede Seite.
  • Die Navigation bleibt auf jeder Seite konsistent, aber wir haben unterschiedliche Positionen und wenden einige Übergänge an.
  • Der Seiteninhalt hat einen subtilen Übergang und wir bauen einige Interaktionen basierend auf Benutzerereignissen ein.

Der einzige Unterschied ist, dass dies eine etwas aufwendigere Implementierung ist. Das CSS, das auf die Elemente angewendet wird, bleibt in der Navigationskomponente gleich. Wir können dem Browser sagen, welche Position alle Elemente haben sollen, und da auf dem Element selbst ein Übergang angewendet wird, wird dieser Übergang angewendet und es bewegt sich jedes Mal zur neuen Position, wenn die Seite gewechselt hat.

// animations
.place {
  .follow {
    transform: translate3d(-215px, -80px, 0);
  }
  .profile-photo {
    transform: translate3d(-20px, -100px, 0) scale(0.75);
  }
  .profile-name {
    transform: translate3d(140px, -125px, 0) scale(0.75);
    color: white;
  }
  .side-icon {
    transform: translate3d(0, -40px, 0);
    background: rgba(255, 255, 255, 0.9);
  }
  .calendar {
    opacity: 1;
  }
}

Das war's! Wir halten es schön und einfach und verwenden Flexbox, Grid und absolute Positionierung in einem relativen Container, um sicherzustellen, dass alles auf allen Geräten leicht übersetzbar ist und wir sehr wenige Media Queries in diesem Projekt haben. Ich verwende hauptsächlich CSS für die Navigationsänderungen, da ich die Platzierung der Elemente und ihre Übergänge deklarativ angeben kann. Für die Mikrointeraktionen von benutzergesteuerten Ereignissen verwende ich JavaScript und GreenSock, da es mir ermöglicht, viele Bewegungen sehr nahtlos zu koordinieren und `transform-origin` browserübergreifend zu stabilisieren, aber es gibt so viele Möglichkeiten, dies zu implementieren. Es gibt unendlich viele Möglichkeiten, wie ich diese Demoanwendung verbessern oder auf diesen Animationen aufbauen kann, es ist ein schnelles Projekt, um einige Möglichkeiten in einem realen Kontext zu zeigen.

Denken Sie daran, Hardware zu beschleunigen und Transformationen zu verwenden, und Sie können einige schöne, native-ähnliche Effekte erzielen. Ich bin gespannt, was Sie machen werden! Das Web hat so viel Potenzial für schöne Bewegung, Platzierung und Interaktion, die die kognitive Belastung für den Benutzer reduziert.