// csstahak/ · 07 z 10

Animace & Přechody

transition, animation, @keyframes, transform — plynulé přechody a animace v čistém CSS. Plus will-change a prefers-reduced-motion pro výkon a přístupnost.

transition

Plynulý přechod mezi stavy

Shorthand: vlastnost trvání easing delay. Animuje změnu CSS hodnoty z jednoho stavu do druhého.

transition animuje změnu CSS vlastnosti. Shorthand: vlastnost trvání easing delay. Lze animovat více vlastností najednou (čárkou oddělené). all animuje vše — není doporučeno z výkonnostních důvodů. Animujte jen transform a opacity pro 60fps.

// Proč jen transform a opacityGPU akcelerované vlastnosti: transform a opacity. Prohlížeč je animuje na GPU bez přepočítávání layoutu (reflow). Vlastnosti jako width, height, top, left způsobují reflow = pomalé animace. margin, padding, border = reflow. Vždy preferujte transform.
Syntaxe
all 0s ease 0s color 0.2s ease transform 0.3s ease-out opacity 0.2s, transform 0.3s
Longhand
transition-propertyKterá vlastnost se animujeall / color / transform
transition-durationJak dlouho trvá přechod0.3s / 300ms
transition-timing-functionEasing křivka (rychlost průběhu)ease / linear
transition-delayZpoždění před začátkem0s / 0.1s
Easing funkce — najeďte myší na lišty
linear
ease-in
ease-out
ease-in-out
overshoot
  • Animujte jen transform a opacity = 60fps
  • ease-out = přirozené pro UI (rychlý start, pomalý konec)
  • transition na základním stavu, ne na :hover!
  • transition: all = animuje vše (ne-performantní)
Příklady
CSS
/* ✅ Transition na základním stavu */
.btn {
  background: #2563EB;
  transform: translateY(0);
  /* transition vždy na základním stavu */
  transition:
    background 0.2s ease,
    transform 0.15s ease-out;
}
.btn:hover {
  background: #1d4ed8;
  transform: translateY(-2px);
}

/* Více vlastností */
.card {
  transition:
    box-shadow 0.2s ease,
    transform 0.2s ease;
}
.card:hover {
  box-shadow: 0 8px 24px rgba(0,0,0,0.15);
  transform: translateY(-4px);
}

/* Cubic-bezier pro vlastní easing */
.spring {
  transition: transform 0.4s
    cubic-bezier(0.34, 1.56, 0.64, 1);
  /* overshoot / pružinový efekt */
}

/* Zpoždění pro staggered efekt */
.item:nth-child(1) { transition-delay: 0ms; }
.item:nth-child(2) { transition-delay: 50ms; }
.item:nth-child(3) { transition-delay: 100ms; }

/* ❌ Transition na :hover — špatně! */
.btn:hover {
  transition: all 0.3s; /* nefunguje správně */
}

/* ❌ Animovat layout vlastnosti */
.box { transition: width 0.3s; } /* reflow! */
/* ✅ Místo toho */
.box { transition: transform 0.3s; }
transform

Geometrická transformace

translate, scale, rotate, skew — nepůsobí na layout! GPU akcelerované. Základ výkonných animací.

transform geometricky transformuje element bez ovlivnění layoutu — okolní elementy se nepohnou. GPU akcelerované = plynulé animace. Funkce lze řetězit: translate(x, y) scale(n) rotate(deg). Pořadí záleží — transformace se aplikují zprava doleva.

// Pořadí transformací záležítransform: translateX(100px) rotate(45deg) ≠ rotate(45deg) translateX(100px). Rotace změní souřadnicový systém — pak translateX jde jiným směrem. Pravidlo: transformace se aplikují zprava doleva (jako maticové násobení).
Funkce
translate(x, y) translateX(n) translateY(n) translateZ(n) scale(n) scaleX(n) / scaleY(n) rotate(deg) rotateX/Y/Z(deg) skew(x, y) perspective(n) matrix(a,b,c,d,e,f)
Živé ukázky — najeďte myší
translateY
posun Y
scale
zvětšení
rotate
otočení
skew
zkosení
combo
kombinace
  • transform-origin: center center (výchozí bod transformace)
  • translate(-50%, -50%) pro centrování
  • translateZ(0) nebo will-change: transform = GPU layer
  • Nové: translate: x y, rotate: deg, scale: n (individuální vlastnosti)
Příklady
CSS
/* Centrovat absolutně pozicovaný element */
.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* Hover lift efekt */
.card {
  transition: transform 0.2s ease-out,
    box-shadow 0.2s ease-out;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 32px rgba(0,0,0,.15);
}

/* Zvětšení na hover */
.thumbnail {
  overflow: hidden;
}
.thumbnail img {
  transition: transform 0.4s ease;
}
.thumbnail:hover img {
  transform: scale(1.08);
}

