Il movimento era fatto con `@keyframes` e transizioni a durata fissa: nessuna di quelle animazioni può essere afferrata a metà. Se l'utente scorre o tocca mentre una è in corso, quella va avanti per conto suo e per ripartire deve prima finire. `lib/molla.ts` raccoglie i parametri, che sono i due di Apple in «Designing Fluid Interfaces» — rimbalzo e durata, non massa/rigidità/smorzamento. `molla.ui` (nessun sorpasso) è il default; `molla.slancio` si usa solo dopo un gesto che portava già inerzia. `proietta()` calcola dove finirebbe un elemento lanciato, con la stessa decelerazione esponenziale dello scroll iOS. Nei tre componenti: - `Reveal` compare quando entra davvero nel viewport. Prima partiva al mount, quindi gli elementi sotto la piega consumavano l'animazione a vuoto e l'utente li trovava già fermi. Il ritardo massimo scende da 480 a 200 ms. - `Barra` anima `scaleX` invece di `width`: la larghezza rifà il layout a ogni frame, la trasformazione la gestisce il compositore. - `Numero` usa una molla al posto dell'interpolazione a durata fissa: se il dato cambia a metà conteggio il numero cambia rotta da dov'è invece di ripartire da capo. Sparisce la prop `durata`, che nessuno passava. `lib/motion.ts` resta solo per il rilevamento del movimento ridotto (che qui guarda anche `deviceMemory`, cosa che `useReducedMotion` non fa) e per i coriandoli. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
32 lines
1.3 KiB
TypeScript
32 lines
1.3 KiB
TypeScript
/**
|
|
* Parametri di molla condivisi, tarati sui valori che Apple usa in
|
|
* "Designing Fluid Interfaces": damping (rimbalzo) e response (durata) invece
|
|
* di massa/rigidità/smorzamento.
|
|
*
|
|
* L'API `bounce` + `duration` di `motion` mappa esattamente su quella coppia:
|
|
* `bounce: 0` è criticamente smorzata (nessun sorpasso), `bounce: 0.2` è la
|
|
* `damping 0.8` di Apple.
|
|
*
|
|
* Regola: `ui` ovunque; `slancio` SOLO dopo un gesto che portava già inerzia
|
|
* (un lancio, un trascinamento rilasciato). Un menu che compare da fermo non
|
|
* deve rimbalzare.
|
|
*/
|
|
export const molla = {
|
|
/** Default: sposta/riposiziona. damping 1.0, response 0.4. */
|
|
ui: { type: "spring", bounce: 0, duration: 0.4 },
|
|
/** Dopo un gesto con inerzia. damping ~0.8, response 0.4. */
|
|
slancio: { type: "spring", bounce: 0.2, duration: 0.4 },
|
|
/** Fogli e drawer. damping ~0.8, response 0.3. */
|
|
foglio: { type: "spring", bounce: 0.2, duration: 0.3 },
|
|
} as const;
|
|
|
|
/**
|
|
* Proietta dove si fermerebbe un oggetto lanciato a `velocita` px/s, con la
|
|
* stessa decelerazione esponenziale dello scroll iOS. Serve a scegliere il
|
|
* punto di arrivo a partire da dove il gesto *stava andando*, non da dove il
|
|
* dito si è staccato.
|
|
*/
|
|
export function proietta(velocita: number, decelerazione = 0.998) {
|
|
return ((velocita / 1000) * decelerazione) / (1 - decelerazione);
|
|
}
|