Bewegtbild: die Kurven von GSAP, der Zeiger faehrt einen Bogen

Die Website macht ihre Bewegungen mit gsap 3.14.2. Uebernommen sind die
Kurven, nicht das Paket: was GSAP darueber hinaus kann -- Zeitleisten,
ScrollTrigger, MotionPath -- setzt voraus, dass etwas laeuft. Hier
laeuft nichts, animChat(i, t) bekommt seinen Fortschritt von aussen und
ist damit selbst schon die Zeitleiste. Ein paused timeline mit
.progress(t) waere dieselbe Konstruktion mit 70 KB darunter, und ein
Easing ist eine Zeile.

Zwei der Kurven waren ohnehin GSAPs: daempfen ist Wort fuer Wort
power2.inOut, rueck ist back.out(1,70158). Die Namen stehen jetzt
daneben, dazu kommen raus (power2.out) und knall (expo.out).

  Der Zeiger fuhr auf power2.inOut, also langsam an, in der Mitte
  schnell, wieder abbremsend -- die Bewegung eines Schlittens. Eine
  Hand ist sofort schnell und wird zum Ziel hin langsam.

  Und er fuhr auf der Geraden. Jetzt auf einer quadratischen Bezier,
  Stuetzpunkt 13 % der Strecke quer nach aussen, vom Verlauf weg. Rund
  27 px Ausbuchtung auf 410 px Weg: es faellt nicht auf, das ist der
  Punkt.

  Der Klickring geht auf zwei Kurven, Radius auf expo.out und die
  Deckung linear hinterher. Beides auf derselben sah aus wie eine
  Blase, die sich aufblaest.

  Der Anlauf ist von 0,6 s auf 0,18 s geschrumpft. Am Ende steht das
  Bild eine halbe Sekunde still, damit man das Ergebnis liest; lief
  vorn nochmal eine halbe, waren das ueber den Schleifenstoss hinweg
  1,2 von 6 Sekunden Stillstand am Stueck.

MP4 neu gebaut, Werte unveraendert: H.264, 1080x1350, 30 fps, 6,0 s,
stumme AAC-Spur. Geprueft ist das an Kontaktabzuegen ueber die 180
Bilder, nicht am Lauf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7RZ6ZpLD8Dp84Rtt4KHfv
This commit is contained in:
dejanandClaude Opus 5 committed 2026-08-28 20:37:21 +02:00
1 parent 7804587ca0
commit e8f5686f30
3 files changed
+119 -14

No files matched your search