/* Hamburger → X animace */
.bar { transition: transform 0.3s ease; }
.open .bar:nth-child(1) {
  transform: translateY(8px) rotate(45deg);
}
.open .bar:nth-child(2) {
  transform: scaleX(0);
}
.open .bar:nth-child(3) {
  transform: translateY(-8px) rotate(-45deg);
}

/* Nové individuální vlastnosti (CSS 2024) */
.box {
  translate: 0 -10px;  /* místo transform: translateY */
  scale: 1.1;          /* místo transform: scale */
  rotate: 45deg;       /* místo transform: rotate */
}

/* transform-origin */
.door {
  transform-origin: left center; /* závěs vlevo */
  transition: transform 0.4s ease;
}
.door:hover {
  transform: perspective(400px) rotateY(-45deg);
}
animation & @keyframes

CSS animace

Opakující se animace definované v @keyframes. Více kontroly než transition — směr, iterace, fill-mode.

@keyframes definuje průběh animace. animation shorthand: name duration timing-function delay iteration-count direction fill-mode play-state. Na rozdíl od transition: spustí se bez interakce, může se opakovat donekonečna.

Shorthand pořadí
animation: name duration timing-fn delay iteration direction fill-mode;
Klíčové atributy
animation-nameNázev @keyframes bloku
animation-durationDélka jednoho cyklu0.3s / 2s
animation-timing-functionEasing průběhuease / linear
animation-delayZpoždění startu (záporné = skip)0s
animation-iteration-countPočet opakování1 / infinite
animation-directionSměr: normal/reverse/alternatenormal
animation-fill-modeStav před/po animacinone/forwards/both
animation-play-statePřehrávání / pauzarunning/paused
Živé animace
↑↓
bounce
spin
pulse
!
shake
  • fill-mode: forwards = zachová stav na konci animace
  • animation-delay: záporná hodnota = animace začne "uprostřed"
  • alternate = přehrává střídavě vpřed a vzad
  • animation-play-state: paused pro JS ovládání
Příklady @keyframes
CSS
/* Bounce */
@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-20px); }
}
.bouncing { animation: bounce 1s ease-in-out infinite; }

/* Spin (loading) */
@keyframes spin {
  to { transform: rotate(360deg); }
}
.spinner {
  animation: spin 0.8s linear infinite;
}

/* Fade in nahoru */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.appear {
  animation: fadeInUp 0.4s ease-out forwards;
}

/* Staggered — postupné zobrazení */
.item {
  opacity: 0;
  animation: fadeInUp 0.4s ease-out forwards;
}
.item:nth-child(1) { animation-delay: 0ms; }
.item:nth-child(2) { animation-delay: 80ms; }
.item:nth-child(3) { animation-delay: 160ms; }

/* Skeleton loading efekt */
@keyframes shimmer {
  from { background-position: -200% 0; }
  to   { background-position: 200% 0; }
}
.skeleton {
  background: linear-gradient(
    90deg,
    #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

/* Pulse pro notifikace */
@keyframes pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.2); opacity: 0.7; }
}
.badge { animation: pulse 2s ease-in-out infinite; }

/* Přehrát jednou a zastavit */
.toast {
  animation: fadeInUp 0.3s ease-out forwards;
}
.toast.hiding {
  animation: fadeInUp 0.3s ease-in reverse forwards;
}
will-change

Optimalizační nápověda GPU

Říká prohlížeči co se bude animovat — přesune element na GPU layer předem. Používejte střídmě!

will-change informuje prohlížeč předem o plánované animaci. Prohlížeč přesune element na separátní GPU layer = animace proběhne plynuleji. Ale! Každý GPU layer spotřebovává VRAM. Aplikujte jen těsně před animací (JS) a odstraňte po skončení.

// Kdy will-change použítJen pro opravdu problematické animace kde pozorujete trhání. NIKDY globálně na *, NIKDY na statické elementy. Alternativa: transform: translateZ(0) nebo translate3d(0,0,0) vytvoří GPU layer bez will-change. Moderní prohlížeče optimalizují transform/opacity automaticky.
Hodnoty
auto transform opacity contents scroll-position
JS: el.style.willChange = 'transform'; (jen před animací)
* { will-change: transform; } — plýtvání VRAM
will-change na statické elementy
  • Použijte jen na problematické animace
  • Přidejte JS těsně před animaci, odeberte po ní
  • Transform/opacity jsou již automaticky na GPU
  • Příliš mnoho GPU layers = pomalejší celá stránka
Správné použití
JS + CSS
/* CSS: jen pro skutečně problematické animace */
.animated-element {
  will-change: transform;
}
/* Ale lepší je JS přístup: */

