Kurs HTML i CSS · Moduł 6: Animacje CSS
Projekt główny - Animowana Galeria 3D Portfolio
W tej lekcji5
Egipskie komnaty grobowe opowiadały historie obraz po obrazie, a zwiedzający z pochodnią odkrywał je po kolei. Twoje portfolio może działać tak samo: sześcian obraca się w przestrzeni, a karty projektów wchodzą na scenę jedna po drugiej. W tym projekcie stworzymy spektakularną galerię 3D wykorzystującą wyłącznie HTML i CSS. Galeria będzie zawierać zaawansowane animacje @keyframes, transformacje 3D, efekty hover oraz automatyczne pokazy slajdów. Projekt demonstruje wszystkie techniki animacji CSS z tego modułu bez użycia JavaScript.
Opis projektu
Nasza galeria 3D będzie zawierać:
- Karuzela 3D - obracająca się kostka z obrazami
- Animowane karty projektów - z efektami hover i transformacjami
- Automatyczny pokaz slajdów - wykorzystujący tylko CSS
- Efekty cząsteczkowe - animowane tło i dekoracje
- Animowane menu nawigacyjne - z efektami przejścia
- Loading screen - z zaawansowanymi animacjami
Galeria 3D krok po kroku
1. Scena 3D
Wszystko zaczyna się od sceny. perspective nadana rodzicowi ustala, jak daleko od ekranu stoi widz, i daje głębię jego dzieciom, a transform-style: preserve-3d pozwala ścianom sześcianu żyć w jednej przestrzeni:
body {
perspective: 1000px;
}
.cube-container {
transform-style: preserve-3d;
}
.front { transform: translateZ(150px); }
.back { transform: rotateY(180deg) translateZ(150px); }Każda ściana jest obracana i wysuwana o połowę szerokości sześcianu, czyli 150 pikseli. perspective działa na dzieci elementu, a sześcian jest wnukiem body, więc w tym kodzie obraca się bez skrótu perspektywicznego. Dopisz perspective: 1000px do .hero i porównaj głębię.
2. Klatki kluczowe obrotu
Obrót opisuje reguła @keyframes, od zera do pełnych 360 stopni wokół obu osi naraz:
@keyframes rotateCube {
0% { transform: rotateX(0) rotateY(0); }
100% { transform: rotateX(360deg) rotateY(360deg); }
}rotateY() obraca element wokół osi pionowej, jak karuzelę, a rotateX() wokół poziomej, jak koło nawadniające nad Nilem.
3. Animacja w ruchu
Obrót uruchamia deklaracja animation na kontenerze sześcianu, z czasem trwania zapisanym w zmiennej CSS:
.cube-container {
animation: rotateCube var(--animation-duration) linear infinite;
}Zmienna --animation-duration ma w :root wartość 20s, linear trzyma stałą prędkość, a infinite kręci sześcian bez końca.
4. Efekty hover na kartach
Karty projektów unoszą się płynnie pod kursorem, a przy wejściu na stronę mają własną animację:
.project-card {
transition: transform 0.6s ease;
animation: cardAppear 0.8s ease-out backwards;
}
.project-card:hover {
transform: translateY(-10px) rotateX(5deg);
}Kluczowy jest tryb backwards. Animacja z forwards trzymałaby kartę w ostatniej klatce i nadpisywała jej transform, więc efekt hover by nie zadziałał. Z backwards animacja po końcu oddaje kartę zwykłym stylom, a transition może ją unieść.
5. Sekwencja wejść
Na koniec animation-delay sprawia, że karty pojawiają się jedna po drugiej, a nie wszystkie naraz:
.project-card:nth-child(1) { animation-delay: 0.1s; }
.project-card:nth-child(2) { animation-delay: 0.2s; }
.project-card:nth-child(3) { animation-delay: 0.3s; }Każda karta rusza 0,1 sekundy po poprzedniej. Dzięki backwards czeka na swoją kolej niewidoczna i obrócona, zamiast mignąć przed startem.
Bonus: slider bez JavaScriptu
Automatyczny slider działa dzięki klatkom, które powtarzają tę samą wartość. Zapis 0%, 20% { transform: translateX(0); } sprawia, że przez pierwsze 20% cyklu slajd stoi w miejscu, potem przesuwa się o całą szerokość i znów czeka. Przy piętnastosekundowym cyklu każdy slajd zatrzymuje się na 3 sekundy.
Znajdź dwie usterki
W pełnym kodzie kryją się dwie pułapki. Pierwsza siedzi w sekcji umiejętności: :nth-child liczy całe rodzeństwo, a pierwszym dzieckiem sekcji jest nagłówek h2. Reguła .skill-item:nth-child(1) nie trafia więc w nic, opóźnienia i szerokości przesuwają się o jeden, a ostatni pasek dostaje pełną szerokość. Naprawi to :nth-of-type(), który liczy tylko elementy tego samego typu. Druga to czas: ekran ładowania znika po pięciu sekundach, a karty kończą wejście po półtorej sekundy, więc sekwencja rozgrywa się pod zasłoną.
Kompletny kod projektu
Style siedzą w znaczniku style, więc wystarczy zapisać cały plik jako index.html i otworzyć go w przeglądarce:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Animowana Galeria 3D Portfolio</title>
<style>
/* Reset i zmienne */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #6c5ce7;
--secondary-color: #a29bfe;
--accent-color: #fd79a8;
--dark-color: #2d3436;
--light-color: #dfe6e9;
--animation-duration: 20s;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
overflow-x: hidden;
perspective: 1000px;
}
/* Animowane tło */
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
body {
background-size: 200% 200%;
animation: gradientShift 15s ease infinite;
}
/* Loading Screen */
.loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #2d3436;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
animation: hideLoading 3s ease-out forwards;
animation-delay: 2s;
}
@keyframes hideLoading {
to {
opacity: 0;
pointer-events: none;
}
}
.loader {
width: 100px;
height: 100px;
position: relative;
}
.loader-circle {
position: absolute;
width: 100%;
height: 100%;
border: 4px solid transparent;
border-top-color: var(--primary-color);
border-radius: 50%;
animation: rotate 1s linear infinite;
}
.loader-circle:nth-child(2) {
width: 80%;
height: 80%;
top: 10%;
left: 10%;
border-top-color: var(--secondary-color);
animation-duration: 0.8s;
animation-direction: reverse;
}
.loader-circle:nth-child(3) {
width: 60%;
height: 60%;
top: 20%;
left: 20%;
border-top-color: var(--accent-color);
animation-duration: 0.6s;
}
@keyframes rotate {
to { transform: rotate(360deg); }
}
/* Nawigacja */
.navbar {
position: fixed;
top: 0;
width: 100%;
background: rgba(45, 52, 54, 0.9);
backdrop-filter: blur(10px);
padding: 1rem 2rem;
z-index: 1000;
animation: slideDown 0.8s ease-out;
}
@keyframes slideDown {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 2rem;
font-weight: bold;
color: white;
animation: logoGlow 3s ease-in-out infinite;
}
@keyframes logoGlow {
0%, 100% {
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
50% {
text-shadow: 0 0 20px rgba(255, 255, 255, 0.8),
0 0 30px var(--accent-color);
}
}
.nav-links {
display: flex;
gap: 2rem;
list-style: none;
}
.nav-link {
color: white;
text-decoration: none;
position: relative;
transition: color 0.3s ease;
}
.nav-link::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 0;
height: 2px;
background: var(--accent-color);
transition: width 0.3s ease;
}
.nav-link:hover::after {
width: 100%;
}
/* Hero Section z 3D Cube */
.hero {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin-top: -60px;
}
.cube-container {
width: 300px;
height: 300px;
position: relative;
transform-style: preserve-3d;
animation: rotateCube var(--animation-duration) linear infinite;
}
@keyframes rotateCube {
0% { transform: rotateX(0) rotateY(0); }
100% { transform: rotateX(360deg) rotateY(360deg); }
}
.cube-face {
position: absolute;
width: 300px;
height: 300px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.2);
display: flex;
justify-content: center;
align-items: center;
font-size: 3rem;
color: white;
}
.cube-face::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg,
transparent 0%,
var(--primary-color) 50%,
transparent 100%);
opacity: 0;
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% { opacity: 0; }
50% { opacity: 0.5; }
}
.front { transform: translateZ(150px); }
.back { transform: rotateY(180deg) translateZ(150px); }
.left { transform: rotateY(-90deg) translateZ(150px); }
.right { transform: rotateY(90deg) translateZ(150px); }
.top { transform: rotateX(90deg) translateZ(150px); }
.bottom { transform: rotateX(-90deg) translateZ(150px); }
/* Animowane cząsteczki */
.particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
}
.particle {
position: absolute;
width: 4px;
height: 4px;
background: white;
border-radius: 50%;
animation: float linear infinite;
}
@keyframes float {
from {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
to {
transform: translateY(-100vh) rotate(360deg);
opacity: 0;
}
}
.particle:nth-child(1) { left: 10%; animation-duration: 15s; animation-delay: 0s; }
.particle:nth-child(2) { left: 20%; animation-duration: 20s; animation-delay: 2s; }
.particle:nth-child(3) { left: 30%; animation-duration: 18s; animation-delay: 4s; }
.particle:nth-child(4) { left: 40%; animation-duration: 22s; animation-delay: 1s; }
.particle:nth-child(5) { left: 50%; animation-duration: 16s; animation-delay: 3s; }
.particle:nth-child(6) { left: 60%; animation-duration: 19s; animation-delay: 5s; }
.particle:nth-child(7) { left: 70%; animation-duration: 21s; animation-delay: 2s; }
.particle:nth-child(8) { left: 80%; animation-duration: 17s; animation-delay: 4s; }
.particle:nth-child(9) { left: 90%; animation-duration: 23s; animation-delay: 1s; }
.particle:nth-child(10) { left: 95%; animation-duration: 14s; animation-delay: 6s; }
/* Galeria projektów */
.gallery {
padding: 100px 2rem;
max-width: 1200px;
margin: 0 auto;
}
.gallery-title {
text-align: center;
font-size: 3rem;
color: white;
margin-bottom: 3rem;
animation: fadeInUp 1s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.project-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
overflow: hidden;
position: relative;
height: 400px;
transform-style: preserve-3d;
transition: transform 0.6s ease;
animation: cardAppear 0.8s ease-out backwards;
}
.project-card:nth-child(1) { animation-delay: 0.1s; }
.project-card:nth-child(2) { animation-delay: 0.2s; }
.project-card:nth-child(3) { animation-delay: 0.3s; }
.project-card:nth-child(4) { animation-delay: 0.4s; }
.project-card:nth-child(5) { animation-delay: 0.5s; }
.project-card:nth-child(6) { animation-delay: 0.6s; }
@keyframes cardAppear {
from {
opacity: 0;
transform: rotateY(90deg) scale(0.8);
}
to {
opacity: 1;
transform: rotateY(0) scale(1);
}
}
.project-card:hover {
transform: translateY(-10px) rotateX(5deg);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
.project-image {
width: 100%;
height: 60%;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
position: relative;
overflow: hidden;
}
.project-image::before {
content: '▣';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 4rem;
opacity: 0.5;
}
.project-wave {
position: absolute;
bottom: -50%;
left: -50%;
width: 200%;
height: 200%;
background: rgba(255, 255, 255, 0.1);
border-radius: 40%;
animation: wave 10s linear infinite;
}
@keyframes wave {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.project-content {
padding: 1.5rem;
color: white;
}
.project-title {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.project-description {
opacity: 0.8;
line-height: 1.6;
}
/* Automatyczny slider */
.slider {
width: 100%;
height: 500px;
margin: 100px auto;
overflow: hidden;
position: relative;
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
}
.slider-wrapper {
display: flex;
animation: slide 15s ease-in-out infinite;
}
@keyframes slide {
0%, 20% { transform: translateX(0); }
25%, 45% { transform: translateX(-100%); }
50%, 70% { transform: translateX(-200%); }
75%, 95% { transform: translateX(-300%); }
100% { transform: translateX(0); }
}
.slide {
min-width: 100%;
height: 500px;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
color: white;
position: relative;
}
.slide:nth-child(1) { background: linear-gradient(135deg, #667eea, #764ba2); }
.slide:nth-child(2) { background: linear-gradient(135deg, #f093fb, #f5576c); }
.slide:nth-child(3) { background: linear-gradient(135deg, #4facfe, #00f2fe); }
.slide:nth-child(4) { background: linear-gradient(135deg, #fa709a, #fee140); }
.slide-content {
text-align: center;
animation: slideContent 15s ease-in-out infinite;
}
@keyframes slideContent {
0%, 20% { opacity: 1; transform: scale(1); }
22%, 25% { opacity: 0; transform: scale(0.8); }
25%, 45% { opacity: 1; transform: scale(1); }
47%, 50% { opacity: 0; transform: scale(0.8); }
50%, 70% { opacity: 1; transform: scale(1); }
72%, 75% { opacity: 0; transform: scale(0.8); }
75%, 95% { opacity: 1; transform: scale(1); }
97%, 100% { opacity: 0; transform: scale(0.8); }
}
/* Wskaźniki slajdów */
.slider-indicators {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
}
.indicator {
width: 50px;
height: 4px;
background: rgba(255, 255, 255, 0.3);
position: relative;
overflow: hidden;
}
.indicator::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: white;
animation: indicatorFill 15s ease-in-out infinite;
}
.indicator:nth-child(1)::before { animation-delay: 0s; }
.indicator:nth-child(2)::before { animation-delay: 3.75s; }
.indicator:nth-child(3)::before { animation-delay: 7.5s; }
.indicator:nth-child(4)::before { animation-delay: 11.25s; }
@keyframes indicatorFill {
0% { left: -100%; }
25% { left: 0; }
26%, 100% { left: 100%; }
}
/* Sekcja umiejętności z animacjami */
.skills {
padding: 100px 2rem;
max-width: 1200px;
margin: 0 auto;
}
.skills-title {
text-align: center;
font-size: 3rem;
color: white;
margin-bottom: 3rem;
}
.skill-item {
margin-bottom: 2rem;
opacity: 0;
animation: skillAppear 0.8s ease-out forwards;
}
.skill-item:nth-child(1) { animation-delay: 0.2s; }
.skill-item:nth-child(2) { animation-delay: 0.4s; }
.skill-item:nth-child(3) { animation-delay: 0.6s; }
.skill-item:nth-child(4) { animation-delay: 0.8s; }
@keyframes skillAppear {
from {
opacity: 0;
transform: translateX(-50px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.skill-name {
color: white;
margin-bottom: 0.5rem;
}
.skill-bar {
width: 100%;
height: 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
overflow: hidden;
}
.skill-progress {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
border-radius: 10px;
animation: fillBar 2s ease-out forwards;
animation-delay: 1s;
transform-origin: left;
transform: scaleX(0);
}
@keyframes fillBar {
to { transform: scaleX(1); }
}
.skill-item:nth-child(1) .skill-progress { width: 90%; }
.skill-item:nth-child(2) .skill-progress { width: 85%; }
.skill-item:nth-child(3) .skill-progress { width: 95%; }
.skill-item:nth-child(4) .skill-progress { width: 80%; }
/* Footer z animacją */
.footer {
padding: 50px 2rem;
text-align: center;
color: white;
position: relative;
overflow: hidden;
}
.footer::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 2px;
background: linear-gradient(90deg,
transparent,
var(--accent-color),
transparent);
animation: footerLine 3s linear infinite;
}
@keyframes footerLine {
to { left: 100%; }
}
/* Responsywność */
@media (max-width: 768px) {
.cube-container {
width: 200px;
height: 200px;
}
.cube-face {
width: 200px;
height: 200px;
font-size: 2rem;
}
.front { transform: translateZ(100px); }
.back { transform: rotateY(180deg) translateZ(100px); }
.left { transform: rotateY(-90deg) translateZ(100px); }
.right { transform: rotateY(90deg) translateZ(100px); }
.top { transform: rotateX(90deg) translateZ(100px); }
.bottom { transform: rotateX(-90deg) translateZ(100px); }
.gallery-title,
.skills-title {
font-size: 2rem;
}
.nav-links {
display: none;
}
}
</style>
</head>
<body>
<!-- Loading Screen -->
<div class="loading-screen">
<div class="loader">
<div class="loader-circle"></div>
<div class="loader-circle"></div>
<div class="loader-circle"></div>
</div>
</div>
<!-- Animowane cząsteczki tła -->
<div class="particles">
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
<div class="particle"></div>
</div>
<!-- Nawigacja -->
<nav class="navbar">
<div class="nav-container">
<div class="logo">Portfolio 3D</div>
<ul class="nav-links">
<li><a href="#home" class="nav-link">Start</a></li>
<li><a href="#projects" class="nav-link">Projekty</a></li>
<li><a href="#skills" class="nav-link">Umiejętności</a></li>
<li><a href="#contact" class="nav-link">Kontakt</a></li>
</ul>
</div>
</nav>
<!-- Hero Section z 3D Cube -->
<section class="hero" id="home">
<div class="cube-container">
<div class="cube-face front">Front</div>
<div class="cube-face back">Back</div>
<div class="cube-face left">Left</div>
<div class="cube-face right">Right</div>
<div class="cube-face top">Top</div>
<div class="cube-face bottom">Bottom</div>
</div>
</section>
<!-- Automatyczny Slider -->
<div class="slider">
<div class="slider-wrapper">
<div class="slide">
<div class="slide-content">
<h2>Projekt 1</h2>
<p>Strona korporacyjna</p>
</div>
</div>
<div class="slide">
<div class="slide-content">
<h2>Projekt 2</h2>
<p>Aplikacja mobilna</p>
</div>
</div>
<div class="slide">
<div class="slide-content">
<h2>Projekt 3</h2>
<p>E-commerce</p>
</div>
</div>
<div class="slide">
<div class="slide-content">
<h2>Projekt 4</h2>
<p>Dashboard analityczny</p>
</div>
</div>
</div>
<div class="slider-indicators">
<div class="indicator"></div>
<div class="indicator"></div>
<div class="indicator"></div>
<div class="indicator"></div>
</div>
</div>
<!-- Galeria projektów -->
<section class="gallery" id="projects">
<h2 class="gallery-title">Moje Projekty</h2>
<div class="projects-grid">
<div class="project-card">
<div class="project-image">
<div class="project-wave"></div>
</div>
<div class="project-content">
<h3 class="project-title">Projekt Alpha</h3>
<p class="project-description">Nowoczesna aplikacja webowa z wykorzystaniem najnowszych technologii.</p>
</div>
</div>
<div class="project-card">
<div class="project-image">
<div class="project-wave"></div>
</div>
<div class="project-content">
<h3 class="project-title">Projekt Beta</h3>
<p class="project-description">Responsywny sklep internetowy z zaawansowanym systemem płatności.</p>
</div>
</div>
<div class="project-card">
<div class="project-image">
<div class="project-wave"></div>
</div>
<div class="project-content">
<h3 class="project-title">Projekt Gamma</h3>
<p class="project-description">System zarządzania treścią dla dużych przedsiębiorstw.</p>
</div>
</div>
<div class="project-card">
<div class="project-image">
<div class="project-wave"></div>
</div>
<div class="project-content">
<h3 class="project-title">Projekt Delta</h3>
<p class="project-description">Interaktywna platforma edukacyjna z grywalizacją.</p>
</div>
</div>
<div class="project-card">
<div class="project-image">
<div class="project-wave"></div>
</div>
<div class="project-content">
<h3 class="project-title">Projekt Epsilon</h3>
<p class="project-description">Aplikacja mobilna do zarządzania finansami osobistymi.</p>
</div>
</div>
<div class="project-card">
<div class="project-image">
<div class="project-wave"></div>
</div>
<div class="project-content">
<h3 class="project-title">Projekt Zeta</h3>
<p class="project-description">Portal społecznościowy dla miłośników fotografii.</p>
</div>
</div>
</div>
</section>
<!-- Sekcja umiejętności -->
<section class="skills" id="skills">
<h2 class="skills-title">Umiejętności</h2>
<div class="skill-item">
<div class="skill-name">HTML/CSS</div>
<div class="skill-bar">
<div class="skill-progress"></div>
</div>
</div>
<div class="skill-item">
<div class="skill-name">JavaScript</div>
<div class="skill-bar">
<div class="skill-progress"></div>
</div>
</div>
<div class="skill-item">
<div class="skill-name">CSS Animations</div>
<div class="skill-bar">
<div class="skill-progress"></div>
</div>
</div>
<div class="skill-item">
<div class="skill-name">Responsive Design</div>
<div class="skill-bar">
<div class="skill-progress"></div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<p>© 2024 Portfolio 3D. Wszystkie prawa zastrzeżone.</p>
</footer>
</body>
</html>Podsumowanie
Ten projekt demonstruje zaawansowane techniki animacji CSS bez użycia JavaScript:
- @keyframes - 17 różnych animacji
- animation-timing-function - różne funkcje czasowe (linear, ease, ease-in-out)
- animation-delay - sekwencyjne animacje z precyzyjnymi opóźnieniami
- animation-direction - normalne i odwrócone kierunki animacji
- animation-iteration-count - animacje pojedyncze i nieskończone
- animation-fill-mode - forwards i backwards dla zachowania stanów
- Transform 3D - pełna kostka 3D w przestrzeni preserve-3d
- Automatyczny slider - tylko z użyciem CSS
- Animowane wskaźniki postępu - synchronizacja wielu animacji
- Efekty cząsteczkowe - animowane elementy rozmieszczone tak, by wyglądały na przypadkowe
Projekt jest w pełni responsywny i wykorzystuje najnowsze techniki CSS dla stworzenia efektownej prezentacji portfolio bez ani jednej linii JavaScript. Moja rada: dodaj regułę prefers-reduced-motion z poprzedniej lokacji, bo część widzów wyłącza ruch w systemie.
W edytorze poniżej przećwiczysz te techniki na mniejszej scenie z wiekiem sarkofagu. Rodzic .tomb ma już perspective: 500px, a wieko transform-origin: top center, więc wystarczy odkomentować animację openLid i jej @keyframes.
Kod do tej lekcji: style.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(180deg, #1a0a2e, #3d2914);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
.scene {
text-align: center;
padding: 20px;
}
h2 {
margin-bottom: 30px;
color: #f0c040;
}
.tomb {
display: flex;
justify-content: center;
perspective: 500px;
}
.sarcophagus {
position: relative;
width: 200px;
height: 300px;
}
.base {
width: 200px;
height: 280px;
background: linear-gradient(180deg, #d4af37, #b8941e);
border-radius: 10px 10px 5px 5px;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
border: 3px solid #8d6e2e;
position: absolute;
bottom: 0;
}
.lid {
width: 210px;
height: 50px;
background: linear-gradient(180deg, #c9a84c, #a88932);
border-radius: 8px 8px 0 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: bold;
border: 3px solid #8d6e2e;
position: absolute;
top: 0;
left: -5px;
transform-origin: top center;
/* Dodaj animation shorthand tutaj */
/* animation: openLid 3s ease-in-out 1s 1 normal forwards; */
}
/* Zdefiniuj @keyframes openLid */
/* @keyframes openLid { */
/* 0% { transform: rotateX(0deg); } */
/* 100% { transform: rotateX(-120deg); } */
/* } */
.hint {
margin-top: 30px;
color: #aaa;
font-style: italic;
}Pamiętaj: galeria 3D to komnata grobowca, w której perspektywa ustawia widza, klatki kluczowe opowiadają historię, a opóźnienia odsłaniają ją obraz po obrazie.
Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Która właściwość CSS nadana rodzicowi tworzy scenę 3D z głębią dla elementów potomnych?
2. Której funkcji transform użyjesz, aby obracać karuzelę 3D wokół pionowej osi?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w pionie
Uporządkuj kroki budowy animowanej galerii 3D od podstaw do efektu finalnego: