Cours HTML et CSS · Module 6: Animations CSS
Projet principal - Galerie de portfolio 3D animée
Dans cette leçon5
Les chambres funéraires égyptiennes racontaient des histoires image après image, et le visiteur muni d'une torche les découvrait l'une après l'autre. Ton portfolio peut fonctionner de la même façon : un cube tourne dans l'espace, et les cartes de projets entrent en scène l'une après l'autre. Dans ce projet, nous allons créer une galerie 3D spectaculaire en utilisant uniquement HTML et CSS. La galerie inclura des animations @keyframes avancées, des transformations 3D, des effets de survol et des diaporamas automatiques. Le projet illustre toutes les techniques d'animation CSS de ce module sans utiliser JavaScript.
Description du projet
Notre galerie 3D inclura :
- Carrousel 3D - un cube en rotation avec des images
- Cartes de projet animées - avec effets de survol et transformations
- Diaporama automatique - en utilisant uniquement CSS
- Effets de particules - arrière-plan animé et décorations
- Menu de navigation animé - avec effets de transition
- Écran de chargement - avec des animations avancées
La galerie 3D étape par étape
1. La scène 3D
Tout commence par la scène. perspective, appliquée au parent, fixe la distance entre le spectateur et l'écran et donne de la profondeur à ses enfants, tandis que transform-style: preserve-3d permet aux faces du cube de vivre dans un même espace :
1body {
2 perspective: 1000px;
3}
4
5.cube-container {
6 transform-style: preserve-3d;
7}
8
9.front { transform: translateZ(150px); }
10.back { transform: rotateY(180deg) translateZ(150px); }Chaque face est tournée puis avancée de la moitié de la largeur du cube, soit 150 pixels. perspective agit sur les enfants de l'élément, or le cube est un petit-enfant de body, donc dans ce code il tourne sans effet de raccourci perspectif. Ajoute perspective: 1000px à .hero et compare la profondeur.
2. Les images clés de la rotation
La rotation est décrite par la règle @keyframes, de zéro jusqu'à 360 degrés complets autour des deux axes à la fois :
1@keyframes rotateCube {
2 0% { transform: rotateX(0) rotateY(0); }
3 100% { transform: rotateX(360deg) rotateY(360deg); }
4}rotateY() fait tourner l'élément autour de l'axe vertical, comme un carrousel, et rotateX() autour de l'axe horizontal, comme une noria d'irrigation sur les rives du Nil.
3. L'animation en mouvement
La rotation est lancée par la déclaration animation placée sur le conteneur du cube, avec une durée stockée dans une variable CSS :
1.cube-container {
2 animation: rotateCube var(--animation-duration) linear infinite;
3}La variable --animation-duration vaut 20s dans :root, linear maintient une vitesse constante, et infinite fait tourner le cube sans fin.
4. Les effets de survol sur les cartes
Les cartes de projets se soulèvent en douceur sous le curseur, et elles ont leur propre animation à l'arrivée sur la page :
1.project-card {
2 transition: transform 0.6s ease;
3 animation: cardAppear 0.8s ease-out backwards;
4}
5
6.project-card:hover {
7 transform: translateY(-10px) rotateX(5deg);
8}Le mode backwards est essentiel. Une animation avec forwards maintiendrait la carte sur la dernière image clé et écraserait son transform, donc l'effet de survol ne fonctionnerait pas. Avec backwards, l'animation rend la carte à ses styles habituels une fois terminée, et transition peut alors la soulever.
5. La séquence d'entrée
Enfin, animation-delay fait apparaître les cartes l'une après l'autre, et non toutes en même temps :
1.project-card:nth-child(1) { animation-delay: 0.1s; }
2.project-card:nth-child(2) { animation-delay: 0.2s; }
3.project-card:nth-child(3) { animation-delay: 0.3s; }Chaque carte démarre 0,1 seconde après la précédente. Grâce à backwards, elle attend son tour invisible et inclinée, au lieu d'apparaître brièvement avant le départ.
Bonus : un diaporama sans JavaScript
Le diaporama automatique fonctionne grâce à des images clés qui répètent la même valeur. L'écriture 0%, 20% { transform: translateX(0); } fait que la diapositive reste immobile pendant les premiers 20% du cycle, puis se décale de toute sa largeur et attend de nouveau. Avec un cycle de quinze secondes, chaque diapositive s'arrête pendant 3 secondes.
Trouve les deux défauts
Deux pièges se cachent dans le code complet. Le premier se trouve dans la section des compétences : :nth-child compte toute la fratrie, et le premier enfant de la section est le titre h2. La règle .skill-item:nth-child(1) ne cible donc rien, les délais et les largeurs sont décalés d'un cran, et la dernière barre reçoit la largeur complète. :nth-of-type(), qui ne compte que les éléments du même type, corrige ce problème. Le second concerne le timing : l'écran de chargement disparaît au bout de cinq secondes, alors que les cartes terminent leur entrée au bout d'une seconde et demie, donc toute la séquence se joue derrière le rideau.
Code complet du projet
Les styles se trouvent dans la balise style, il suffit donc d'enregistrer tout le fichier sous le nom index.html et de l'ouvrir dans un navigateur :
1<!DOCTYPE html>
2<html lang="fr">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Galerie de portfolio 3D animée</title>
7 <style>
8 /* Reset et variables */
9 * {
10 margin: 0;
11 padding: 0;
12 box-sizing: border-box;
13 }
14
15 :root {
16 --primary-color: #6c5ce7;
17 --secondary-color: #a29bfe;
18 --accent-color: #fd79a8;
19 --dark-color: #2d3436;
20 --light-color: #dfe6e9;
21 --animation-duration: 20s;
22 }
23
24 body {
25 font-family: 'Arial', sans-serif;
26 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
27 min-height: 100vh;
28 overflow-x: hidden;
29 perspective: 1000px;
30 }
31
32 /* Arrière-plan animé */
33 @keyframes gradientShift {
34 0%, 100% { background-position: 0% 50%; }
35 50% { background-position: 100% 50%; }
36 }
37
38 body {
39 background-size: 200% 200%;
40 animation: gradientShift 15s ease infinite;
41 }
42
43 /* Écran de chargement */
44 .loading-screen {
45 position: fixed;
46 top: 0;
47 left: 0;
48 width: 100%;
49 height: 100%;
50 background: #2d3436;
51 display: flex;
52 justify-content: center;
53 align-items: center;
54 z-index: 9999;
55 animation: hideLoading 3s ease-out forwards;
56 animation-delay: 2s;
57 }
58
59 @keyframes hideLoading {
60 to {
61 opacity: 0;
62 pointer-events: none;
63 }
64 }
65
66 .loader {
67 width: 100px;
68 height: 100px;
69 position: relative;
70 }
71
72 .loader-circle {
73 position: absolute;
74 width: 100%;
75 height: 100%;
76 border: 4px solid transparent;
77 border-top-color: var(--primary-color);
78 border-radius: 50%;
79 animation: rotate 1s linear infinite;
80 }
81
82 .loader-circle:nth-child(2) {
83 width: 80%;
84 height: 80%;
85 top: 10%;
86 left: 10%;
87 border-top-color: var(--secondary-color);
88 animation-duration: 0.8s;
89 animation-direction: reverse;
90 }
91
92 .loader-circle:nth-child(3) {
93 width: 60%;
94 height: 60%;
95 top: 20%;
96 left: 20%;
97 border-top-color: var(--accent-color);
98 animation-duration: 0.6s;
99 }
100
101 @keyframes rotate {
102 to { transform: rotate(360deg); }
103 }
104
105 /* Navigation */
106 .navbar {
107 position: fixed;
108 top: 0;
109 width: 100%;
110 background: rgba(45, 52, 54, 0.9);
111 backdrop-filter: blur(10px);
112 padding: 1rem 2rem;
113 z-index: 1000;
114 animation: slideDown 0.8s ease-out;
115 }
116
117 @keyframes slideDown {
118 from {
119 transform: translateY(-100%);
120 opacity: 0;
121 }
122 to {
123 transform: translateY(0);
124 opacity: 1;
125 }
126 }
127
128 .nav-container {
129 max-width: 1200px;
130 margin: 0 auto;
131 display: flex;
132 justify-content: space-between;
133 align-items: center;
134 }
135
136 .logo {
137 font-size: 2rem;
138 font-weight: bold;
139 color: white;
140 animation: logoGlow 3s ease-in-out infinite;
141 }
142
143 @keyframes logoGlow {
144 0%, 100% {
145 text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
146 }
147 50% {
148 text-shadow: 0 0 20px rgba(255, 255, 255, 0.8),
149 0 0 30px var(--accent-color);
150 }
151 }
152
153 .nav-links {
154 display: flex;
155 gap: 2rem;
156 list-style: none;
157 }
158
159 .nav-link {
160 color: white;
161 text-decoration: none;
162 position: relative;
163 transition: color 0.3s ease;
164 }
165
166 .nav-link::after {
167 content: '';
168 position: absolute;
169 bottom: -5px;
170 left: 0;
171 width: 0;
172 height: 2px;
173 background: var(--accent-color);
174 transition: width 0.3s ease;
175 }
176
177 .nav-link:hover::after {
178 width: 100%;
179 }
180
181 /* Section Hero avec cube 3D */
182 .hero {
183 height: 100vh;
184 display: flex;
185 justify-content: center;
186 align-items: center;
187 position: relative;
188 margin-top: -60px;
189 }
190
191 .cube-container {
192 width: 300px;
193 height: 300px;
194 position: relative;
195 transform-style: preserve-3d;
196 animation: rotateCube var(--animation-duration) linear infinite;
197 }
198
199 @keyframes rotateCube {
200 0% { transform: rotateX(0) rotateY(0); }
201 100% { transform: rotateX(360deg) rotateY(360deg); }
202 }
203
204 .cube-face {
205 position: absolute;
206 width: 300px;
207 height: 300px;
208 background: rgba(255, 255, 255, 0.1);
209 backdrop-filter: blur(10px);
210 border: 2px solid rgba(255, 255, 255, 0.2);
211 display: flex;
212 justify-content: center;
213 align-items: center;
214 font-size: 3rem;
215 color: white;
216 }
217
218 .cube-face::before {
219 content: '';
220 position: absolute;
221 inset: 0;
222 background: linear-gradient(135deg,
223 transparent 0%,
224 var(--primary-color) 50%,
225 transparent 100%);
226 opacity: 0;
227 animation: shimmer 3s ease-in-out infinite;
228 }
229
230 @keyframes shimmer {
231 0%, 100% { opacity: 0; }
232 50% { opacity: 0.5; }
233 }
234
235 .front { transform: translateZ(150px); }
236 .back { transform: rotateY(180deg) translateZ(150px); }
237 .left { transform: rotateY(-90deg) translateZ(150px); }
238 .right { transform: rotateY(90deg) translateZ(150px); }
239 .top { transform: rotateX(90deg) translateZ(150px); }
240 .bottom { transform: rotateX(-90deg) translateZ(150px); }
241
242 /* Particules animées */
243 .particles {
244 position: fixed;
245 top: 0;
246 left: 0;
247 width: 100%;
248 height: 100%;
249 pointer-events: none;
250 overflow: hidden;
251 }
252
253 .particle {
254 position: absolute;
255 width: 4px;
256 height: 4px;
257 background: white;
258 border-radius: 50%;
259 animation: float linear infinite;
260 }
261
262 @keyframes float {
263 from {
264 transform: translateY(100vh) rotate(0deg);
265 opacity: 0;
266 }
267 10% {
268 opacity: 1;
269 }
270 90% {
271 opacity: 1;
272 }
273 to {
274 transform: translateY(-100vh) rotate(360deg);
275 opacity: 0;
276 }
277 }
278
279 .particle:nth-child(1) { left: 10%; animation-duration: 15s; animation-delay: 0s; }
280 .particle:nth-child(2) { left: 20%; animation-duration: 20s; animation-delay: 2s; }
281 .particle:nth-child(3) { left: 30%; animation-duration: 18s; animation-delay: 4s; }
282 .particle:nth-child(4) { left: 40%; animation-duration: 22s; animation-delay: 1s; }
283 .particle:nth-child(5) { left: 50%; animation-duration: 16s; animation-delay: 3s; }
284 .particle:nth-child(6) { left: 60%; animation-duration: 19s; animation-delay: 5s; }
285 .particle:nth-child(7) { left: 70%; animation-duration: 21s; animation-delay: 2s; }
286 .particle:nth-child(8) { left: 80%; animation-duration: 17s; animation-delay: 4s; }
287 .particle:nth-child(9) { left: 90%; animation-duration: 23s; animation-delay: 1s; }
288 .particle:nth-child(10) { left: 95%; animation-duration: 14s; animation-delay: 6s; }
289
290 /* Galerie de projets */
291 .gallery {
292 padding: 100px 2rem;
293 max-width: 1200px;
294 margin: 0 auto;
295 }
296
297 .gallery-title {
298 text-align: center;
299 font-size: 3rem;
300 color: white;
301 margin-bottom: 3rem;
302 animation: fadeInUp 1s ease-out;
303 }
304
305 @keyframes fadeInUp {
306 from {
307 opacity: 0;
308 transform: translateY(30px);
309 }
310 to {
311 opacity: 1;
312 transform: translateY(0);
313 }
314 }
315
316 .projects-grid {
317 display: grid;
318 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
319 gap: 2rem;
320 }
321
322 .project-card {
323 background: rgba(255, 255, 255, 0.1);
324 backdrop-filter: blur(10px);
325 border-radius: 15px;
326 overflow: hidden;
327 position: relative;
328 height: 400px;
329 transform-style: preserve-3d;
330 transition: transform 0.6s ease;
331 animation: cardAppear 0.8s ease-out backwards;
332 }
333
334 .project-card:nth-child(1) { animation-delay: 0.1s; }
335 .project-card:nth-child(2) { animation-delay: 0.2s; }
336 .project-card:nth-child(3) { animation-delay: 0.3s; }
337 .project-card:nth-child(4) { animation-delay: 0.4s; }
338 .project-card:nth-child(5) { animation-delay: 0.5s; }
339 .project-card:nth-child(6) { animation-delay: 0.6s; }
340
341 @keyframes cardAppear {
342 from {
343 opacity: 0;
344 transform: rotateY(90deg) scale(0.8);
345 }
346 to {
347 opacity: 1;
348 transform: rotateY(0) scale(1);
349 }
350 }
351
352 .project-card:hover {
353 transform: translateY(-10px) rotateX(5deg);
354 box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
355 }
356
357 .project-image {
358 width: 100%;
359 height: 60%;
360 background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
361 position: relative;
362 overflow: hidden;
363 }
364
365 .project-image::before {
366 content: '▣';
367 position: absolute;
368 top: 50%;
369 left: 50%;
370 transform: translate(-50%, -50%);
371 font-size: 4rem;
372 opacity: 0.5;
373 }
374
375 .project-wave {
376 position: absolute;
377 bottom: -50%;
378 left: -50%;
379 width: 200%;
380 height: 200%;
381 background: rgba(255, 255, 255, 0.1);
382 border-radius: 40%;
383 animation: wave 10s linear infinite;
384 }
385
386 @keyframes wave {
387 from { transform: rotate(0deg); }
388 to { transform: rotate(360deg); }
389 }
390
391 .project-content {
392 padding: 1.5rem;
393 color: white;
394 }
395
396 .project-title {
397 font-size: 1.5rem;
398 margin-bottom: 0.5rem;
399 }
400
401 .project-description {
402 opacity: 0.8;
403 line-height: 1.6;
404 }
405
406 /* Diaporama automatique */
407 .slider {
408 width: 100%;
409 height: 500px;
410 margin: 100px auto;
411 overflow: hidden;
412 position: relative;
413 background: rgba(255, 255, 255, 0.05);
414 border-radius: 20px;
415 }
416
417 .slider-wrapper {
418 display: flex;
419 animation: slide 15s ease-in-out infinite;
420 }
421
422 @keyframes slide {
423 0%, 20% { transform: translateX(0); }
424 25%, 45% { transform: translateX(-100%); }
425 50%, 70% { transform: translateX(-200%); }
426 75%, 95% { transform: translateX(-300%); }
427 100% { transform: translateX(0); }
428 }
429
430 .slide {
431 min-width: 100%;
432 height: 500px;
433 display: flex;
434 align-items: center;
435 justify-content: center;
436 font-size: 3rem;
437 color: white;
438 position: relative;
439 }
440
441 .slide:nth-child(1) { background: linear-gradient(135deg, #667eea, #764ba2); }
442 .slide:nth-child(2) { background: linear-gradient(135deg, #f093fb, #f5576c); }
443 .slide:nth-child(3) { background: linear-gradient(135deg, #4facfe, #00f2fe); }
444 .slide:nth-child(4) { background: linear-gradient(135deg, #fa709a, #fee140); }
445
446 .slide-content {
447 text-align: center;
448 animation: slideContent 15s ease-in-out infinite;
449 }
450
451 @keyframes slideContent {
452 0%, 20% { opacity: 1; transform: scale(1); }
453 22%, 25% { opacity: 0; transform: scale(0.8); }
454 25%, 45% { opacity: 1; transform: scale(1); }
455 47%, 50% { opacity: 0; transform: scale(0.8); }
456 50%, 70% { opacity: 1; transform: scale(1); }
457 72%, 75% { opacity: 0; transform: scale(0.8); }
458 75%, 95% { opacity: 1; transform: scale(1); }
459 97%, 100% { opacity: 0; transform: scale(0.8); }
460 }
461
462 /* Indicateurs du diaporama */
463 .slider-indicators {
464 position: absolute;
465 bottom: 20px;
466 left: 50%;
467 transform: translateX(-50%);
468 display: flex;
469 gap: 10px;
470 }
471
472 .indicator {
473 width: 50px;
474 height: 4px;
475 background: rgba(255, 255, 255, 0.3);
476 position: relative;
477 overflow: hidden;
478 }
479
480 .indicator::before {
481 content: '';
482 position: absolute;
483 top: 0;
484 left: -100%;
485 width: 100%;
486 height: 100%;
487 background: white;
488 animation: indicatorFill 15s ease-in-out infinite;
489 }
490
491 .indicator:nth-child(1)::before { animation-delay: 0s; }
492 .indicator:nth-child(2)::before { animation-delay: 3.75s; }
493 .indicator:nth-child(3)::before { animation-delay: 7.5s; }
494 .indicator:nth-child(4)::before { animation-delay: 11.25s; }
495
496 @keyframes indicatorFill {
497 0% { left: -100%; }
498 25% { left: 0; }
499 26%, 100% { left: 100%; }
500 }
501
502 /* Section des compétences avec animations */
503 .skills {
504 padding: 100px 2rem;
505 max-width: 1200px;
506 margin: 0 auto;
507 }
508
509 .skills-title {
510 text-align: center;
511 font-size: 3rem;
512 color: white;
513 margin-bottom: 3rem;
514 }
515
516 .skill-item {
517 margin-bottom: 2rem;
518 opacity: 0;
519 animation: skillAppear 0.8s ease-out forwards;
520 }
521
522 .skill-item:nth-child(1) { animation-delay: 0.2s; }
523 .skill-item:nth-child(2) { animation-delay: 0.4s; }
524 .skill-item:nth-child(3) { animation-delay: 0.6s; }
525 .skill-item:nth-child(4) { animation-delay: 0.8s; }
526
527 @keyframes skillAppear {
528 from {
529 opacity: 0;
530 transform: translateX(-50px);
531 }
532 to {
533 opacity: 1;
534 transform: translateX(0);
535 }
536 }
537
538 .skill-name {
539 color: white;
540 margin-bottom: 0.5rem;
541 }
542
543 .skill-bar {
544 width: 100%;
545 height: 20px;
546 background: rgba(255, 255, 255, 0.1);
547 border-radius: 10px;
548 overflow: hidden;
549 }
550
551 .skill-progress {
552 height: 100%;
553 background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
554 border-radius: 10px;
555 animation: fillBar 2s ease-out forwards;
556 animation-delay: 1s;
557 transform-origin: left;
558 transform: scaleX(0);
559 }
560
561 @keyframes fillBar {
562 to { transform: scaleX(1); }
563 }
564
565 .skill-item:nth-child(1) .skill-progress { width: 90%; }
566 .skill-item:nth-child(2) .skill-progress { width: 85%; }
567 .skill-item:nth-child(3) .skill-progress { width: 95%; }
568 .skill-item:nth-child(4) .skill-progress { width: 80%; }
569
570 /* Pied de page avec animation */
571 .footer {
572 padding: 50px 2rem;
573 text-align: center;
574 color: white;
575 position: relative;
576 overflow: hidden;
577 }
578
579 .footer::before {
580 content: '';
581 position: absolute;
582 top: 0;
583 left: -100%;
584 width: 100%;
585 height: 2px;
586 background: linear-gradient(90deg,
587 transparent,
588 var(--accent-color),
589 transparent);
590 animation: footerLine 3s linear infinite;
591 }
592
593 @keyframes footerLine {
594 to { left: 100%; }
595 }
596
597 /* Responsivité */
598 @media (max-width: 768px) {
599 .cube-container {
600 width: 200px;
601 height: 200px;
602 }
603
604 .cube-face {
605 width: 200px;
606 height: 200px;
607 font-size: 2rem;
608 }
609
610 .front { transform: translateZ(100px); }
611 .back { transform: rotateY(180deg) translateZ(100px); }
612 .left { transform: rotateY(-90deg) translateZ(100px); }
613 .right { transform: rotateY(90deg) translateZ(100px); }
614 .top { transform: rotateX(90deg) translateZ(100px); }
615 .bottom { transform: rotateX(-90deg) translateZ(100px); }
616
617 .gallery-title,
618 .skills-title {
619 font-size: 2rem;
620 }
621
622 .nav-links {
623 display: none;
624 }
625 }
626 </style>
627</head>
628<body>
629 <!-- Écran de chargement -->
630 <div class="loading-screen">
631 <div class="loader">
632 <div class="loader-circle"></div>
633 <div class="loader-circle"></div>
634 <div class="loader-circle"></div>
635 </div>
636 </div>
637
638 <!-- Particules d'arrière-plan animées -->
639 <div class="particles">
640 <div class="particle"></div>
641 <div class="particle"></div>
642 <div class="particle"></div>
643 <div class="particle"></div>
644 <div class="particle"></div>
645 <div class="particle"></div>
646 <div class="particle"></div>
647 <div class="particle"></div>
648 <div class="particle"></div>
649 <div class="particle"></div>
650 </div>
651
652 <!-- Navigation -->
653 <nav class="navbar">
654 <div class="nav-container">
655 <div class="logo">Portfolio 3D</div>
656 <ul class="nav-links">
657 <li><a href="#home" class="nav-link">Accueil</a></li>
658 <li><a href="#projects" class="nav-link">Projets</a></li>
659 <li><a href="#skills" class="nav-link">Compétences</a></li>
660 <li><a href="#contact" class="nav-link">Contact</a></li>
661 </ul>
662 </div>
663 </nav>
664
665 <!-- Section Hero avec cube 3D -->
666 <section class="hero" id="home">
667 <div class="cube-container">
668 <div class="cube-face front">Front</div>
669 <div class="cube-face back">Back</div>
670 <div class="cube-face left">Left</div>
671 <div class="cube-face right">Right</div>
672 <div class="cube-face top">Top</div>
673 <div class="cube-face bottom">Bottom</div>
674 </div>
675 </section>
676
677 <!-- Diaporama automatique -->
678 <div class="slider">
679 <div class="slider-wrapper">
680 <div class="slide">
681 <div class="slide-content">
682 <h2>Projet 1</h2>
683 <p>Site web d'entreprise</p>
684 </div>
685 </div>
686 <div class="slide">
687 <div class="slide-content">
688 <h2>Projet 2</h2>
689 <p>Application mobile</p>
690 </div>
691 </div>
692 <div class="slide">
693 <div class="slide-content">
694 <h2>Projet 3</h2>
695 <p>E-commerce</p>
696 </div>
697 </div>
698 <div class="slide">
699 <div class="slide-content">
700 <h2>Projet 4</h2>
701 <p>Tableau de bord analytique</p>
702 </div>
703 </div>
704 </div>
705 <div class="slider-indicators">
706 <div class="indicator"></div>
707 <div class="indicator"></div>
708 <div class="indicator"></div>
709 <div class="indicator"></div>
710 </div>
711 </div>
712
713 <!-- Galerie de projets -->
714 <section class="gallery" id="projects">
715 <h2 class="gallery-title">Mes projets</h2>
716 <div class="projects-grid">
717 <div class="project-card">
718 <div class="project-image">
719 <div class="project-wave"></div>
720 </div>
721 <div class="project-content">
722 <h3 class="project-title">Projet Alpha</h3>
723 <p class="project-description">Une application web moderne utilisant les dernières technologies.</p>
724 </div>
725 </div>
726 <div class="project-card">
727 <div class="project-image">
728 <div class="project-wave"></div>
729 </div>
730 <div class="project-content">
731 <h3 class="project-title">Projet Beta</h3>
732 <p class="project-description">Une boutique en ligne responsive avec un système de paiement avancé.</p>
733 </div>
734 </div>
735 <div class="project-card">
736 <div class="project-image">
737 <div class="project-wave"></div>
738 </div>
739 <div class="project-content">
740 <h3 class="project-title">Projet Gamma</h3>
741 <p class="project-description">Un système de gestion de contenu pour les grandes entreprises.</p>
742 </div>
743 </div>
744 <div class="project-card">
745 <div class="project-image">
746 <div class="project-wave"></div>
747 </div>
748 <div class="project-content">
749 <h3 class="project-title">Projet Delta</h3>
750 <p class="project-description">Une plateforme éducative interactive avec gamification.</p>
751 </div>
752 </div>
753 <div class="project-card">
754 <div class="project-image">
755 <div class="project-wave"></div>
756 </div>
757 <div class="project-content">
758 <h3 class="project-title">Projet Epsilon</h3>
759 <p class="project-description">Une application mobile pour la gestion des finances personnelles.</p>
760 </div>
761 </div>
762 <div class="project-card">
763 <div class="project-image">
764 <div class="project-wave"></div>
765 </div>
766 <div class="project-content">
767 <h3 class="project-title">Projet Zeta</h3>
768 <p class="project-description">Un portail social pour les passionnés de photographie.</p>
769 </div>
770 </div>
771 </div>
772 </section>
773
774 <!-- Section des compétences -->
775 <section class="skills" id="skills">
776 <h2 class="skills-title">Compétences</h2>
777 <div class="skill-item">
778 <div class="skill-name">HTML/CSS</div>
779 <div class="skill-bar">
780 <div class="skill-progress"></div>
781 </div>
782 </div>
783 <div class="skill-item">
784 <div class="skill-name">JavaScript</div>
785 <div class="skill-bar">
786 <div class="skill-progress"></div>
787 </div>
788 </div>
789 <div class="skill-item">
790 <div class="skill-name">Animations CSS</div>
791 <div class="skill-bar">
792 <div class="skill-progress"></div>
793 </div>
794 </div>
795 <div class="skill-item">
796 <div class="skill-name">Responsive Design</div>
797 <div class="skill-bar">
798 <div class="skill-progress"></div>
799 </div>
800 </div>
801 </section>
802
803 <!-- Pied de page -->
804 <footer class="footer">
805 <p>© 2024 Portfolio 3D. Tous droits réservés.</p>
806 </footer>
807</body>
808</html>Récapitulatif
Ce projet illustre des techniques d'animation CSS avancées sans utiliser JavaScript :
- @keyframes - 17 animations différentes
- animation-timing-function - différentes fonctions de timing (linear, ease, ease-in-out)
- animation-delay - animations séquentielles avec des délais précis
- animation-direction - sens d'animation normal et inversé
- animation-iteration-count - animations uniques et infinies
- animation-fill-mode - forwards et backwards pour préserver les états
- Transform 3D - un cube 3D complet dans l'espace preserve-3d
- Diaporama automatique - en utilisant uniquement CSS
- Indicateurs de progression animés - synchronisation de plusieurs animations
- Effets de particules - éléments animés disposés de façon à paraître aléatoires
Le projet est entièrement responsive et utilise les techniques CSS les plus récentes pour créer une présentation de portfolio impressionnante sans une seule ligne de JavaScript. Mon conseil : ajoute la règle prefers-reduced-motion du lieu précédent, car une partie des visiteurs désactive les animations dans leur système.
Dans l'éditeur ci-dessous, tu vas t'exercer à ces techniques sur une scène plus petite, avec le couvercle d'un sarcophage. Le parent .tomb a déjà perspective: 500px, et le couvercle transform-origin: top center, il suffit donc de décommenter l'animation openLid et ses @keyframes.
Code de cette leçon : style.css
1* {
2 margin: 0;
3 padding: 0;
4 box-sizing: border-box;
5}
6
7body {
8 font-family: 'Segoe UI', sans-serif;
9 background: linear-gradient(180deg, #1a0a2e, #3d2914);
10 min-height: 100vh;
11 display: flex;
12 justify-content: center;
13 align-items: center;
14 color: white;
15}
16
17.scene {
18 text-align: center;
19 padding: 20px;
20}
21
22h2 {
23 margin-bottom: 30px;
24 color: #f0c040;
25}
26
27.tomb {
28 display: flex;
29 justify-content: center;
30 perspective: 500px;
31}
32
33.sarcophagus {
34 position: relative;
35 width: 200px;
36 height: 300px;
37}
38
39.base {
40 width: 200px;
41 height: 280px;
42 background: linear-gradient(180deg, #d4af37, #b8941e);
43 border-radius: 10px 10px 5px 5px;
44 display: flex;
45 align-items: center;
46 justify-content: center;
47 font-size: 40px;
48 border: 3px solid #8d6e2e;
49 position: absolute;
50 bottom: 0;
51}
52
53.lid {
54 width: 210px;
55 height: 50px;
56 background: linear-gradient(180deg, #c9a84c, #a88932);
57 border-radius: 8px 8px 0 0;
58 display: flex;
59 align-items: center;
60 justify-content: center;
61 font-size: 18px;
62 font-weight: bold;
63 border: 3px solid #8d6e2e;
64 position: absolute;
65 top: 0;
66 left: -5px;
67 transform-origin: top center;
68 /* Ajoute la syntaxe raccourcie animation ici */
69 /* animation: openLid 3s ease-in-out 1s 1 normal forwards; */
70}
71
72/* Définis @keyframes openLid */
73/* @keyframes openLid { */
74/* 0% { transform: rotateX(0deg); } */
75/* 100% { transform: rotateX(-120deg); } */
76/* } */
77
78.hint {
79 margin-top: 30px;
80 color: #aaa;
81 font-style: italic;
82}Souviens-toi : une galerie 3D est une chambre funéraire où la perspective place le spectateur, où les images clés racontent l'histoire et où les délais la dévoilent image après image.
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Quelle propriété CSS, appliquée au parent, crée une scène 3D avec de la profondeur pour ses éléments enfants ?
2. Quelle fonction transform fait pivoter un carrousel 3D autour de l'axe vertical ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Tri vertical
Classe les étapes de construction de la galerie 3D animée, des fondations jusqu'à l'effet final :