Cours JavaScript et React · Module 7: Styles et animations
Projet : Panneau de contrôle de mission cosmique
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
| Technique | Utilisation dans le projet |
|---|---|
| Styled Components | Tous les composants UI |
| ThemeProvider | Bascule mode sombre/clair |
@keyframes | Pulsation du statut, rotation du radar, entrée des cartes |
| CSS Grid | Mise en page du tableau de bord |
| Flexbox | Mise en page interne des cartes |
transition | Effets de survol sur les cartes et les boutons |
| Props dynamiques | Couleurs 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 :
- CSS classique et CSS Modules - les fondations du style avec isolation des classes
- Styled Components - CSS-in-JS avec props dynamiques et theming
- SCSS - préprocesseur avec variables, mixins et imbrication
- Tailwind CSS - approche utility-first pour un prototypage rapide
- Frameworks UI - bibliothèques de composants prêtes à l'emploi (MUI, Ant Design, Chakra)
- Design systems - construire des systèmes de design cohérents
- React Transition Group - animer le cycle de vie des composants
- Animations CSS -
@keyframes,transitionet 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 !