Ich bin mir nicht sicher, wie es dazu kam. Aber es ist eine Geschichte. Dieser Artikel dreht sich mehr um das Durchdringen eines Konzepts, eines, das Ihnen helfen wird, Ihre Animationen auf eine andere Weise zu betrachten. Es stellt sich heraus, dass dieses spezielle Beispiel endloses Scrollen beinhaltet – speziell das „perfekte“ endlose Scrollen für einen Kartenstapel, ohne einen davon zu duplizieren.
Warum bin ich hier? Nun, das alles begann mit einem Tweet. Ein Tweet, der mich über Layouts und seitwärts scrollende Inhalte nachdenken ließ.
Ich habe dieses Konzept übernommen und es auf meiner Website angewendet. Und es ist zum Zeitpunkt des Schreibens immer noch in Aktion.
Dann dachte ich weiter über Galerieansichten und seitwärts scrollende Konzepte nach. Wir haben uns einen Livestream angeschaut und beschlossen, etwas Ähnliches wie das alte Apple „Cover Flow“-Muster zu versuchen. Erinnern Sie es sich?
Meine ersten Gedanken, um dies zu ermöglichen, gingen davon aus, dass ich es so machen würde, dass es ohne JavaScript funktioniert, wie es in der obigen Demo der Fall ist, auf eine Weise, die „progressive enhancement“ nutzt. Ich habe Greensock und ScrollTrigger genommen und los ging es. Ich war von dieser Arbeit ziemlich enttäuscht. Ich hatte etwas, aber konnte das endlose Scrollen nicht so zum Laufen bringen, wie ich es wollte. Die Schaltflächen „Weiter“ und „Zurück“ spielten nicht mit. Sie können es hier sehen, und es erfordert horizontales Scrollen.
Also eröffnete ich einen neuen Thread im Greensock-Forum. Ich wusste nicht, dass ich gerade dabei war, mich in ein ernsthaftes Lernen zu stürzen! Wir lösten das Problem mit den Schaltflächen. Aber, wie ich nun mal bin, musste ich fragen, ob etwas *anderes* möglich sei. Gab es einen „sauberen“ Weg, um endloses Scrollen zu erreichen? Ich hatte etwas im Stream versucht, aber keinen Erfolg. Ich war neugierig. Ich hatte eine Technik ausprobiert, die in diesem Pen verwendet wird, den ich für die ScrollTrigger-Veröffentlichung erstellt hatte.
Die erste Antwort war, dass es ziemlich knifflig ist.
Das Schwierige an unendlichen Dingen beim Scrollen ist, dass die Scrollleiste begrenzt ist, während der gewünschte Effekt es nicht ist. Sie müssen also entweder die Scrollposition durchlaufen lassen, wie diese Demo (gefunden im ScrollTrigger Demos-Bereich), oder Sie greifen direkt in die scrollbezogenen Navigationsereignisse (wie das Wheel-Event) ein, anstatt die tatsächliche Scrollposition zu verwenden.
Ich ging davon aus, dass das der Fall sei und war glücklich, es „wie es ist“ zu belassen. Ein paar Tage vergingen, und Jack warf eine Antwort ein, die mich ziemlich umhaute, als ich anfing, mich damit zu beschäftigen. Und jetzt, nach viel Hin und Her, bin ich hier, um die Technik mit Ihnen zu teilen.
Alles animieren
Eine Sache, die bei GSAP oft übersehen wird, ist, dass man damit *fast* alles animieren kann. Das liegt oft daran, dass bei Animationen visuelle Dinge im Vordergrund stehen – die tatsächliche physische Bewegung von etwas. Unser erster Gedanke ist nicht, diesen Prozess auf eine Meta-Ebene zu heben und aus einer Schritt zurück zu animieren.
Aber denken Sie an Animationsarbeit im größeren Maßstab und zerlegen Sie sie dann in Schichten. Zum Beispiel spielen Sie einen Zeichentrickfilm. Der Zeichentrickfilm ist eine Sammlung von Kompositionen. Jede Komposition ist eine Szene. Und dann haben Sie die Möglichkeit, diese Sammlung von Kompositionen mit einer Fernbedienung durchzuspulen, sei es auf YouTube, mit Ihrer TV-Fernbedienung oder was auch immer. Es gibt fast drei Ebenen dessen, was passiert.
Und das ist der Trick, den wir brauchen, um unterschiedliche Arten von Endlosschleifen zu erstellen. Das ist das Hauptkonzept hier. Wir animieren die Wiedergabeposition einer Zeitleiste mit einer Zeitleiste. Und dann können wir diese Zeitleiste mit unserer Scrollposition durchlaufen.
Machen Sie sich keine Sorgen, wenn das verwirrend klingt. Wir werden es aufschlüsseln.
„Meta“ werden
Beginnen wir mit einem Beispiel. Wir erstellen eine Tween, die einige Boxen von links nach rechts bewegt. Hier ist sie.
Zehn Boxen, die von links nach rechts laufen. Das ist mit Greensock recht einfach. Hier verwenden wir fromTo und repeat, um die Animation am Laufen zu halten. Aber wir haben eine Lücke am Anfang jeder Wiederholung. Wir verwenden auch stagger, um die Bewegung zu verteilen, und das wird eine wichtige Rolle spielen, wenn wir weitermachen.
gsap.fromTo('.box', {
xPercent: 100
}, {
xPercent: -200,
stagger: 0.5,
duration: 1,
repeat: -1,
ease: 'none',
})
Nun zum spaßigen Teil. Lassen Sie uns die Tween anhalten und einer Variablen zuweisen. Dann erstellen wir eine Tween, die sie abspielt. Dies können wir erreichen, indem wir den totalTime der Tween animieren, was es uns ermöglicht, die Wiedergabeposition der Tween abzurufen oder festzulegen, wobei Wiederholungen und Wiederholungsverzögerungen berücksichtigt werden.
const SHIFT = gsap.fromTo('.box', {
xPercent: 100
}, {
paused: true,
xPercent: -200,
stagger: 0.5,
duration: 1,
repeat: -1,
ease: 'none',
})
const DURATION = SHIFT.duration()
gsap.to(SHIFT, {
totalTime: DURATION,
repeat: -1,
duration: DURATION,
ease: 'none',
})
Dies ist unsere erste „Meta“-Tween. Sie sieht genau gleich aus, aber wir fügen eine weitere Kontrollebene hinzu. Wir können Dinge auf dieser Ebene ändern, ohne die ursprüngliche Ebene zu beeinflussen. Zum Beispiel könnten wir die Tween ease auf power4.in ändern. Das verändert die Animation komplett, ohne die zugrunde liegende Animation zu beeinflussen. Wir schützen uns sozusagen mit einem Fallback.
Nicht nur das, wir könnten uns entscheiden, nur einen bestimmten Teil der Zeitleiste zu wiederholen. Das könnten wir mit einer weiteren fromTo tun, so:
Der Code dafür wäre etwa so:
gsap.fromTo(SHIFT, {
totalTime: 2,
}, {
totalTime: DURATION - 1,
repeat: -1,
duration: DURATION,
ease: 'none'
})
Sehen Sie, wohin das führt? Beobachten Sie diese Tween. Obwohl sie sich immer wiederholt, wechseln die Zahlen bei jeder Wiederholung. Aber die Boxen sind in der richtigen Position.
Erreichen der „perfekten“ Schleife
Wenn wir zu unserem ursprünglichen Beispiel zurückkehren, gibt es eine spürbare Lücke zwischen jeder Wiederholung.
Hier kommt der Trick. Der Teil, der alles freischaltet. Wir müssen eine perfekte Schleife bauen.
Beginnen wir damit, den Schiebevorgang dreimal zu wiederholen. Das entspricht der Verwendung von repeat: 3. Beachten Sie, dass wir repeat: -1 aus der Tween entfernt haben.
const getShift = () => gsap.fromTo('.box', {
xPercent: 100
}, {
xPercent: -200,
stagger: 0.5,
duration: 1,
ease: 'none',
})
const LOOP = gsap.timeline()
.add(getShift())
.add(getShift())
.add(getShift())
Wir haben die ursprüngliche Tween in eine Funktion umgewandelt, die die Tween zurückgibt und sie dreimal zu einer neuen Zeitleiste hinzufügt. Und das ergibt Folgendes.
OK. Aber es gibt immer noch eine Lücke. Jetzt können wir den position-Parameter verwenden, um diese Tweens hinzuzufügen und zu positionieren. Wir wollen, dass es nahtlos ist. Das bedeutet, dass jede Gruppe von Tweens eingefügt wird, bevor die vorherige endet. Das ist ein Wert, der auf dem stagger und der Anzahl der Elemente basiert.
const stagger = 0.5 // Used in our shifting tween
const BOXES = gsap.utils.toArray('.box')
const LOOP = gsap.timeline({
repeat: -1
})
.add(getShift(), 0)
.add(getShift(), BOXES.length * stagger)
.add(getShift(), BOXES.length * stagger * 2)
Wenn wir unsere Zeitleiste aktualisieren, um sie zu wiederholen und zu beobachten (während wir den stagger anpassen, um zu sehen, wie er sich auf die Dinge auswirkt)…
Sie werden feststellen, dass es in der Mitte ein Zeitfenster gibt, das eine „nahtlose“ Schleife erzeugt. Erinnern Sie sich an die Fähigkeiten von früher, wo wir die Zeit manipuliert haben? Genau das müssen wir hier tun: das Zeitfenster durchlaufen, in dem die Schleife „nahtlos“ ist.
Wir könnten versuchen, den totalTime durch dieses Zeitfenster der Schleife zu animieren.
const LOOP = gsap.timeline({
paused: true,
repeat: -1,
})
.add(getShift(), 0)
.add(getShift(), BOXES.length * stagger)
.add(getShift(), BOXES.length * stagger * 2)
gsap.fromTo(LOOP, {
totalTime: 4.75,
},
{
totalTime: '+=5',
duration: 10,
ease: 'none',
repeat: -1,
})
Hier sagen wir, wir animieren den totalTime von 4.75 und addieren die Länge eines Zyklus hinzu. Die Länge eines Zyklus beträgt 5. Und das ist das mittlere Zeitfenster der Zeitleiste. Wir können das mit dem praktischen += von GSAP machen, was uns Folgendes gibt:
Nehmen Sie sich einen Moment Zeit, um zu verdauen, was dort passiert. Das könnte der kniffligste Teil sein, den man begreifen kann. Wir berechnen Zeitfenster in unserer Zeitleiste. Es ist schwer zu visualisieren, aber ich habe es versucht.
Dies ist eine Demo einer Uhr, bei der die Zeiger 12 Sekunden brauchen, um sich einmal zu drehen. Sie wird unendlich oft mit repeat: -1 wiederholt, und dann verwenden wir fromTo, um ein bestimmtes Zeitfenster mit einer bestimmten Dauer zu animieren. Wenn Sie das Zeitfenster auf z. B. 2 und 6 reduzieren und dann die Dauer auf 1 ändern, bewegen sich die Zeiger von 2 Uhr auf 6 Uhr wiederholt. Aber wir haben die zugrunde liegende Animation nie geändert.
Versuchen Sie, die Werte zu konfigurieren, um zu sehen, wie sie sich auf die Dinge auswirken.
An diesem Punkt ist es eine gute Idee, eine Formel für unser Fensterposition zu erstellen. Wir könnten auch eine Variable für die Dauer verwenden, die jede Box für den Übergang benötigt.
const DURATION = 1
const CYCLE_DURATION = BOXES.length * STAGGER
const START_TIME = CYCLE_DURATION + (DURATION * 0.5)
const END_TIME = START_TIME + CYCLE_DURATION
Anstatt drei gestapelte Zeitleisten zu verwenden, könnten wir dreimal über unsere Elemente iterieren, wobei wir den Vorteil haben, die Positionen nicht berechnen zu müssen. Die Visualisierung als drei gestapelte Zeitleisten ist jedoch eine gute Möglichkeit, das Konzept zu durchdringen, und eine schöne Art, die Hauptidee zu verstehen.
Lassen Sie uns unsere Implementierung ändern, um von Anfang an eine große Zeitleiste zu erstellen.
const STAGGER = 0.5
const BOXES = gsap.utils.toArray('.box')
const LOOP = gsap.timeline({
paused: true,
repeat: -1,
})
const SHIFTS = [...BOXES, ...BOXES, ...BOXES]
SHIFTS.forEach((BOX, index) => {
LOOP.fromTo(BOX, {
xPercent: 100
}, {
xPercent: -200,
duration: 1,
ease: 'none',
}, index * STAGGER)
})
Das ist einfacher zusammenzustellen und ergibt dasselbe Fenster. Aber wir müssen uns keine Gedanken über Mathematik machen. Jetzt durchlaufen wir drei Sätze von Boxen und positionieren jede Animation entsprechend dem Stagger.
Wie würde das aussehen, wenn wir den Stagger anpassen? Es wird die Boxen näher zusammendrücken.
Aber das hat das Fenster kaputt gemacht, weil jetzt der totalTime nicht stimmt. Wir müssen das Fenster neu berechnen. Jetzt ist ein guter Zeitpunkt, um die zuvor berechnete Formel einzugeben.
const DURATION = 1
const CYCLE_DURATION = STAGGER * BOXES.length
const START_TIME = CYCLE_DURATION + (DURATION * 0.5)
const END_TIME = START_TIME + CYCLE_DURATION
gsap.fromTo(LOOP, {
totalTime: START_TIME,
},
{
totalTime: END_TIME,
duration: 10,
ease: 'none',
repeat: -1,
})
Behoben!
Wir könnten sogar einen „Offset“ einführen, wenn wir die Startposition ändern wollten.
const STAGGER = 0.5
const OFFSET = 5 * STAGGER
const START_TIME = (CYCLE_DURATION + (STAGGER * 0.5)) + OFFSET
Jetzt beginnt unser Fenster an einer anderen Position.
Aber immer noch ist das nicht gut, da es diese ungeschickten Stapel an jedem Ende gibt. Um diesen Effekt zu beseitigen, müssen wir über ein „physisches“ Fenster für unsere Boxen nachdenken. Oder darüber nachdenken, wie sie in die Szene ein- und austreten.
Wir verwenden document.body als Fenster für unser Beispiel. Aktualisieren wir die Box-Tweens zu einzelnen Zeitleisten, bei denen sich die Boxen beim Eintreten vergrößern und beim Austreten verkleinern. Wir können yoyo und repeat: 1 verwenden, um das Ein- und Austreten zu erreichen.
SHIFTS.forEach((BOX, index) => {
const BOX_TL = gsap
.timeline()
.fromTo(
BOX,
{
xPercent: 100,
},
{
xPercent: -200,
duration: 1,
ease: 'none',
}, 0
)
.fromTo(
BOX,
{
scale: 0,
},
{
scale: 1,
repeat: 1,
yoyo: true,
ease: 'none',
duration: 0.5,
},
0
)
LOOP.add(BOX_TL, index * STAGGER)
})
Warum verwenden wir eine Zeitleistendauer von 1? Das macht die Dinge einfacher zu verfolgen. Wir wissen, dass die Zeit 0.5 ist, wenn sich die Box in der Mitte befindet. Es ist erwähnenswert, dass dasEasing hier nicht die übliche Wirkung hat, die wir erwarten. Tatsächlich spielt das Easing eine Rolle dabei, wie die Boxen sich positionieren. Zum Beispiel würde ein ease-in die Boxen rechts zusammendrängen, bevor sie sich bewegen.
Der obige Code ergibt dies.
Fast. Aber unsere Boxen verschwinden für eine Weile in der Mitte. Um dies zu beheben, führen wir die Eigenschaft immediateRender ein. Sie verhält sich wie animation-fill-mode: none in CSS. Wir sagen GSAP, dass wir keine Stile, die auf einer Box gesetzt werden, beibehalten oder vorab aufzeichnen wollen.
SHIFTS.forEach((BOX, index) => {
const BOX_TL = gsap
.timeline()
.fromTo(
BOX,
{
xPercent: 100,
},
{
xPercent: -200,
duration: 1,
ease: 'none',
immediateRender: false,
}, 0
)
.fromTo(
BOX,
{
scale: 0,
},
{
scale: 1,
repeat: 1,
zIndex: BOXES.length + 1,
yoyo: true,
ease: 'none',
duration: 0.5,
immediateRender: false,
},
0
)
LOOP.add(BOX_TL, index * STAGGER)
})
Diese kleine Änderung behebt die Dinge für uns! Beachten Sie, wie wir auch z-index: BOXES.length aufgenommen haben. Das sollte uns vor allen z-index-Problemen schützen.
Da haben wir es! Unsere erste endlose nahtlose Schleife. Keine doppelten Elemente und perfekte Fortsetzung. Wir biegen die Zeit! Klopfen Sie sich auf die Schulter, wenn Sie es bis hierher geschafft haben! 🎉
Wenn wir mehr Boxen auf einmal sehen wollen, können wir mit der Zeitgebung, dem Stagger und der Ease spielen. Hier haben wir einen STAGGER von 0.2 und auch opacity hinzugefügt.
Der Schlüssel hier ist, dass wir repeatDelay nutzen können, damit der opacity-Übergang schneller ist als die Skalierung. Ein- und Ausblenden über 0,25 Sekunden. 0,5 Sekunden warten. Wieder ausblenden über 0,25 Sekunden.
.fromTo(
BOX, {
opacity: 0,
}, {
opacity: 1,
duration: 0.25,
repeat: 1,
repeatDelay: 0.5,
immediateRender: false,
ease: 'none',
yoyo: true,
}, 0)
Cool! Mit diesen Ein- und Ausblendübergängen könnten wir tun, was wir wollen. Das Wichtigste hier ist, dass wir unser Zeitfenster haben, das uns die Endlosschleife gibt.
Das an Scrollen binden
Nachdem wir nun eine nahtlose Schleife haben, binden wir sie an das Scrollen. Dazu können wir GSAP's ScrollTrigger verwenden. Dies erfordert eine zusätzliche Tween, um unser Schleifenfenster zu durchlaufen. Beachten Sie, dass wir die Schleife jetzt auch auf paused gesetzt haben.
const LOOP_HEAD = gsap.fromTo(LOOP, {
totalTime: START_TIME,
},
{
totalTime: END_TIME,
duration: 10,
ease: 'none',
repeat: -1,
paused: true,
})
const SCRUB = gsap.to(LOOP_HEAD, {
totalTime: 0,
paused: true,
duration: 1,
ease: 'none',
})
Der Trick hier ist, ScrollTrigger zu verwenden, um die Wiedergabeposition der Schleife zu durchlaufen, indem der totalTime von SCRUB aktualisiert wird. Es gibt verschiedene Möglichkeiten, diese Scroll-Einrichtung zu gestalten. Wir könnten sie horizontal oder an einen Container gebunden haben. Aber was wir tun werden, ist, unsere Boxen in ein .boxes-Element zu wickeln und dieses am Viewport zu fixieren. (Dies fixiert seine Position im Viewport.) Wir werden auch vertikal scrollen. Überprüfen Sie die Demo, um die Styling für .boxes zu sehen, die alles auf die Größe des Viewports setzt.
import ScrollTrigger from 'https://cdn.skypack.dev/gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger)
ScrollTrigger.create({
start: 0,
end: '+=2000',
horizontal: false,
pin: '.boxes',
onUpdate: self => {
SCRUB.vars.totalTime = LOOP_HEAD.duration() * self.progress
SCRUB.invalidate().restart()
}
})
Der wichtige Teil ist innerhalb von onUpdate. Dort setzen wir den totalTime der Tween basierend auf dem Scroll-Fortschritt. Der Aufruf invalidate leert alle intern aufgezeichneten Positionen für den Scrub. Der Aufruf restart setzt dann die Position auf den neuen totalTime, den wir setzen.
Probieren Sie es aus! Wir können in der Zeitleiste hin und her gehen und die Position aktualisieren.
Wie cool ist das? Wir können scrollen, um eine Zeitleiste zu durchlaufen, die eine Zeitleiste durchläuft, die ein Fenster einer Zeitleiste ist. Verdauen Sie das mal, denn das ist es, was hier passiert.
Zeitreisen für endloses Scrollen
Bis jetzt haben wir Zeit manipuliert. Jetzt werden wir Zeitreisen unternehmen!
Dazu werden wir einige andere GSAP-Utilities verwenden und nicht mehr den totalTime von LOOP_HEAD durchlaufen lassen. Stattdessen werden wir ihn über einen Proxy aktualisieren. Dies ist ein weiteres großartiges Beispiel für „Meta“-GSAP.
Beginnen wir mit einem Proxy-Objekt, das die Position der Wiedergabeposition markiert.
const PLAYHEAD = { position: 0 }
Jetzt können wir unseren SCRUB aktualisieren, um die position zu aktualisieren. Gleichzeitig können wir das wrap-Utility von GSAP verwenden, das den position-Wert um die Dauer von LOOP_HEAD wickelt. Wenn die Dauer beispielsweise 10 ist und wir den Wert 11 angeben, erhalten wir 1 zurück.
const POSITION_WRAP = gsap.utils.wrap(0, LOOP_HEAD.duration())
const SCRUB = gsap.to(PLAYHEAD, {
position: 0,
onUpdate: () => {
LOOP_HEAD.totalTime(POSITION_WRAP(PLAYHEAD.position))
},
paused: true,
duration: 1,
ease: 'none',
})
Zuletzt, aber nicht zuletzt, müssen wir ScrollTrigger überarbeiten, damit es die richtige Variable für den SCRUB aktualisiert. Das ist position anstelle von totalTime.
ScrollTrigger.create({
start: 0,
end: '+=2000',
horizontal: false,
pin: '.boxes',
onUpdate: self => {
SCRUB.vars.position = LOOP_HEAD.duration() * self.progress
SCRUB.invalidate().restart()
}
})
Zu diesem Zeitpunkt haben wir auf einen Proxy umgestellt und werden keine Änderungen sehen.
Wir wollen eine Endlosschleife, wenn wir scrollen. Unser erster Gedanke mag sein, zum Anfang zu scrollen, wenn wir den Scroll-Fortschritt abgeschlossen haben. Und das würde genau das tun, zurückscrollen. Obwohl das das ist, was wir tun wollen, wollen wir nicht, dass die Wiedergabeposition rückwärts durchläuft. Hier kommt totalTime ins Spiel. Erinnern Sie sich? Es ruft die Position der Wiedergabeposition ab oder setzt sie, entsprechend der totalDuration, die Wiederholungen und Wiederholungsverzögerungen *enthält*.
Sagen wir zum Beispiel, die Dauer des Loop-Heads beträgt 5 und wir haben ihn erreicht, dann werden wir nicht auf 0 zurücklaufen. Stattdessen werden wir den Loop-Head weiter bis 10 durchlaufen lassen. Wenn wir weitermachen, geht es bis 15 und so weiter. In der Zwischenzeit werden wir eine Variable iteration führen, da sie uns sagt, wo wir uns im Scrub befinden. Wir stellen auch sicher, dass wir iteration nur aktualisieren, wenn wir die Fortschrittsschwellenwerte erreichen.
Beginnen wir mit einer iteration-Variable
let iteration = 0
Jetzt aktualisieren wir unsere ScrollTrigger-Implementierung
const TRIGGER = ScrollTrigger.create({
start: 0,
end: '+=2000',
horizontal: false,
pin: '.boxes',
onUpdate: self => {
const SCROLL = self.scroll()
if (SCROLL > self.end - 1) {
// Go forwards in time
WRAP(1, 1)
} else if (SCROLL < 1 && self.direction <; 0) {
// Go backwards in time
WRAP(-1, self.end - 1)
} else {
SCRUB.vars.position = (iteration + self.progress) * LOOP_HEAD.duration()
SCRUB.invalidate().restart()
}
}
})
Beachten Sie, wie wir jetzt iteration in die position-Berechnung einbeziehen. Denken Sie daran, dass dies mit dem Scrubber gewickelt wird. Wir erkennen auch, wenn wir die Grenzen unseres Scrolls erreichen, und das ist der Punkt, an dem wir WRAP. Diese Funktion setzt den entsprechenden iteration-Wert und die neue Scroll-Position.
const WRAP = (iterationDelta, scrollTo) => {
iteration += iterationDelta
TRIGGER.scroll(scrollTo)
TRIGGER.update()
}
Wir haben endloses Scrollen! Wenn Sie eine dieser schicken Mäuse mit dem Scrollrad haben, das Sie loslassen können, probieren Sie es aus! Es macht Spaß!
Hier ist eine Demo, die die aktuelle iteration und den progress anzeigt
Scroll-Snapping
Wir sind da. Aber es gibt immer „nice to haves“, wenn man an einem Feature wie diesem arbeitet. Beginnen wir mit dem Scroll-Snapping. GSAP macht das einfach, da wir gsap.utils.snap ohne weitere Abhängigkeiten verwenden können. Das handhabt das Snappen an eine Zeit, wenn wir die Punkte angeben. Wir deklarieren den Schritt zwischen 0 und 1, und wir haben 10 Boxen in unserem Beispiel. Das bedeutet, ein Snap von 0.1 würde für uns funktionieren.
const SNAP = gsap.utils.snap(1 / BOXES.length)
Und das gibt eine Funktion zurück, die wir verwenden können, um unseren position-Wert zu snappen.
Wir wollen nur einmal snappen, wenn das Scrollen beendet ist. Dafür können wir einen Event-Listener auf ScrollTrigger verwenden. Wenn das Scrollen endet, scrollen wir zu einer bestimmten position.
ScrollTrigger.addEventListener('scrollEnd', () => {
scrollToPosition(SCRUB.vars.position)
})
Und hier ist scrollToPosition
const scrollToPosition = position => {
const SNAP_POS = SNAP(position)
const PROGRESS =
(SNAP_POS - LOOP_HEAD.duration() * iteration) / LOOP_HEAD.duration()
const SCROLL = progressToScroll(PROGRESS)
TRIGGER.scroll(SCROLL)
}
Was machen wir hier?
- Berechnung des Zeitpunkts für den Snap
- Berechnung des aktuellen Fortschritts. Nehmen wir an, die
LOOP_HEAD.duration()beträgt1und wir haben zu2.5gesnappt. Das ergibt einen Fortschritt von0.5, was zu eineriterationvon2führt, wobei2.5 - 1 * 2 / 1 === 0.5ist. Wir berechnen den Fortschritt so, dass er immer zwischen1und0liegt. - Berechnung des Scroll-Ziels. Dies ist ein Bruchteil der Distanz, die unser ScrollTrigger abdecken kann. In unserem Beispiel haben wir eine Distanz von
2000festgelegt und wir wollen einen Bruchteil davon. Wir erstellen eine neue FunktionprogressToScroll, um ihn zu berechnen.
const progressToScroll = progress =>
gsap.utils.clamp(1, TRIGGER.end - 1, gsap.utils.wrap(0, 1, progress) * TRIGGER.end)
Diese Funktion nimmt den Fortschrittswert und bildet ihn auf die größte Scroll-Distanz ab. Aber wir verwenden einen Clamp, um sicherzustellen, dass der Wert niemals 0 oder 2000 sein kann. Das ist wichtig. Wir schützen uns davor, auf diese Werte zu snappen, da dies uns in eine Endlosschleife bringen würde.
Da gibt es einiges zu verdauen. Schauen Sie sich diese Demo an, die die aktualisierten Werte bei jedem Snap anzeigt.
Warum sind die Dinge viel schneller? Die Scrubbing-Dauer und die Ease wurden geändert. Eine kürzere Dauer und eine knackigere Ease sorgen für den Snap.
const SCRUB = gsap.to(PLAYHEAD, {
position: 0,
onUpdate: () => {
LOOP_HEAD.totalTime(POSITION_WRAP(PLAYHEAD.position))
},
paused: true,
duration: 0.25,
ease: 'power3',
})
Aber wenn Sie mit dieser Demo gespielt haben, werden Sie ein Problem feststellen. Manchmal, wenn wir innerhalb des Snaps herumwickeln, springt die Wiedergabeposition. Wir müssen das berücksichtigen, indem wir sicherstellen, dass wir beim Snappen wickeln – aber nur, wenn es notwendig ist.
const scrollToPosition = position => {
const SNAP_POS = SNAP(position)
const PROGRESS =
(SNAP_POS - LOOP_HEAD.duration() * iteration) / LOOP_HEAD.duration()
const SCROLL = progressToScroll(PROGRESS)
if (PROGRESS >= 1 || PROGRESS < 0) return WRAP(Math.floor(PROGRESS), SCROLL)
TRIGGER.scroll(SCROLL)
}
Und jetzt haben wir endloses Scrollen mit Snapping!
Was als Nächstes?
Wir haben die Grundlagen für einen soliden unendlichen Scroller gelegt. Wir können darauf aufbauen, um Dinge hinzuzufügen, wie Steuerelemente oder Tastaturfunktionalität. Zum Beispiel könnte dies eine Möglichkeit sein, „Weiter“- und „Zurück“-Schaltflächen und Tastatursteuerungen anzubinden. Wir müssen nur die Zeit manipulieren, oder?
const NEXT = () => scrollToPosition(SCRUB.vars.position - (1 / BOXES.length))
const PREV = () => scrollToPosition(SCRUB.vars.position + (1 / BOXES.length))
// Left and Right arrow plus A and D
document.addEventListener('keydown', event => {
if (event.keyCode === 37 || event.keyCode === 65) NEXT()
if (event.keyCode === 39 || event.keyCode === 68) PREV()
})
document.querySelector('.next').addEventListener('click', NEXT)
document.querySelector('.prev').addEventListener('click', PREV)
Das könnte uns etwas wie das hier geben.
Wir können unsere scrollToPosition-Funktion nutzen und den Wert nach Bedarf erhöhen.
Das ist alles!
Sehen Sie das? GSAP kann mehr als nur Elemente animieren! Hier haben wir Zeit gebogen und manipuliert, um einen fast perfekten unendlichen Slider zu erstellen. Keine doppelten Elemente, kein Durcheinander und gute Flexibilität.
Lassen Sie uns rekapitulieren, was wir behandelt haben
- Wir können eine Animation animieren. 🤯
- Wir können Zeit als Positionierungswerkzeug betrachten, wenn wir Zeit manipulieren.
- Wie man ScrollTrigger verwendet, um eine Animation über einen Proxy zu durchlaufen.
- Wie man einige der großartigen Utilities von GSAP verwendet, um die Logik für uns zu handhaben.
Sie können jetzt Zeit manipulieren! 😅
Dieses Konzept, „Meta“-GSAP zu werden, eröffnet eine Vielzahl von Möglichkeiten. Was könnten Sie sonst noch animieren? Audio? Video? Was das „Cover Flow“-Demo betrifft, hier ging es hin!
Toller Artikel. Ich bin ständig erstaunt über die Kraft von GSAP.
GSAP regiert ;) Beste IMHO Animationsbibliothek, ein Muss – für alle und für alles. Ich empfehle den Kanal auf Y. #snorklTV
Großartig, großartig, großartig.
Ich habe gesehen, wie jemand diesen Fall auf Twitch genutzt hat, um ein normales vertikales Bild-Endlos-Scrollen zu erstellen. Das Verrückte ist, dass man nach oben scrollen kann. Es gibt keinen Startpunkt.
Was nicht möglich war, wurde möglich. Endloses Scrollen, das die Seite nach oben scrollt.
Ich habe daraus einen Codepen gemacht.
Ich habe diese Util-Funktionen von GSAP entdeckt und werde sie sicher verwenden, um Zahlen zu animieren, nicht nur DOM-Elemente.
Mein Gehirn ist ein bisschen geschmolzen. Tolle Arbeit. Dies sollte ein eigenständiges Plugin sein, ich möchte diesen Code nicht manuell nachbauen, wenn ich ihn brauche.
Hoffentlich wird dieser Beitrag aktualisiert, einige Beispiele sind veraltet und verursachen einige seltsame Fehler