2.3 Advanced CSS Animations
Dive into the fascinating world of advanced CSS animations and discover how to create captivating and interactive user experiences. Master modern animation techniques to bring your web interfaces to life with fluidity and elegance.
Educational objectives
- Mastering advanced CSS animation properties and keyframes
- Create smooth transitions and complex visual effects
- Optimize animation performance for a better user experience
- Implement responsive and accessible animations
- Develop modern and interactive animated UI components
Mastering @keyframes
Keyframes are the core of advanced CSS animations. They allow you to define the intermediate stages of an animation, creating smooth transitions between different states. Unlike simple transitions, keyframes offer precise control over each moment of the animation, enabling sophisticated effects such as bounces, wobbles, or complex transformations.
Advanced animation properties
The `animation-timing-function` property is crucial for creating natural movements. The `cubic-bezier` function allows you to customize the acceleration and deceleration, while `animation-fill-mode` controls the element's state before and after the animation. `animation-direction` can reverse or alternate the cycle, and `animation-iteration-count` defines the number of repetitions.
Key point : Use animation-play-state to dynamically control the playback of animations with JavaScript, creating advanced user interactions.
Key points to remember
Keyframes offer granular control over animations, allowing for the creation of complex and natural movements through custom timing functions.
🎯 Mini-exercise: Morphing animation with keyframes
Create a morphing loader that transforms a circle into a square and then into a triangle, with custom timing functions for a smooth and natural effect.
Reference code:
<div class="morphing-loader"> <div class="shape"></div> </div>
.morphing-loader { display: flex; justify-content: center; align-items: center; height: 200px; background: radial-gradient(circle, #667eea, #764ba2); } .shape { width: 60px; height: 60px; background: linear-gradient(45deg, #ff6b6b, #4ecdc4); border-radius: 50%; animation: morphing 3s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite; box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3); } @keyframes morphing { 0% { border-radius: 50%; transform: rotate(0deg) scale(1); background: linear-gradient(45deg, #ff6b6b, #4ecdc4); } 33% { border-radius: 10%; transform: rotate(180deg) scale(1.2); background: linear-gradient(45deg, #4ecdc4, #45b7d1); } 66% { border-radius: 0%; transform: rotate(360deg) scale(0.8); background: linear-gradient(45deg, #45b7d1, #96ceb4); clip-path: polygon(50% 0%, 0% 100%, 100% 100%); } 100% { border-radius: 50%; transform: rotate(720deg) scale(1); background: linear-gradient(45deg, #ff6b6b, #4ecdc4); clip-path: none; } }
// Interactive animation control const shape = document.querySelector('.shape'); let isPaused = false; shape.addEventListener('click', () => { if (isPaused) { shape.style.animationPlayState = 'running'; } else { shape.style.animationPlayState = 'paused'; } isPaused = !isPaused; });
Monaco Editor v0.45
Three-dimensional space in CSS
3D transformations open a new dimension to web animations. The perspective property defines the distance between the user and the z=0 plane, creating a realistic depth effect. Transform-style: preserve-3d maintains the 3D transformation of child elements, essential for creating complex objects like cubes or flippable cards.
Advanced Transformation Techniques
The translateZ, rotateX, rotateY, and rotateZ functions allow you to position and orient elements in 3D space. The backface-visibility property controls the visibility of the back face of a transformed element, which is crucial for flipping effects. Using these properties together allows you to create immersive and modern interfaces.
Key point : Perspective must be applied to the parent element to affect all its children, creating a consistent 3D space for all animations.
Key points to remember
3D transformations require proper perspective setup and the use of preserve-3d to maintain spatial consistency in complex animations.
🎯 Mini-exercise: 3D card flip with perspective
Build an interactive map that flips into 3D on hover, revealing content on its back side with realistic perspective effects.
Reference code:
<div class="card-container">
<div class="flip-card">
<div class="flip-card-inner">
<div class="flip-card-front">
<div class="card-content">
<h3>Frontend Master</h3>
<div class="card-icon">🎨</div>
<p>Hover to reveal</p>
</div>
</div>
<div class="flip-card-back">
<div class="card-content">
<h3>Skills Unlocked</h3>
<ul>
<li>CSS Animations</li>
<li>3D Transforms</li>
<li>Modern UI/UX</li>
</ul>
</div>
</div>
</div>
</div>
</div>
.card-container { display: flex; justify-content: center; align-items: center; height: 300px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); perspective: 1000px; } .flip-card { width: 280px; height: 200px; position: relative; cursor: pointer; } .flip-card-inner { position: relative; width: 100%; height: 100%; text-align: center; transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275); transform-style: preserve-3d; border-radius: 16px; box-shadow: 0 16px 40px rgba(0, 0, 0, 0.12); } .flip-card:hover .flip-card-inner { transform: rotateY(180deg); } .flip-card-front, .flip-card-back { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; border-radius: 16px; display:flex; align-items: center; justify-content: center; } .flip-card-front { background: linear-gradient(135deg, #ff6b6b 0%, #4ecdc4 100%); color:white; } .flip-card-back { background: linear-gradient(135deg, #4ecdc4 0%, #44a08d 100%); color:white; transform: rotateY(180deg); } .card-content h3 { margin: 0 0 16px 0; font-size: 24px; font-weight: 700; } .card-icon { font-size: 48px; margin: 16px 0; } .card-content ul { list-style: none; padding: 0; margin: 16px 0; } .card-content li { margin: 8px 0; padding: 8px 16px; background: rgba(255, 255, 255, 0.2); border-radius: 20px; backdrop-filter: blur(10px); }
// Interactive animation on click const flipCard = document.querySelector('.flip-card'); let isFlipped = false; flipCard.addEventListener('click', () => { const inner = flipCard.querySelector('.flip-card-inner'); if (isFlipped) { inner.style.transform = 'rotateY(0deg)'; } else { inner.style.transform = 'rotateY(180deg)'; } isFlipped = !isFlipped; });
Monaco Editor v0.45
Glassmorphism and transparency effects
Glassmorphism is a major design trend that uses backdrop filters to create frosted glass effects. This technique combines transparency, background blur, and subtle edges to simulate glass surfaces. The effect is particularly effective on colored or textured backgrounds, creating striking visual depth.
Advanced morphing techniques
The clip-path and mask properties allow for the creation of sophisticated morphing animations. Clip-path can smoothly transform geometric shapes, while masks offer endless creative possibilities. Blend modes (mix-blend-mode) add merging effects between elements, creating unique visual interactions.
Key point : Backdrop-filter requires a semi-transparent background to be visible and may impact performance on some older browsers.
Key points to remember
Modern visual effects combine transparency, filters and morphing to create immersive interfaces that respect contemporary design trends.
🎯 Mini-exercise: Animated glassmorphism interface
Create a control panel with glassmorphism effect, hover morphing animations and smooth backdrop-filter transitions.
Reference code:
<div class="glass-interface">
<div class="glass-panel">
<div class="panel-header">
<h3>Control Panel</h3>
<div class="status-dot"></div>
</div>
<div class="control-grid">
<button class="glass-button" data-action="power">
<span class="button-icon">⚡</span>
<span class="button-label">Power</span>
</button>
<button class="glass-button" data-action="settings">
<span class="button-icon">⚙️</span>
<span class="button-label">Settings</span>
</button>
<button class="glass-button" data-action="network">
<span class="button-icon">🌐</span>
<span class="button-label">Network</span>
</button>
<button class="glass-button" data-action="security">
<span class="button-icon">🔒</span>
<span class="button-label">Security</span>
</button>
</div>
</div>
</div>
.glass-interface { display: flex; justify-content: center; align-items: center; height: 400px; background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); position: relative; } .glass-interface::before { content: ''; position: absolute; top: 20%; left: 10%; width: 200px; height: 200px; background: radial-gradient(circle, rgba(255, 255, 255, 0.3), transparent); border-radius: 50%; filter: blur(40px); animation: float 6s ease-in-out infinite; } .glass-panel { backdrop-filter: blur(16px) saturate(180%); background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 24px; padding: 32px; box-shadow: 0 24px 48px rgba(0, 0, 0, 0.1); transition: all 0.4s cubic-bezier(0.23, 1, 0.320, 1); min-width: 320px; } .glass-panel:hover { backdrop-filter: blur(20px) saturate(200%); background: rgba(255, 255, 255, 0.18); transform: translateY(-8px); box-shadow: 0 32px 64px rgba(0, 0, 0, 0.15); } .panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; } .panel-header h3 { margin: 0; color:white; font-size: 24px; font-weight: 600; } .status-dot { width: 12px; height: 12px; background: #4ade80; border-radius: 50%; animation: pulse 2s infinite; box-shadow: 0 0 16px #4ade80; } .control-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; } .glass-button { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 16px; padding: 20px; cursor: pointer; transition: all 0.3s cubic-bezier(0.23, 1, 0.320, 1); backdrop-filter: blur(8px); color:white; display:flex; flex-direction: column; align-items: center; gap: 8px; } .glass-button:hover { background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(12px); transform: scale(1.05) translateY(-2px); box-shadow: 0 16px 32px rgba(0, 0, 0, 0.1); } .glass-button:active { transform: scale(0.95); } .button-icon { font-size: 24px; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2)); } .button-label { font-size: 14px; font-weight: 500; } @keyframes float { 0%, 100% { transform: translate(0, 0) rotate(0deg); } 33% { transform: translate(30px, -30px) rotate(120deg); } 66% { transform: translate(-20px, 20px) rotate(240deg); } } @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.7; transform: scale(1.1); } }
// Advanced interactions with states const glassButtons = document.querySelectorAll('.glass-button'); const statusDot = document.querySelector('.status-dot'); glassButtons.forEach(button => { button.addEventListener('click', (e) => { const action = e.currentTarget.dataset.action; // Feedback animation button.style.background = 'rgba(255, 255, 255, 0.3)'; button.style.transform = 'scale(0.9)'; setTimeout(() => { button.style.background = ''; button.style.transform = ''; }, 200); // Status change statusDot.style.background = '#f59e0b'; setTimeout(() => { statusDot.style.background = '#4ade80'; }, 1000);
Monaco Editor v0.45
Optimizing animation performance
CSS animations can significantly impact performance if not properly optimized. Properties that trigger reflow (width, height, position) are resource-intensive and should be avoided in favor of transform and opacity, which only require the composite phase. Using will-change informs the browser about the elements that will be animated, allowing for advance preparation.
GPU and hardware acceleration
GPU acceleration can be forced with `transform: translateZ(0)` or `will-change`, shifting rendering to the graphics processor for smoother animations. However, this technique should be used judiciously as it consumes video memory. The 60 FPS rule requires maintaining each frame under 16.67ms, necessitating monitoring via developer tools.
Key point : Always prioritize transform and opacity for animations, as these properties avoid reflow and repaint, using only the browser's composite layer.
Key points to remember
Animation optimization relies on the use of composite properties (transform, opacity) and intelligent management of GPU acceleration via will-change.
🎯 Mini-exercise: Optimized particle system
Develop a floating particle system using only transform and opacity, with will-change for GPU optimization and 60fps performance.
Reference code:
<div class="particle-system">
<div class="performance-monitor">
<span class="fps-counter">FPS: 60</span>
<button class="toggle-particles">Toggle Animation</button>
</div>
<div class="particles-container">
<div class="particle" style="--delay: 0s; --duration: 4s; --x: 10%; --y: 20%;"></div>
<div class="particle" style="--delay: 0.5s; --duration: 5s; --x: 30%; --y: 80%;"></div>
<div class="particle" style="--delay: 1s; --duration: 3.5s; --x: 60%; --y: 40%;"></div>
<div class="particle" style="--delay: 1.5s; --duration: 4.5s; --x: 80%; --y: 60%;"></div>
<div class="particle" style="--delay: 2s; --duration: 3s; --x: 20%; --y: 90%;"></div>
<div class="particle" style="--delay: 2.5s; --duration: 4s; --x: 70%; --y: 10%;"></div>
<div class="particle" style="--delay: 3s; --duration: 5.5s; --x: 40%; --y: 70%;"></div>
<div class="particle" style="--delay: 3.5s; --duration: 3.5s; --x: 90%; --y: 30%;"></div>
</div>
</div>
.particle-system { position: relative; width: 100%; height: 400px; background: radial-gradient(ellipse at center, #1e3c72 0%, #2a5298 50%, #000 100%); overflow:hidden; border-radius: 12px; } .performance-monitor { position: absolute; top: 16px; left: 16px; z-index: 10; display:flex; gap: 12px; align-items: center; } .fps-counter { background: rgba(0, 0, 0, 0.7); color: #4ade80; padding: 8px 12px; border-radius: 20px; font-family: minivan; font-size: 12px; backdrop-filter: blur(4px); } .toggle-particles { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.3); color:white; padding: 8px 16px; border-radius: 20px; cursor: pointer; font-size: 12px; backdrop-filter: blur(4px); transition: background 0.3s ease; } .toggle-particles:hover { background: rgba(255, 255, 255, 0.2); } .particles-container { position: relative; width: 100%; height: 100%; } .particle { position: absolute; width: 8px; height: 8px; background: radial-gradient(circle, #4ade80, #22d3ee); border-radius: 50%; left: var(--x); top: var(--y); /* Critical GPU optimization */ will-change: transform, opacity; transform: translateZ(0); /* Animation using only composite properties */ animation: particleFloat var(--duration) ease-in-out var(--delay) infinite, particleFade var(--duration) ease-in-out var(--delay) infinite; box-shadow: 0 0 16px currentColor, 0 0 32px rgba(34, 211, 238, 0.3); } .particle::before { content: ''; position: absolute; top: -2px; left: -2px; right: -2px; bottom: -2px; background: inherit; border-radius: 50%; opacity: 0.3; filter: blur(4px); will-change: transform; transform: translateZ(0); } /* Optimized animations - only transform and opacity */ @keyframes particleFloat { 0% { transform: translateZ(0) translate(0, 0) scale(1) rotate(0deg); opacity: 0; } 10% { opacity: 1; } 25% { transform: translateZ(0) translate(20px, -30px) scale(1.2) rotate(90deg); } 50% { transform: translateZ(0) translate(-15px, -60px) scale(0.8) rotate(180deg); } 75% { transform: translateZ(0) translate(25px, -90px) scale(1.1) rotate(270deg); } 90% { opacity: 1; } 100% { transform: translateZ(0) translate(-10px, -120px) scale(0.5) rotate(360deg); opacity: 0; } } @keyframes particleFade { 0%, 100% { opacity: 0; } 50% { opacity: 1; } } /* Paused state for performance monitoring */ .particles-container.paused .particle { animation-play-state: paused; }
// Performance monitoring system class PerformanceMonitor { constructor() { this.fps = 0; this.lastTime = performance.now(); this.frameCount = 0; this.fpsDisplay = document.querySelector('.fps-counter'); this.isRunning = true; this.measureFPS(); } measureFPS() { const currentTime = performance.now(); this.frameCount++; if (currentTime - this.lastTime >= 1000) { this.fps = Math.round((this.frameCount * 1000) / (currentTime - this.lastTime)); this.updateDisplay(); this.frameCount = 0; this.lastTime = currentTime; } if (this.isRunning) { requestAnimationFrame(() => this.measureFPS()); } } updateDisplay() { const color = this.fps >= 55 ? '#4ade80': this.fps >= 30? '#f59e0b': '#ef4444'; this.fpsDisplay.textContent = `FPS: ${this.fps}`; this.fpsDisplay.style.color = color; } stop() { this.isRunning = false; } } // Animation control const particlesContainer = document.querySelector('.particles-container'); const toggleButton = document.querySelector('.toggle-particles'); const monitor = new PerformanceMonitor(); let isAnimating = true; toggleButton.addEventListener('click', () => { isAnimating = !isAnimating; if (isAnimating) { particlesContainer.classList.remove('paused'); toggleButton.textContent = 'Pause Animation'; } else { particlesContainer.classList.add('paused'); toggleButton.textContent = 'Resume Animation'; } });
Monaco Editor v0.45
Respect for user preferences
Accessible animations are crucial for an inclusive experience. The media query `prefers-reduced-motion` detects if the user has disabled animations in their system preferences. This directive must be followed by reducing or removing movement for people sensitive to animations or suffering from vestibular disorders.
Responsive and adaptive animations
Animations must adapt to different screen sizes and device capabilities. On mobile, complex animations can be simplified to conserve battery life and ensure smooth gameplay. Using specific media queries allows for adjusting the duration, intensity, or even disabling certain animations on less powerful devices.
Key point Always implement prefers-reduced-motion to offer a static or reduced alternative to users who need it, thus respecting accessibility standards.
Key points to remember
Accessibility of animations requires adherence to prefers-reduced-motion and responsive adaptation to ensure an inclusive experience on all devices.
🎯 Mini-exercise: Adaptive and accessible interface
Build a navigation menu with animations respecting prefers-reduced-motion, responsive design and accessible status indicators.
Reference code:
<nav class="accessible-nav" role="navigation" aria-label="Main Menu">
<div class="nav-brand">
<h1>AccessNav</h1>
</div>
<button class="nav-toggle" aria-expanded="false" aria-controls="nav-menu" aria-label="Open the navigation menu">
<span class="hamburger">
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
</span>
<span class="sr-only">Menu</span>
</button>
<ul class="nav-menu" id="nav-menu" role="menubar">
<li class="nav-item" role="none">
<a href="#home" class="nav-link" role="menuitem" aria-current="page">
<span class="nav-icon" aria-hidden="true">🏠</span>
<span class="nav-text">Welcome</span>
</a>
</li>
<li class="nav-item" role="none">
<a href="#about" class="nav-link" role="menuitem">
<span class="nav-icon" aria-hidden="true">👤</span>
<span class="nav-text">About</span>
</a>
</li>
<li class="nav-item" role="none">
<a href="#services" class="nav-link" role="menuitem">
<span class="nav-icon" aria-hidden="true">⚙️</span>
<span class="nav-text">Services</span>
</a>
</li>
<li class="nav-item" role="none">
<a href="#contact" class="nav-link" role="menuitem">
<span class="nav-icon" aria-hidden="true">📧</span>
<span class="nav-text">Contact</span>
</a>
</li>
</ul>
<div class="accessibility-settings">
<button class="a11y-toggle" aria-label="Reduce animations" title="Reduce animations">
<span class="a11y-icon">🎭</span>
</button>
</div>
</nav>
<main class="content-area">
<p>Main content of the page...</p>
</main>
/* Accessibility utility */ .sr-only { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important; } .accessible-nav { display: flex; justify-content: space-between; align-items: center; padding: 16px 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color:white; position: relative; z-index: 1000; } .nav-brand h1 { margin: 0; font-size: 24px; font-weight: 700; } .nav-toggle { display: none; background:none; border:none; cursor: pointer; padding: 8px; color:white; position: relative; z-index: 1001; } .hamburger { display: flex; flex-direction: column; gap: 4px; } .hamburger-line { width: 24px; height: 3px; background: currentColor; border-radius: 2px; transition: all 0.3s cubic-bezier(0.23, 1, 0.320, 1); } /* Hamburger states */ .nav-toggle[aria-expanded="true"] .hamburger-line:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); } .nav-toggle[aria-expanded="true"] .hamburger-line:nth-child(2) { opacity: 0; } .nav-toggle[aria-expanded="true"] .hamburger-line:nth-child(3) { transform: rotate(-45deg) translate(7px, -6px); } .nav-menu { display: flex; list-style: none; margin: 0; padding: 0; gap: 8px; } .nav-link { display: flex; align-items: center; gap: 8px; padding: 12px 20px; color:white; text-decoration: none; border-radius: 12px; transition: all 0.3s cubic-bezier(0.23, 1, 0.320, 1); position: relative; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(8px); } .nav-link::before { content: ''; position: absolute; bottom: 0; left: 50%; width: 0; height: 3px; background:white; border-radius: 2px; transition: all 0.3s cubic-bezier(0.23, 1, 0.320, 1); transform: translateX(-50%); } .nav-link:hover, .nav-link:focus { background: rgba(255, 255, 255, 0.2); transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); } .nav-link:hover::before, .nav-link:focus::before, .nav-link[aria-current="page"]::before { width: 80%; } .nav-link[aria-current="page"] { background: rgba(255, 255, 255, 0.25); font-weight: 600; } .accessibility-settings { display: flex; align-items: center; } .a11y-toggle { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.3); color:white; padding: 8px; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; backdrop-filter: blur(4px); } .a11y-toggle:hover { background: rgba(255, 255, 255, 0.2); } .content-area { padding: 40px 24px; color: #333; } /* Responsive design */ @media (max-width: 768px) { .nav-toggle { display: block; } .nav-menu { position: fixed; top: 0; left: -100%; width: 100%; height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); flex-direction: column; justify-content: center; align-items: center; gap: 24px; transition: left 0.4s cubic-bezier(0.23, 1, 0.320, 1); backdrop-filter: blur(16px); } .nav-menu.is-open { left: 0; } .nav-link { font-size: 18px; padding: 16px 24px; min-width: 200px; justify-content: center; } .accessibility-settings { position: fixed; top: 20px; right: 80px; z-index: 1002; } } /* Respect accessibility preferences */ @media (prefers-reduced-motion: reduce) { .hamburger-line, .nav-link, .nav-link::before, .nav-menu, .a11y-toggle { transition: none !important; animation: none !important; } .nav-link:hover, .nav-link:focus { transform: none !important; } /* Static flags for users with reduced animations */ .nav-link[aria-current="page"] { border-left: 4px solid white; border-radius: 0 12px 12px 0; } } /* Dark mode and high contrast */ @media (prefers-contrast: high) { .accessible-nav { background: #000; border-bottom: 2px solid #fff; } .nav-link { background: none; border: 2px solid transparent; } .nav-link:hover, .nav-link:focus, .nav-link[aria-current="page"] { border-color: white; background: rgba(255, 255, 255, 0.1); } }
// Gestion accessible du menu mobile
class AccessibleNavigation {
constructor() {
this.navToggle = document.querySelector('.nav-toggle');
this.navMenu = document.querySelector('.nav-menu');
this.navLinks = document.querySelectorAll('.nav-link');
this.a11yToggle = document.querySelector('.a11y-toggle');
this.isOpen = false;
this.reducedMotion = false;
this.initEventListeners();
this.checkReducedMotionPreference();
}
initEventListeners() {
// Toggle menu mobile
this.navToggle.addEventListener('click', (e) => {
e.preventDefault();
this.toggleMenu();
});
// Fermer menu avec Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isOpen) {
this.closeMenu();
}
});
// Navigation au clavier
this.navLinks.forEach((link, index) => {
link.addEventListener('keydown', (e) => {
this.handleKeyNavigation(e, index);
});
// Mise à jour aria-current
link.addEventListener('click', (e) => {
this.updateCurrentPage(e.target);
});
});
// Toggle animations
this.a11yToggle.addEventListener('click', () => {
this.toggleReducedMotion();
});
// Fermer menu mobile si redimensionnement
window.addEventListener('resize', () => {
if (window.innerWidth > 768 && this.isOpen) {
this.closeMenu();
}
});
}
toggleMenu() {
this.isOpen = !this.isOpen;
this.navToggle.setAttribute('aria-expanded', this.isOpen);
this.navToggle.setAttribute('aria-label',
this.isOpen ? 'Fermer le menu de navigation' : 'Ouvrir le menu de navigation'
);
if (this.isOpen) {
this.navMenu.classList.add('is-open');
// Focus sur le premier lien
this.navLinks[0].focus();
// Empêcher le scroll du body
document.body.style.overflow = 'hidden';
} else {
this.navMenu.classList.remove('is-open');
// Restaurer le scroll
document.body.style.overflow = '';
// Retour focus sur le toggle
this.navToggle.focus();
}
}
closeMenu() {
this.isOpen = false;
this.navToggle.setAttribute('aria-expanded', 'false');
this.navToggle.setAttribute('aria-label', 'Ouvrir le menu de navigation');
this.navMenu.classList.remove('is-open');
document.body.style.overflow = '';
this.navToggle.focus();
}
handleKeyNavigation(e, currentIndex) {
let targetIndex;
switch (e.key) {
case 'ArrowDown':
case 'Arrow
Summary
Key points to remember
- Modern CSS properties Flexbox, Grid and CSS variables allow for flexible and maintainable layouts.
- Responsive Design Media queries and relative units ensure adaptation to all screens.
- CSS Animations Keyframes and transitions create smooth and engaging visual effects.
- Performance : Prioritize transform and opacity for high-performance animations at 60fps.
- Accessibility : Respect prefers-reduced-motion for motion-sensitive users.
Sources
To deepen your knowledge:
Validate your knowledge
Test your understanding with this 10-question quiz:

