Der folgende Artikel beschreibt den Prozess des Aufbaus der Million Developers Microsite für Netlify. Dieses Projekt wurde von ein paar Leuten erstellt, und wir haben einige Teile des Entstehungsprozesses hier festgehalten – hauptsächlich mit Fokus auf die Animation, falls es für andere, die ähnliche Erlebnisse schaffen, hilfreich sein mag.

Eine Vue App aus einem SVG erstellen
Die Schönheit von SVG ist, dass man es und das Koordinatensystem wie ein großes Schiffe versenken-Spiel betrachten kann. Man denkt wirklich in Begriffen von x, y, Breite und Höhe.
<div id="app">
<app-login-result-sticky v-if="user.number" />
<app-github-corner />
<app-header />
<!-- this is one big SVG -->
<svg id="timeline" xmlns="http://www.w3.org/2000/svg" :viewBox="timelineAttributes.viewBox">
<!-- this is the desktop path -->
<path
class="cls-1 timeline-path"
transform="translate(16.1 -440.3)"
d="M951.5,7107..."
/>
<!-- this is the path for mobile -->
<app-mobilepath v-if="viewportSize === 'small'" />
<!-- all of the stations, broken down by year -->
<app2016 />
<app2017 />
<app2018 />
<app2019 />
<app2020 />
<!-- the 'you are here' marker, only shown on desktop and if you're logged in -->
<app-youarehere v-if="user.number && viewportSize === 'large'" />
</svg>
</div>
Innerhalb der größeren App-Komponente haben wir den großen Header, aber der Rest ist ein riesiges SVG. Von dort haben wir den Rest des riesigen SVGs in mehrere Komponenten zerlegt
- Candyland-ähnliche Pfade für Desktop und Mobilgeräte, bedingt durch einen Zustand im Vuex-Store angezeigt
- Es gibt 27 Stationen, ihre Textgegenstücke nicht mitgerechnet, und viele dekorative Komponenten wie Büsche, Bäume und Laternen, was in einer Komponente viel zu verfolgen ist, daher sind sie nach Jahren aufgeteilt
- Der „Sie sind hier“-Marker, nur auf dem Desktop angezeigt, wenn Sie angemeldet sind
SVG ist wunderbar flexibel, da wir nicht nur absolute und relative Formen und Pfade innerhalb dieses Koordinatensystems zeichnen können, sondern auch SVGs innerhalb von SVGs. Wir müssen nur x, y, width und height dieser SVGs definieren und können sie in das größere SVG einbetten, was genau das ist, was wir mit all diesen Komponenten tun werden, damit wir ihre Platzierung bei Bedarf anpassen können. Das <g> innerhalb der Komponenten steht für group (Gruppe), man kann sie sich ein wenig wie divs in HTML vorstellen.
So sieht das in den Jahreskomponenten aus
<template>
<g>
<!-- decorative components -->
<app-tree x="650" y="5500" />
<app-tree x="700" y="5550" />
<app-bush x="750" y="5600" />
<!-- station component -->
<app-virtual x="1200" y="6000" xSmall="50" ySmall="15100" />
<!-- text component, with slots -->
<app-text
x="1400"
y="6500"
xSmall="50"
ySmall="15600"
num="20"
url-slug="jamstack-conf-virtual"
>
<template v-slot:date>May 27, 2020</template>
<template v-slot:event>Jamstack Conf Virtual</template>
</app-text>
...
</template>
<script>
...
export default {
components: {
// loading the decorative components in syncronously
AppText,
AppTree,
AppBush,
AppStreetlamp2,
// loading the heavy station components in asyncronously
AppBuildPlugins: () => import("@/components/AppBuildPlugins.vue"),
AppMillion: () => import("@/components/AppMillion.vue"),
AppVirtual: () => import("@/components/AppVirtual.vue"),
},
};
...
</script>
Innerhalb dieser Komponenten sehen Sie eine Reihe von Mustern
- Wir haben Büsche und Bäume zur Dekoration, die wir über
x- undy-Werte per Props verteilen können - Wir können einzelne Stationskomponenten haben, die ebenfalls zwei verschiedene Positionierungswerte haben, einen für große und einen für kleine Geräte
- Wir haben eine Textkomponente, die drei verfügbare
Slotshat, einen für das Datum und zwei für zwei verschiedene Textzeilen - Wir laden auch die dekorativen Komponenten synchron und die schwereren SVG-Stationen asynchron.
SVG-Animation

