Cours HTML et CSS · Module 6: Animations CSS

Projet principal - Galerie de portfolio 3D animée

15 min de lecture
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>&copy; 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 :

  1. @keyframes - 17 animations différentes
  2. animation-timing-function - différentes fonctions de timing (linear, ease, ease-in-out)
  3. animation-delay - animations séquentielles avec des délais précis
  4. animation-direction - sens d'animation normal et inversé
  5. animation-iteration-count - animations uniques et infinies
  6. animation-fill-mode - forwards et backwards pour préserver les états
  7. Transform 3D - un cube 3D complet dans l'espace preserve-3d
  8. Diaporama automatique - en utilisant uniquement CSS
  9. Indicateurs de progression animés - synchronisation de plusieurs animations
  10. 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. 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. 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 :

Articles utiles