+60 -3
View File
@@ -638,15 +638,72 @@ das Projekt bestätigt sie. Das ist die Funktion, die die Website meint mit
| Zeit | Was passiert |
|---|---|
| 0,0 – 0,6 s | Ruhe. Der Verlauf steht: Sandra, Maike, Tobias |
| 0,6 – 1,3 s | Der Zeiger fährt auf Tobias' Nachricht, der Pfeil erscheint an ihr |
| 0,0 – 0,2 s | Ruhe. Der Verlauf steht: Sandra, Maike, Tobias |
| 0,2 – 1,1 s | Der Zeiger fährt auf Tobias' Nachricht, der Pfeil erscheint an ihr |
| 1,3 – 1,6 s | Klick |
| 1,6 – 2,0 s | Das Menü schnappt nach oben auf: Reagieren, Antworten, Im Thread antworten, Anpinnen, Aufgabe erstellen, Bearbeiten, Löschen |
| 1,5 – 2,0 s | Das Menü schnappt nach oben auf: Reagieren, Antworten, Im Thread antworten, Anpinnen, Aufgabe erstellen, Bearbeiten, Löschen |
| 2,4 – 3,4 s | Der Zeiger geht auf „Aufgabe erstellen", die Zeile hebt sich, Klick |
| 3,6 – 4,8 s | Das Menü schließt, die Aufgabenkarte klappt in Tobias' Nachricht auf, der Verlauf rutscht nach |
| 4,8 – 5,4 s | Die Systemzeile „Aufgabe „Materialliste prüfen" im Projekt erstellt" erscheint |
| 5,4 – 6,0 s | Halt auf dem Ergebnis |
### Die Kurven sind die von GSAP, die Bibliothek ist es nicht
Die Website macht ihre Bewegungen mit **GSAP** (`gsap@3.14.2`, Standard-Lizenz;
`HeroSection`, `Navbar`, `HoneycombSection`, `FAQSection` und weitere). Die
Frage lag also nahe, es hier auch zu tun. Die Antwort ist: **die Kurven ja, die
Bibliothek nein.**
Was GSAP über eine Kurve hinaus kann — Zeitleisten, ScrollTrigger,
MotionPathPlugin —, setzt voraus, dass etwas *läuft*. Hier läuft nichts:
`animChat(i, t)` bekommt seinen Fortschritt von außen und zeichnet daraus den
Stand. Die Funktion **ist** schon die Zeitleiste, und `animation.mjs` sampelt
sie Bild für Bild. Ein `gsap.timeline({paused: true})` mit `.progress(t)` wäre
genau dieselbe Konstruktion, nur mit 70 KB darunter — und das Repo ist autark,
jede Datei in `vendor/` liegt dort, weil sie etwas kann, das man nicht in fünf
Zeilen schreibt (DOM nach Canvas, ZIP). Ein Easing ist eine Zeile.
Dazu kommt: **zwei der Kurven waren längst GSAPs.** `daempfen` ist Wort für
Wort `power2.inOut`, `rueck` ist `back.out(1.70158)`. Die Datei sprach die
Sprache schon, sie sagte es nur nicht. Jetzt stehen die Namen daneben, und der
Satz ist vollständig:
| in der Datei | GSAP | wofür |
|---|---|---|
| `daempfen` | `power2.inOut` | Wege mit Anfang und Ende: das Wandern zur Menüzeile, das Aufklappen der Karte |
| `rueck` | `back.out(1.7)` | das Menü, das aufschnappt und einmal leicht überschwingt |
| `raus` | `power2.out` | alles, was ankommt und liegen bleibt: der Zeiger, die Systemzeile |
| `knall` | `expo.out` | der Klickring |
**Der Zeiger fährt jetzt wie eine Hand und nicht wie ein Schlitten.** Er lag
auf `power2.inOut` — langsam an, in der Mitte schnell, wieder abbremsend. So
bewegt sich ein Schlitten auf einer Schiene. Eine Hand ist sofort schnell und
wird zum Ziel hin langsam, also `power2.out`.
**Und er fährt einen Bogen.** Auf der Geraden zwischen Start und Ziel liegt kein
Zeiger, den je ein Mensch geführt hat. Der Stützpunkt einer quadratischen
Bézierkurve sitzt quer zur Strecke, 13 % ihrer Länge nach außen — vom Verlauf
weg, damit der Zeiger nicht über die Blasen streicht. Das sind rund 27 px
Ausbuchtung auf 410 px Weg; es fällt nicht auf, und genau das ist der Punkt.
Bei `p = 1` liegt die Bahn exakt auf dem Ziel, das anschließende Wandern zur
Menüzeile setzt also sauber daran an.
**Der Klickring geht auf zwei Kurven.** Der Radius auf `expo.out`, also fast
sofort draußen, die Deckung linear hinterher. Beides auf derselben Kurve sah
aus wie eine Blase, die sich aufbläst — ein Klick ist ein Stoß und kein
Aufpumpen.
**Der Anlauf ist von 0,6 s auf 0,18 s geschrumpft.** Am Ende steht das Bild
eine halbe Sekunde still, damit man das Ergebnis liest. Lief vorne noch einmal
eine halbe Sekunde nichts, waren das über den Schleifenstoß hinweg 1,2 von
6 Sekunden Stillstand am Stück, und die Schleife sah aus, als hänge sie. Der
Halt am Ende bleibt, der am Anfang ist auf ein Achtel geschrumpft.
> **Nachgeprüft ist das an Einzelbildern, nicht am Lauf.** Kontaktabzüge über
> die 180 Bilder und eine Nahaufnahme des Klickrings zeigen, was die Kurven
> rechnen. Ob die Bewegung *läuft*, sieht man erst im Abspielen — dieser eine
> Schritt steht noch aus.
### Warum Tobias' Nachricht
Nicht irgendeine: die Aufgabenkarte aus der Quelle trägt **„Tobias Wagner ·
Binary file not shown.
+59 -11
View File
@@ -3537,8 +3537,12 @@ if (location.search.indexOf("nur=anim") >= 0)
'<path d="M0,0 L0,17.5 L4.3,13.4 L7,19.4 L10.1,18 L7.4,12.2 L13,12.2 Z" ' +
'fill="#FFFFFF" stroke="#24242B" stroke-width="1.5" stroke-linejoin="round"/></g>';
/* Der Ring geht auf einer anderen Kurve als er verblasst: der Radius mit
knall, also fast sofort draussen, die Deckung linear hinterher. Beides auf
derselben Kurve sah aus wie eine Blase, die sich aufblaest -- ein Klick
ist ein Stoss und kein Aufpumpen. */
const klickRing = (x, y, p) => p <= 0 || p >= 1 ? "" :
'<circle cx="' + n1(x) + '" cy="' + n1(y) + '" r="' + n1(7 + 17 * p) +
'<circle cx="' + n1(x) + '" cy="' + n1(y) + '" r="' + n1(7 + 17 * knall(p)) +
'" fill="none" stroke="#24242B" stroke-width="' + n1(2.4 * (1 - p)) +
'" opacity="' + n1(.55 * (1 - p)) + '"/>';
@@ -3577,20 +3581,29 @@ if (location.search.indexOf("nur=anim") >= 0)
const innenB = B - 2 * RAND;
const bahnO = KOPF + 8, bahnU = H - FELD - 8;
const zeigerAn = stufe(t, .10, .22);
const pfeil = daempfen(stufe(t, .16, .24)); // der Pfeil an der Blase
const klick1 = stufe(t, .22, .27);
/* Der Zeiger faehrt bei 0,03 los und nicht mehr bei 0,10. Am Ende steht
das Bild eine halbe Sekunde still, damit man das Ergebnis liest -- lief
vorn nochmal eine halbe Sekunde nichts, waren das ueber den
Schleifenstoss hinweg 1,2 von 6 Sekunden Stillstand am Stueck, und die
Schleife sah aus, als haenge sie. Der Halt am Ende bleibt, der am
Anfang ist auf ein Achtel geschrumpft. */
const zeigerAn = stufe(t, .03, .19);
const pfeil = daempfen(stufe(t, .13, .21)); // der Pfeil an der Blase
const klick1 = stufe(t, .21, .26);
/* rueck statt daempfen, und kuerzer: gedaempft war die Deckung nach
fuenf Bildern fertig, waehrend die Skalierung noch eine halbe Sekunde
von 0,96 auf 1,00 weiterkroch. Sichtbar war davon fast nichts, zu
spueren war es als Haenger. Jetzt schnappt es in einer Drittelsekunde
auf, schwingt einmal ganz leicht ueber und steht. */
const auf = rueck(stufe(t, .26, .34));
const auf = rueck(stufe(t, .25, .33));
const wandern = daempfen(stufe(t, .40, .54));
const klick2 = stufe(t, .56, .61);
const zu = daempfen(stufe(t, .60, .66));
const klappen = daempfen(stufe(t, .62, .80)); // die Aufgabe klappt auf
const system = daempfen(stufe(t, .80, .90));
/* raus statt daempfen: die Zeile kommt von unten herein und bleibt
liegen. Eine Kurve, die auch langsam anfaengt, laesst sie zaeh
aussehen -- sie soll ankommen, nicht anfahren. */
const system = raus(stufe(t, .80, .90));
const menueAn = Math.max(0, auf - zu);
let g = "";
@@ -3674,9 +3687,25 @@ if (location.search.indexOf("nur=anim") >= 0)
const zAn = zeigerAn * (1 - zWeg);
if (zAn > .01) {
const vonX = B - 60, vonY = H + 40;
const p1 = daempfen(zeigerAn);
let zx = vonX + (chevX - 2 - vonX) * p1;
let zy = vonY + (chevY - 2 - vonY) * p1;
const zuX = chevX - 2, zuY = chevY - 2;
/* raus statt daempfen. Gedaempft faehrt der Zeiger langsam an, wird in
der Mitte schnell und bremst wieder ab -- das ist die Bewegung eines
Schlittens und nicht die einer Hand. Eine Hand ist sofort schnell und
wird zum Ziel hin langsam. */
const p1 = raus(zeigerAn);
/* Und sie faehrt einen Bogen. Auf der Geraden zwischen Start und Ziel
liegt kein Zeiger, den je ein Mensch gefuehrt hat; das faellt nicht
als Fehler auf, aber die Bewegung wirkt gestellt. Der Stuetzpunkt
sitzt quer zur Strecke, um ein Zehntel ihrer Laenge nach aussen --
weg vom Verlauf, damit der Zeiger nicht ueber die Blasen streicht.
Bei p1 = 1 liegt die Bahn exakt auf dem Ziel, das anschliessende
Wandern zur Menuezeile setzt also sauber daran an. */
const bx = zuX - vonX, by = zuY - vonY, BOGEN = .13;
const sx = (vonX + zuX) / 2 - by * BOGEN;
const sy = (vonY + zuY) / 2 + bx * BOGEN;
const u = 1 - p1;
let zx = u * u * vonX + 2 * u * p1 * sx + p1 * p1 * zuX;
let zy = u * u * vonY + 2 * u * p1 * sy + p1 * p1 * zuY;
if (wandern > 0) {
zx += (m.zielX - zx) * wandern;
zy += (m.zielY - 4 - zy) * wandern;
@@ -3707,10 +3736,29 @@ if (location.search.indexOf("nur=anim") >= 0)
/* Ein paar Verlaeufe. Linear sieht nach Maschine aus, deshalb sind Ein-
und Ausgang gedaempft. `stufe` blendet einen Abschnitt zwischen zwei
Zeitpunkten ein und liefert 0 davor und 1 danach. */
const daempfen = x => x <= 0 ? 0 : x >= 1 ? 1 : x < .5
/* Die Kurven sind die von GSAP, mit dem die Website ihre Bewegungen macht --
als Formel und nicht als Bibliothek. Ein Verlauf ist eine Zeile Rechnung;
was GSAP darueber hinaus koennte (Zeitleisten, Motion-Path, ScrollTrigger),
braucht hier nichts: die Zeichenfunktion bekommt ihr t von aussen und ist
damit selbst schon die Zeitleiste. Die Namen stehen daneben, damit
nachvollziehbar bleibt, welche Kurve gemeint ist.
daempfen und rueck standen vorher unbenannt hier und sind Wort fuer Wort
GSAPs power2.inOut und back.out(1,70158) -- die Datei sprach die Sprache
also schon, sie sagte es nur nicht. */
const daempfen = x => x <= 0 ? 0 : x >= 1 ? 1 : x < .5 // power2.inOut
? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
const rueck = x => x <= 0 ? 0 : x >= 1 ? 1
const rueck = x => x <= 0 ? 0 : x >= 1 ? 1 // back.out(1.7)
: 1 + 2.7 * Math.pow(x - 1, 3) + 1.7 * Math.pow(x - 1, 2);
/* Schnell los, lang aus. Alles, was ankommt und liegen bleibt -- ein Zeiger,
der ins Bild faehrt, eine Zeile, die einlaeuft -- gehoert hierher und
nicht auf eine Kurve, die auch langsam anfaengt. */
const raus = x => x <= 0 ? 0 : x >= 1 ? 1 // power2.out
: 1 - Math.pow(1 - x, 3);
/* Noch schaerfer: schnappt heraus und ist praktisch sofort fertig. Fuer den
Klickring, der einem Fingertipp folgt. */
const knall = x => x <= 0 ? 0 : x >= 1 ? 1 // expo.out
: 1 - Math.pow(2, -10 * x);
const stufe = (t, von, bis) => t <= von ? 0 : t >= bis ? 1 : (t - von) / (bis - von);
const VIS = {