Die SVG-Animation wird mit GreenSock (GSAP) und deren neuem ScrollTrigger-Plugin durchgeführt. Ich habe Anfang des Jahres einen Leitfaden zur Arbeit mit GSAP für deren neueste 3.0-Version geschrieben. Wenn Sie diese Bibliothek nicht kennen, könnte das ein guter Anfang sein.
Die Arbeit mit dem Plugin ist dankenswerterweise unkompliziert, hier ist die Basis der Funktionalität, die wir benötigen werden
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger.js";
import { mapState } from "vuex";
gsap.registerPlugin(ScrollTrigger);
export default {
computed: {
...mapState([
"toggleConfig",
"startConfig",
"isAnimationDisabled",
"viewportSize",
]),
},
...
methods: {
millionAnim() {
let vm = this;
let tl;
const isScrollElConfig = {
scrollTrigger: {
trigger: `.million${vm.num}`,
toggleActions: this.toggleConfig,
start: this.startConfig,
},
defaults: {
duration: 1.5,
ease: "sine",
},
};
}
},
mounted() {
this.millionAnim();
},
};
Zuerst importieren wir gsap und das benötigte Paket sowie den State aus dem Vuex-Store. Ich habe die toggleActions und start Konfigurationseinstellungen im Store hinterlegt und sie in jede Komponente übergeben, da ich während der Arbeit experimentieren musste, welcher Punkt in der UI die Animationen auslösen sollte. Das ersparte mir die separate Konfiguration jeder Komponente.
Diese Konfigurationen im Store sehen so aus
export default new Vuex.Store({
state: {
toggleConfig: `play pause none pause`,
startConfig: `center 90%`,
}
}
Diese Konfiguration lässt sich wie folgt aufschlüsseln:
toggleConfig: Die Animation wird abgespielt, wenn sie die Seite nach unten durchläuft (eine andere Option istrestart, dann wird sie erneut ausgelöst, wenn man sie wieder sieht), sie pausiert, wenn sie außerhalb des Viewports ist (das kann der Performance leicht helfen), und sie löst beim Zurückgehen nach oben nicht erneut in umgekehrter Richtung aus.startConfiggibt an, dass die Animation beginnen soll, wenn die Mitte des Elements 90% der Viewport-Höhe nach unten erreicht hat.
Dies sind die Einstellungen, die wir für dieses Projekt gewählt haben, es gibt noch viele weitere! Sie können alle Optionen mit diesem Video verstehen.
Für diese spezielle Animation mussten wir sie etwas anders behandeln, wenn es sich um eine Banneranimation handelte, die nicht durch Scrollen ausgelöst werden musste, oder wenn sie später im Zeitplan lag. Wir übergaben einen Prop und nutzten diesen, um die Konfiguration je nach Anzahl der Props zu übergeben.
if (vm.num === 1) {
tl = gsap.timeline({
defaults: {
duration: 1.5,
ease: "sine",
},
});
} else {
tl = gsap.timeline(isScrollElConfig);
}
Dann verwende ich für die Animation selbst das, was als Label auf der Timeline bezeichnet wird. Man kann es sich wie die Markierung eines Zeitpunkts auf dem Playhead vorstellen, an dem man Animationen oder Funktionalitäten anbringen möchte. Wir müssen auch hier die Nummern-Props für das Label verwenden, damit wir die Timelines für die Header- und Footer-Komponenten getrennt halten.
tl.add(`million${vm.num}`)
...
.from(
"#front-leg-r",
{
duration: 0.5,
rotation: 10,
transformOrigin: "50% 0%",
repeat: 6,
yoyo: true,
ease: "sine.inOut",
},
`million${vm.num}`
)
.from(
"#front-leg-l",
{
duration: 0.5,
rotation: 10,
transformOrigin: "50% 0%",
repeat: 6,
yoyo: true,
ease: "sine.inOut",
},
`million${vm.num}+=0.25`
);
In der Million Devs-Animation passiert viel, daher isoliere ich nur ein Bewegungselement, um es aufzuschlüsseln: Oben sehen wir die schwingenden Beine der Mädchen. Wir haben beide Beine separat schwingen, beide wiederholen sich mehrmals, und yoyo: true teilt GSAP mit, dass ich möchte, dass die Animation bei jeder zweiten Änderung rückgängig gemacht wird. Wir drehen die Beine, aber was sie realistisch macht, ist, dass der transformOrigin oben in der Mitte des Beins beginnt, sodass sie sich beim Drehen um die Knieachse drehen, so wie Knie es tun :)
Hinzufügen eines Animations-Toggles