/* JS: přidat těsně před animaci */
btn.addEventListener('mouseenter', () => {
  card.style.willChange = 'transform';
});

btn.addEventListener('mouseleave', () => {
  card.addEventListener('transitionend', () => {
    card.style.willChange = 'auto';
  }, { once: true });
});

/* GPU vrstva bez will-change */
.gpu-layer {
  transform: translateZ(0);
  /* nebo: translate3d(0, 0, 0) */
}

/* ❌ NIKDY toto */
* { will-change: transform; }
/* Každý element na GPU layer = chaos */
prefers-reduced-motion

Přístupnost animací

Respektujte uživatele kteří mají citlivost na pohyb. Odstraňte nebo zmírněte animace. Povinné!

PŘÍSTUPNOST

Někteří uživatelé mají vestibulární poruchy — rychlé animace jim způsobují nevolnost nebo závrať. Nastavení "Snížit pohyb" v systému spouští media query prefers-reduced-motion: reduce. Vždy ho respektujte — alespoň odstraňte neesenciální animace nebo zkraťte trvání.

// Co je "neesenciální animace"Neesenciální: parallax scrolling, auto-přehrávané animace, dekorativní animace, infinite loops. Esenciální: focus indikátory, načítací stavy, přechody které komunikují stav (modal open/close). Esenciální zachovejte — jen je zmírněte.
Hodnoty
no-preference reduce ← uživatel chce méně pohybu
// Best practice přístupMísto odstraňování animací v reduce: nastavte animace jako výchozí a přidejte je jen pro uživatele bez preference pohybu. Inverzní přístup: @media (prefers-reduced-motion: no-preference) { ... animace ... }
  • WCAG 2.3.3: animace delší 5s jdou zastavit
  • Parallel scrolling = nutně vypnout při reduce
  • Infinite loops = nutně vypnout při reduce
  • Esenciální animace: alespoň zkrátit duration
Příklady
CSS
/* ✅ Přístup 1: vypnout animace při reduce */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ✅ Přístup 2: animace jen bez reduce */
@media (prefers-reduced-motion: no-preference) {
  .hero-bg {
    animation: parallax 20s linear infinite;
  }
  .spinner {
    animation: spin 0.8s linear infinite;
  }
}

/* ✅ Zmírnit místo odstranit */
.modal {
  transition: opacity 0.3s ease, transform 0.3s ease;
}
@media (prefers-reduced-motion: reduce) {
  .modal {
    transition: opacity 0.1s ease;
    /* Jen fade, ne pohyb */
  }
}

/* JS detekce */
const reducedMotion =
  window.matchMedia(
    '(prefers-reduced-motion: reduce)'
  ).matches;

if (!reducedMotion) {
  initAnimations(); /* jen pokud není reduce */
}
Respektovat prefers-reduced-motion
Ignorovat — WCAG 2.3.3 porušení
// Copy-paste: kompletní animace knihovna
CSS — ready-to-use animace
/* === ANIMACE KNIHOVNA === */

/* Fade In */
@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Fade In Up */
@keyframes fadeInUp {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Fade In Down */
@keyframes fadeInDown {
  from { opacity: 0; transform: translateY(-20px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Slide In Left */
@keyframes slideInLeft {
  from { transform: translateX(-100%); }
  to   { transform: translateX(0); }
}

/* Scale In */
@keyframes scaleIn {
  from { opacity: 0; transform: scale(0.8); }
  to   { opacity: 1; transform: scale(1); }
}

/* Shake (pro chyby) */
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20%, 60%  { transform: translateX(-8px); }
  40%, 80%  { transform: translateX(8px); }
}

/* Bounce */
@keyframes bounce {
  0%, 100% { transform: translateY(0);    animation-timing-function: ease-in; }
  50%      { transform: translateY(-20px); animation-timing-function: ease-out; }
}

/* Spinner */
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Shimmer (skeleton) */
@keyframes shimmer {
  from { background-position: -200% 0; }
  to   { background-position:  200% 0; }
}

/* Utility třídy */
.animate-fade-in   { animation: fadeIn    0.3s ease forwards; }
.animate-fade-up   { animation: fadeInUp  0.4s ease-out forwards; }
.animate-scale-in  { animation: scaleIn   0.3s ease-out forwards; }
.animate-shake     { animation: shake     0.4s ease-in-out; }
.animate-spin      { animation: spin      0.8s linear infinite; }

.skeleton {
  background: linear-gradient(
    90deg, #e0e0e0 25%, #f5f5f5 50%, #e0e0e0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

/* Respektovat reduced motion */
@media (prefers-reduced-motion: reduce) {
  [class*="animate-"] {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}
← 06 Layout 07 / 10 — Animace & Přechody 08 Pseudo →