Cours JavaScript et React · Module 7: Styles et animations

Projet : Panneau de contrôle de mission cosmique

4 min de lecture
Dans cette leçon4

Félicitations, Navigateur ! Tu es arrivé à la fin de ce module et il est temps de combiner toutes les techniques de style que tu as apprises dans un projet complet. Ta mission est de créer un Panneau de contrôle de mission cosmique - un tableau de bord interactif qui exploite les connaissances de tout le module.

Objectif du projet

Tu vas construire un panneau de contrôle de vaisseau qui combine :

  • Styled Components avec un theming dynamique
  • Animations CSS avec @keyframes
  • Mise en page responsive avec CSS Grid et Flexbox
  • Theming (mode clair/sombre)
  • Éléments interactifs avec effets de survol et transitions

Architecture du projet

Le projet se compose de plusieurs sections clés :

1. Système de thèmes (Theming)

Nous utilisons ThemeProvider de styled-components pour gérer les couleurs et les styles à travers toute l'application :

1const darkTheme = {
2  bg: '#0a0a1a',
3  surface: '#12122a',
4  accent: '#00ff88',
5  text: '#e0e0e0',
6  danger: '#ff4444',
7  warning: '#ffaa00'
8};
9
10const lightTheme = {
11  bg: '#f0f4f8',
12  surface: '#ffffff',
13  accent: '#0066ff',
14  text: '#1a1a2e',
15  danger: '#cc0000',
16  warning: '#cc8800'
17};

2. Animations CSS

Le panneau utilise plusieurs types d'animations que tu as appris dans le module :

1/* Pulsation du statut */
2@keyframes pulse {
3  0%, 100% { opacity: 1; }
4  50% { opacity: 0.5; }
5}
6
7/* Glissement avec décalage */
8@keyframes slideIn {
9  from {
10    opacity: 0;
11    transform: translateY(20px);
12  }
13  to {
14    opacity: 1;
15    transform: translateY(0);
16  }
17}
18
19/* Rotation du radar */
20@keyframes rotate {
21  from { transform: rotate(0deg); }
22  to { transform: rotate(360deg); }
23}

3. Styled Components avec props

Stylisation dynamique des composants en fonction des props passées :

1const StatusBadge = styled.span`
2  padding: 4px 12px;
3  border-radius: 12px;
4  font-size: 12px;
5  font-weight: bold;
6  background: ${props => props.active ? '#00ff8833' : '#ff444433'};
7  color: ${props => props.active ? '#00ff88' : '#ff4444'};
8  animation: pulse 2s infinite;
9`;
10
11const Card = styled.div`
12  background: ${props => props.theme.surface};
13  border: 1px solid ${props => props.theme.accent}33;
14  border-radius: 12px;
15  padding: 20px;
16  transition: transform 0.3s ease, box-shadow 0.3s ease;
17
18  &:hover {
19    transform: translateY(-4px);
20    box-shadow: 0 8px 25px rgba(0, 255, 136, 0.15);
21  }
22`;

4. Mise en page responsive avec CSS Grid

Le panneau s'adapte automatiquement à la largeur de l'écran :

1.dashboard-grid {
2  display: grid;
3  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
4  gap: 20px;
5  padding: 20px;
6}
7
8@media (max-width: 768px) {
9  .dashboard-grid {
10    grid-template-columns: 1fr;
11  }
12}

5. Structure complète des composants

Le composant principal App rassemble tous les éléments :

1function App() {
2  const [isDark, setIsDark] = useState(true);
3
4  return (
5    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
6      <GlobalStyle />
7      <Header>
8        <Title>Mission Control</Title>
9        <ThemeToggle onClick={() => setIsDark(!isDark)}>
10          {isDark ? 'Clair' : 'Sombre'}
11        </ThemeToggle>
12      </Header>
13      <DashboardGrid>
14        <StatusCard systems={systemsData} />
15        <RadarCard />
16        <MissionsCard missions={missionsData} />
17        <CrewCard crew={crewData} />
18      </DashboardGrid>
19    </ThemeProvider>
20  );
21}

Techniques clés utilisées dans le projet