Wir wollten den Benutzern die Möglichkeit geben, die Seite ohne Animation zu erkunden, falls sie eine vestibuläre Störung haben. Daher haben wir einen Schalter für den Animations-Wiedergabezustand erstellt. Der Schalter ist nichts Besonderes – er aktualisiert den Zustand im Vuex-Store über eine Mutation, wie Sie es erwarten würden.
export default new Vuex.Store({
state: {
...
isAnimationDisabled: false,
},
mutations: {
updateAnimationState(state) {
state.isAnimationDisabled = !state.isAnimationDisabled
},
...
})
Die eigentlichen Aktualisierungen finden in der obersten App-Komponente statt, wo wir alle Animationen und Trigger sammeln und sie dann basierend auf dem Zustand im Store anpassen. Wir watchen die Eigenschaft isAnimationDisabled auf Änderungen und wenn eine auftritt, greifen wir alle Instanzen von Scrolltrigger-Animationen in der App. Wir kill()en die Animationen nicht, obwohl das eine Option ist, denn wenn wir das täten, könnten wir sie nicht neu starten.
Stattdessen setzen wir ihren Fortschritt entweder auf das letzte Bild, wenn Animationen deaktiviert sind, oder wenn wir sie neu starten, setzen wir ihren Fortschritt auf 0, damit sie neu starten können, wenn sie auf der Seite ausgelöst werden sollen. Wenn wir hier .restart() verwendet hätten, wären alle Animationen abgespielt worden und wir hätten sie nicht beim Weiterblättern auf der Seite ausgelöst gesehen. Das Beste aus beiden Welten!
watch: {
isAnimationDisabled(newVal, oldVal) {
ScrollTrigger.getAll().forEach((trigger) => {
let animation = trigger.animation;
if (newVal === true) {
animation && animation.progress(1);
} else {
animation && animation.progress(0);
}
});
},
},
SVG-Barrierefreiheit
Ich bin keineswegs ein Experte für Barrierefreiheit, also lassen Sie mich bitte wissen, wenn ich hier Fehler gemacht habe – aber ich habe viel recherchiert und getestet auf dieser Seite und war ziemlich aufgeregt, dass, als ich auf meinem Macbook über Voiceover getestet habe, die relevanten Informationen der Seite traversierbar waren. Daher teile ich mit, was wir getan haben, um dorthin zu gelangen.
Für das initiale SVG, das alles umschloss, haben wir keine Rolle angewendet, damit der Screenreader darin navigieren kann. Für die Bäume und Büsche haben wir role="img" angewendet, damit der Screenreader sie überspringt, und für die detaillierteren Stationen haben wir eine eindeutige id und title angewendet, was das erste Element innerhalb des SVG war. Wir haben auch role="presentation" angewendet.
<svg
...
role="presentation"
aria-labelledby="analyticsuklaunch"
>
<title id="analyticsuklaunch">Launch of analytics</title>
Vieles davon habe ich aus diesem Artikel von Heather Migliorisi und diesem großartigen Artikel von Leonie Watson gelernt.
Der Text innerhalb des SVG wird beim Tabben durch die Seite angekündigt, und der Link wird gefunden, der gesamte Text wird gelesen. So sieht die Textkomponente aus, mit den oben erwähnten Slots.
<template>
<a
:href="`https://www.netlify.com/blog/2020/08/03/netlify-milestones-on-the-road-to-1-million-devs/#${urlSlug}`"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="450"
height="250"
:x="svgCoords.x"
:y="svgCoords.y"
viewBox="0 0 280 115.4"
>
<g :class="`textnode text${num}`">
<text class="d" transform="translate(7.6 14)">
<slot name="date">Jul 13, 2016</slot>
</text>
<text class="e" transform="translate(16.5 48.7)">
<slot name="event">Something here</slot>
</text>
<text class="e" transform="translate(16.5 70)">
<slot name="event2" />
</text>
<text class="h" transform="translate(164.5 104.3)">View Milestone</text>
</g>
</svg>
</a>
</template>
Hier ist ein Video davon, wie sich das anhört, wenn ich auf meinem Mac durch das SVG tabbiere
Wenn Sie weitere Verbesserungsvorschläge haben, lassen Sie es uns bitte wissen!
Das Repository ist auch Open Source, wenn Sie den Code einsehen oder einen PR einreichen möchten.
Vielen Dank (Wortspiel beabsichtigt) an meine Kollegen Zach Leatherman und Hugues Tennier, die mit mir daran gearbeitet haben; ihr Input und ihre Arbeit waren für das Projekt von unschätzbarem Wert. Es existiert nur durch Teamwork, um es zum Abschluss zu bringen! Und viel Respekt an Alejandro Alvarez, der das Design gemacht hat und eine spektakuläre Arbeit geleistet hat. Hoch fünf an alle. 🙌
Fantastisch! Ich wusste, dass Sarah etwas Großartiges mit ScrollTrigger machen würde! Ich freue mich darauf, dies als Inspiration zu nutzen! :D
Das ist wunderbar Sarah – schön zu sehen, wie du weiterhin zeigst, wie überzeugend SVG für die Animation von Geschichten und das Belebung einer Webseite ist!
Ich muss zugeben, dass ich etwas überrascht war über die steifen rechteckigen weißen Karten und das Gewicht der
font-size, die dort verwendet wurde (im Gegensatz zu den extrem raffinierten Illustrationsbereichen); aber ich gebe zu, dass ich da sehr meinungsfreudig bin, haha.Insgesamt ein fantastisches Beispiel dafür, was mit SVG und GSAP möglich ist und wie man effektiv eine Geschichte erzählt!
Etwas, das ich sehr interessant zu erfahren fände, ist, wie das Projekt den relevanten Netflix-Stakeholdern schmackhaft gemacht wurde. Wie sich der Zeitplan entfaltete. Sie müssen ROI auf eine überzeugende Weise dargestellt haben, oder?
Ich habe oft damit gekämpft, die Argumente für solche Projekte zu liefern, die offensichtlich länger dauern als eine statische Seite. Und entweder musste ich Zeit von meiner Familie wegnehmen, um außerhalb der Arbeitszeit zu arbeiten, weil ich so überzeugt war, es richtig zu machen (was offensichtlich nicht nachhaltig ist), oder ich habe die Seite einfach nicht auf dem Verfeinerungsgrad umgesetzt, den ich mir gewünscht hätte. Vielleicht hast du ein paar Ratschläge zum Thema „Zustimmung von Stakeholdern gewinnen“, Sarah. Ich denke, wir würden mehr SVG / GSAP-Kreativität aus unserer Community sehen, wenn wir diesen nicht-technischen Aspekt irgendwie meistern könnten.
Ok, und nur noch ein BRAVO scheint angebracht :-)
Das ist absolut wunderbar!
Wie lernt und erstellt man benutzerdefinierte animierte SVGs wie diese???