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.
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.
| transition-property | Která vlastnost se animuje | all / color / transform |
| transition-duration | Jak dlouho trvá přechod | 0.3s / 300ms |
| transition-timing-function | Easing křivka (rychlost průběhu) | ease / linear |
| transition-delay | Zpoždění před začátkem | 0s / 0.1s |
/* ✅ 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; }
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.
/* 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);
}
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.
animation: name duration timing-fn delay iteration direction fill-mode;
| animation-name | Název @keyframes bloku | |
| animation-duration | Délka jednoho cyklu | 0.3s / 2s |
| animation-timing-function | Easing průběhu | ease / linear |
| animation-delay | Zpoždění startu (záporné = skip) | 0s |
| animation-iteration-count | Počet opakování | 1 / infinite |
| animation-direction | Směr: normal/reverse/alternate | normal |
| animation-fill-mode | Stav před/po animaci | none/forwards/both |
| animation-play-state | Přehrávání / pauza | running/paused |
/* 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;
}
Ří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í.
/* 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 */
Respektujte uživatele kteří mají citlivost na pohyb. Odstraňte nebo zmírněte animace. Povinné!
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í.
/* ✅ 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 */
}
/* === 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;
}
}