TechniqueUtilisation dans le projet
Styled ComponentsTous les composants UI
ThemeProviderBascule mode sombre/clair
@keyframesPulsation du statut, rotation du radar, entrée des cartes
CSS GridMise en page du tableau de bord
FlexboxMise en page interne des cartes
transitionEffets de survol sur les cartes et les boutons
Props dynamiquesCouleurs de statut, styles conditionnels
Responsivitéauto-fit et minmax dans la grille

Résumé du module

Dans ce module, tu as appris tout l'éventail des techniques de style dans React :

  1. CSS classique et CSS Modules - les fondations du style avec isolation des classes
  2. Styled Components - CSS-in-JS avec props dynamiques et theming
  3. SCSS - préprocesseur avec variables, mixins et imbrication
  4. Tailwind CSS - approche utility-first pour un prototypage rapide
  5. Frameworks UI - bibliothèques de composants prêtes à l'emploi (MUI, Ant Design, Chakra)
  6. Design systems - construire des systèmes de design cohérents
  7. React Transition Group - animer le cycle de vie des composants
  8. Animations CSS - @keyframes, transition et effets avancés

Chacune de ces techniques a sa place dans l'arsenal d'un programmeur cosmique. Les meilleurs ingénieurs savent les combiner en un système cohérent - exactement comme dans ce projet.

Essaie le panneau ci-dessous et expérimente avec le code - change les couleurs du thème, ajoute de nouvelles animations ou enrichis les sections du tableau de bord !

