Przejdź do treści

Ruch

Krótki, celowy ruch, który wyjaśnia, skąd elementy się biorą i dokąd znikają. Czasy trwania i krzywe przejścia są tokenami, a sprężyny działają w czystym CSS.

Ruch w prfct ma jedno zadanie: wyjaśniać zmianę. Menu wyrasta z przycisku, który je otworzył; okno dialogowe wznosi się na swoje miejsce; gałka przełącznika sprężyście przeskakuje po torze. Nic nie rusza się tylko po to, żeby było widać, że się rusza.

Kompilacja zakończona

Pojawia się w 220 ms (ease-enter), znika w 100 ms (ease-exit).

"use client"

import * as React from "react"
import { BellIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

export default function MotionChoreography() {
  const [open, setOpen] = React.useState(false)

  return (
    <div className="flex h-56 w-full max-w-sm flex-col items-center gap-4">
      <Button variant="outline" onClick={() => setOpen((value) => !value)}>
        <BellIcon data-icon="inline-start" />
        {open ? "Ukryj" : "Pokaż"} powiadomienie
      </Button>
      <div
        data-open={open || undefined}
        className="w-full origin-top rounded-xl bg-popover p-4 shadow-floating ring-1 ring-edge transition-[opacity,scale,translate] duration-fast ease-exit not-data-open:pointer-events-none not-data-open:-translate-y-1 not-data-open:scale-[0.97] not-data-open:opacity-0 data-open:duration-moderate data-open:ease-enter"
      >
        <p className="text-sm font-semibold">Kompilacja zakończona</p>
        <p className="text-sm text-muted-foreground">
          Pojawia się w 220 ms (ease-enter), znika w 100 ms (ease-exit).
        </p>
      </div>
    </div>
  )
}

Zasady

Natychmiastowy. Informacja zwrotna pojawia się w ciągu 100 ms od działania użytkownika. Stany najechania i naciśnięcia są niemal natychmiastowe; interfejs nigdy nie czeka z reakcją, aż skończy się animacja.

Kierunkowy. Elementy wchodzą ze swojego źródła — od strony wyzwalacza, od krawędzi ekranu — i wychodzą tą samą drogą. Wyskakujące elementy skalują się od --transform-origin, zmiennej, którą Base UI ustawia na punkt zakotwiczenia.

Asymetryczny. Elementy znikają szybciej, niż się pojawiają. Wyjścia mają krótsze czasy trwania i przyspieszające krzywe, więc zamykanie nigdy nie wydaje się ociężałe.

Opcjonalny. Ruch to ozdoba nałożona na działający interfejs. Po włączeniu ograniczenia ruchu wszystko nadal działa — tyle że natychmiast.

Czasy trwania

Ta sama krzywa, różne czasy trwania.
instant0ms
Zastępstwa przy ograniczonym ruchu, natychmiastowa informacja zwrotna.
fast100ms
Najechanie, naciśnięcie, zmiany koloru i przezroczystości.
base160ms
Drobne zmiany stanu: przełączniki, zaznaczenia, zakładki.
moderate220ms
Pojawianie się menu, popoverów i podpowiedzi.
slow320ms
Pojawianie się okien dialogowych i paneli.
slower480ms
Osiadanie szuflad, przestawianie powiadomień, duże zmiany układu.

Krzywe przejścia

prfct ma cztery krzywe cubic-bézier i dwie sprężyny. Sprężyny są symulowane na podstawie parametrów fizycznych (sztywności i tłumienia) i zapisane jako funkcje CSS linear() — to prawdziwy ruch sprężyny w przejściach CSS, bez biblioteki animacji i bez JavaScriptu w głównym wątku.

Każda kropka pokonuje ten sam dystans w tym samym czasie; różni się tylko krzywa.

ease-standard700ms

Domyślna dla elementów, które zmieniają się w miejscu.

ease-enter700ms

Elementy wchodzące na ekran. Szybki start, długie wyhamowanie.

ease-exit700ms

Elementy znikające. Przyspiesza przy wyjściu; łącz z krótszymi czasami.

ease-move700ms

Elementy przemieszczające się z miejsca na miejsce na ekranie.

ease-spring521ms

Fizyczna, responsywna reakcja: przełączniki, znaczniki pól radio, puszczenie po przeciągnięciu.

ease-bounce859ms

Zabawne podkreślenie: potwierdzenia sukcesu, pojawiające się odznaki.

// A switch thumb that settles like a physical toggle
<span className="transition-transform duration-spring ease-spring" />
TokenZastosowanie
ease-standardDomyślna dla wszystkiego, co zmienia się w miejscu: kolor, rozmiar, położenie w układzie.
ease-enterElementy, które się pojawiają. Szybki start, długie wyhamowanie.
ease-exitElementy, które znikają. Przyspiesza, odchodząc.
ease-moveElementy przemieszczające się po ekranie.
ease-springFizyczne reakcje na bezpośrednią manipulację: przełączniki, suwaki, puszczenie po przeciągnięciu.
ease-bounceNie do interfejsu produktu. Tylko na rzadki moment świętowania, najwyżej raz w danym procesie.

Klasy Tailwinda ease-in, ease-out i ease-in-out są przypisane do krzywych ease-exit, ease-enter i ease-move, więc standardowa klasa też trzyma się tempa systemu.

Klasy choreografii

Komponenty współdzielą ruch dzięki trzem klasom narzędziowym, więc każda nakładka w systemie porusza się tak samo:

KlasaDziałanie
popup-motionPojawienie się i skalowanie od kotwicy z przesunięciem o 4 px od strony, po której element się otwiera; szybsze wyjście z przyspieszeniem.
modal-motionPojawienie się, skalowanie i uniesienie — dla okien dialogowych i palet.
backdrop-motionPłynnie pokazuje i ukrywa przyciemnienie w rytmie powierzchni nad nim.

Sterują nimi atrybuty data-starting-style i data-ending-style z Base UI, więc animacje da się przerwać: zamknięcie menu w połowie otwierania płynnie odwraca ruch od miejsca, w którym akurat jest.

Ograniczony ruch

Gdy system operacyjny prosi o ograniczenie ruchu, prfct skraca każde przejście i każdą animację do zera. Połysk szkieletów ładowania i obrót spinnera się zatrzymują; stan nadal się zmienia, więc nic nie ginie. Ponieważ prfct nigdy nie przekazuje znaczenia ruchem, jest to zawsze bezpieczne.

Testuj z włączonym ograniczeniem ruchu — w macOS: Ustawienia systemowe → Dostępność → Ruch → Redukuj ruch (przed macOS 26: Ekran); w DevTools: Renderowanie → Emuluj funkcję mediów CSS „prefers-reduced-motion”.