Code de cette leçon : App.jsx
1import React, { useState } from 'react';
2import styled, { keyframes, css, ThemeProvider, createGlobalStyle } from 'styled-components';
3
4// ===== THEMES =====
5const darkTheme = {
6  bg: '#0a0a1a',
7  surface: '#12122a',
8  surfaceHover: '#1a1a3a',
9  accent: '#00ff88',
10  accentDim: 'rgba(0,255,136,0.15)',
11  text: '#e0e0e0',
12  textSecondary: '#8888aa',
13  danger: '#ff4444',
14  warning: '#ffaa00',
15  border: 'rgba(0,255,136,0.2)',
16};
17
18const lightTheme = {
19  bg: '#f0f4f8',
20  surface: '#ffffff',
21  surfaceHover: '#e8ecf0',
22  accent: '#0066ff',
23  accentDim: 'rgba(0,102,255,0.1)',
24  text: '#1a1a2e',
25  textSecondary: '#666688',
26  danger: '#cc0000',
27  warning: '#cc8800',
28  border: 'rgba(0,102,255,0.2)',
29};
30
31// ===== GLOBAL STYLES =====
32const GlobalStyle = createGlobalStyle`
33  * { margin: 0; padding: 0; box-sizing: border-box; }
34  body {
35    font-family: 'Segoe UI', Tahoma, sans-serif;
36    background: ${props => props.theme.bg};
37    color: ${props => props.theme.text};
38    transition: background 0.3s ease, color 0.3s ease;
39  }
40`;
41
42// ===== ANIMATIONS =====
43const pulse = keyframes`
44  0%, 100% { opacity: 1; }
45  50% { opacity: 0.5; }
46`;
47
48const slideIn = keyframes`
49  from { opacity: 0; transform: translateY(20px); }
50  to { opacity: 1; transform: translateY(0); }
51`;
52
53const rotate = keyframes`
54  from { transform: rotate(0deg); }
55  to { transform: rotate(360deg); }
56`;
57
58const glow = keyframes`
59  0%, 100% { box-shadow: 0 0 5px ${() => 'rgba(0,255,136,0.3)'}; }
60  50% { box-shadow: 0 0 20px ${() => 'rgba(0,255,136,0.6)'}; }
61`;
62
63// ===== LAYOUT COMPONENTS =====
64const Header = styled.header`
65  display: flex;
66  justify-content: space-between;
67  align-items: center;
68  padding: 16px 24px;
69  background: ${p => p.theme.surface};
70  border-bottom: 1px solid ${p => p.theme.border};
71`;
72
73const Title = styled.h1`
74  font-size: 20px;
75  color: ${p => p.theme.accent};
76  letter-spacing: 2px;
77  text-transform: uppercase;
78`;
79
80const ThemeToggle = styled.button`
81  background: ${p => p.theme.accentDim};
82  border: 1px solid ${p => p.theme.accent};
83  color: ${p => p.theme.accent};
84  padding: 8px 16px;
85  border-radius: 8px;
86  cursor: pointer;
87  font-size: 14px;
88  transition: all 0.3s ease;
89  &:hover {
90    background: ${p => p.theme.accent};
91    color: ${p => p.theme.bg};
92  }
93`;
94
95const DashboardGrid = styled.div`
96  display: grid;
97  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
98  gap: 20px;
99  padding: 24px;
100`;
101
102const Card = styled.div`
103  background: ${p => p.theme.surface};
104  border: 1px solid ${p => p.theme.border};
105  border-radius: 12px;
106  padding: 20px;
107  animation: ${slideIn} 0.5s ease ${p => p.delay || '0s'} both;
108  transition: transform 0.3s ease, box-shadow 0.3s ease;
109  &:hover {
110    transform: translateY(-4px);
111    box-shadow: 0 8px 25px ${p => p.theme.accentDim};
112  }
113`;
114
115const CardTitle = styled.h3`
116  font-size: 14px;
117  text-transform: uppercase;
118  letter-spacing: 1px;
119  color: ${p => p.theme.accent};
120  margin-bottom: 16px;
121  display: flex;
122  align-items: center;
123  gap: 8px;
124`;
125
126// ===== STATUS COMPONENTS =====
127const StatusBadge = styled.span`
128  display: inline-block;
129  padding: 3px 10px;
130  border-radius: 12px;
131  font-size: 11px;
132  font-weight: bold;
133  background: ${p => p.active ? 'rgba(0,255,136,0.15)' : 'rgba(255,68,68,0.15)'};
134  color: ${p => p.active ? '#00ff88' : '#ff4444'};
135  animation: ${pulse} 2s infinite;
136`;
137
138const SystemRow = styled.div`
139  display: flex;
140  justify-content: space-between;
141  align-items: center;
142  padding: 8px 0;
143  border-bottom: 1px solid ${p => p.theme.border};
144  &:last-child { border-bottom: none; }
145`;
146
147// ===== RADAR COMPONENTS =====
148const RadarWrapper = styled.div`
149  width: 120px;
150  height: 120px;
151  border-radius: 50%;
152  border: 2px solid ${p => p.theme.accent};
153  position: relative;
154  margin: 0 auto 16px;
155  overflow: hidden;
156  background: radial-gradient(circle, ${p => p.theme.accentDim} 0%, transparent 70%);
157`;
158
159const RadarSweep = styled.div`
160  position: absolute;
161  top: 0; left: 50%;
162  width: 2px; height: 50%;
163  background: ${p => p.theme.accent};
164  transform-origin: bottom center;
165  animation: ${rotate} 3s linear infinite;
166`;
167
168const RadarDot = styled.div`
169  position: absolute;
170  width: 6px; height: 6px;
171  background: ${p => p.theme.accent};
172  border-radius: 50%;
173  top: ${p => p.y}%;
174  left: ${p => p.x}%;
175  animation: ${pulse} 1.5s infinite;
176`;
177
178// ===== PROGRESS BAR =====
179const ProgressOuter = styled.div`
180  width: 100%;
181  height: 6px;
182  background: ${p => p.theme.border};
183  border-radius: 3px;
184  margin-top: 6px;
185`;
186
187const ProgressInner = styled.div`
188  height: 100%;
189  width: ${p => p.value || 0}%;
190  background: ${p =>
191    p.value > 75 ? '#00ff88' :
192    p.value > 40 ? '#ffaa00' : '#ff4444'};
193  border-radius: 3px;
194  transition: width 0.5s ease;
195`;
196
197// ===== CREW AVATAR =====
198const AvatarCircle = styled.div`
199  width: 36px;
200  height: 36px;
201  border-radius: 50%;
202  background: ${p => p.color || p.theme.accent};
203  display: flex;
204  align-items: center;
205  justify-content: center;
206  font-size: 14px;
207  font-weight: bold;
208  color: #fff;
209`;
210
211const CrewRow = styled.div`
212  display: flex;
213  align-items: center;
214  gap: 12px;
215  padding: 8px 0;
216`;
217
218const CrewInfo = styled.div`
219  flex: 1;
220  & span { display: block; font-size: 12px; color: ${p => p.theme.textSecondary}; }
221`;
222
223// ===== DATA =====
224const systems = [
225  { name: 'Moteurs warp', active: true },
226  { name: 'Boucliers énergétiques', active: true },
227  { name: 'Systèmes de navigation', active: true },
228  { name: 'Communication longue portée', active: false },
229  { name: 'Maintien des fonctions vitales', active: true },
230];
231
232const missions = [
233  { name: 'Exploration de la nébuleuse X-7', progress: 87 },
234  { name: 'Transport de ravitaillement', progress: 45 },
235  { name: 'Cartographie des astéroïdes', progress: 62 },
236  { name: 'Réparation de la station relais', progress: 15 },
237];
238
239const crew = [
240  { name: 'Cpt. Nova', role: 'Commandante', color: '#e74c3c', initial: 'N' },
241  { name: 'Lt. Orion', role: 'Navigateur', color: '#3498db', initial: 'O' },
242  { name: 'Dr. Stella', role: 'Médecin', color: '#9b59b6', initial: 'S' },
243  { name: 'Ing. Vega', role: 'Ingénieur', color: '#2ecc71', initial: 'V' },
244];
245
246// ===== APP =====
247function App() {
248  const [isDark, setIsDark] = useState(true);
249  const theme = isDark ? darkTheme : lightTheme;
250
251  return (
252    <ThemeProvider theme={theme}>
253      <GlobalStyle />
254      <Header>
255        <Title>Mission Control</Title>
256        <ThemeToggle onClick={() => setIsDark(!isDark)}>
257          {isDark ? 'Light Mode' : 'Dark Mode'}
258        </ThemeToggle>
259      </Header>
260
261      <DashboardGrid>
262        {/* Systems Status */}
263        <Card delay="0s">
264          <CardTitle>Systèmes du vaisseau</CardTitle>
265          {systems.map((s, i) => (
266            <SystemRow key={i}>
267              <span>{s.name}</span>
268              <StatusBadge active={s.active}>
269                {s.active ? 'ONLINE' : 'OFFLINE'}
270              </StatusBadge>
271            </SystemRow>
272          ))}
273        </Card>
274
275        {/* Radar */}
276        <Card delay="0.1s">
277          <CardTitle>Radar</CardTitle>
278          <RadarWrapper>
279            <RadarSweep />
280            <RadarDot x={30} y={25} />
281            <RadarDot x={65} y={40} />
282            <RadarDot x={45} y={70} />
283          </RadarWrapper>
284          <p style={{ textAlign: 'center', fontSize: 13, opacity: 0.7 }}>
285            3 objets à portée du scanner
286          </p>
287        </Card>
288
289        {/* Missions Progress */}
290        <Card delay="0.2s">
291          <CardTitle>Missions</CardTitle>
292          {missions.map((m, i) => (
293            <div key={i} style={{ marginBottom: 12 }}>
294              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
295                <span>{m.name}</span>
296                <span style={{ fontWeight: 'bold' }}>{m.progress}%</span>
297              </div>
298              <ProgressOuter>
299                <ProgressInner value={m.progress} />
300              </ProgressOuter>
301            </div>
302          ))}
303        </Card>
304
305        {/* Crew */}
306        <Card delay="0.3s">
307          <CardTitle>Équipage</CardTitle>
308          {crew.map((c, i) => (
309            <CrewRow key={i}>
310              <AvatarCircle color={c.color}>{c.initial}</AvatarCircle>
311              <CrewInfo>
312                <strong>{c.name}</strong>
313                <span>{c.role}</span>
314              </CrewInfo>
315              <StatusBadge active={true}>En service</StatusBadge>
316            </CrewRow>
317          ))}
318        </Card>
319      </DashboardGrid>
320    </ThemeProvider>
321  );
322}
323
324export default App;

Tu as repéré une erreur dans cette leçon ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Construis un design system complet comprenant : styled-components pour le style au niveau des composants, des patterns CSS-in-JS, un theme provider avec modes sombre/clair, un design responsive avec breakpoints, des mises en page CSS Grid et Flexbox, des animations avec @keyframes, un système typographique, des tokens de couleur, un système d'espacement et des fonctionnalités d'accessibilité (états de focus, ARIA). Crée un design system digne des interfaces les plus avancées de Deep Space Nine !

Articles utiles