Maîtriser l'intégration et le stylisme des vidéos en CSS : conteneurs responsives, overlays, contrôles personnalisés, arrière-plans vidéo, et composants vidéo modernes.
Les vidéos sont devenues un pilier incontournable du web moderne. En CSS, on ne se contente pas de les afficher : on crée des conteneurs proportionnels, des overlays interactifs, des barres de contrôle personnalisées, et même des sections hero avec vidéo d'arrière-plan. Ce chapitre couvre tout l'arsenal dont vous disposez pour maîtriser les vidéos en CSS.
<video>, <iframe> (YouTube, Vimeo)background (pour les effets décoratifs uniquement)| Capacité | Propriétés CSS | Usage |
|---|---|---|
| Dimensionnement | aspect-ratio, width, max-width |
Vidéos responsives, ratios fixes |
| Ajustement | object-fit, object-position |
Recadrage intelligent dans un conteneur |
| Overlays | position: absolute, opacity |
Bouton play, titre superposé, gradient |
| Contrôles | appearance: none, pseudo-éléments |
Barre de progression personnalisée |
| Plein écran | :fullscreen, object-fit |
Mode plein écran stylé |
| Fond vidéo | Positionnement, overlay | Sections hero avec vidéo d'arrière-plan |
| Lazy loading | loading="lazy", decoding |
Performance et chargement différé |
| Grilles | CSS Grid, aspect-ratio |
Grilles de miniatures vidéo |
La balise <video> est l'élément HTML natif pour intégrer des vidéos. En CSS, on la style comme un conteneur pour contrôler son apparence.
<!-- Vidéo avec contrôles natifs -->
<video controls width="640" height="360">
<source src="video.mp4" type="video/mp4">
Votre navigateur ne supporte pas la vidéo HTML5.
</video>
| Attribut | Description |
|---|---|
controls |
Affiche les contrôles natifs du navigateur (play, volume, barre de progression) |
autoplay |
Lance la vidéo automatiquement (souvent bloqué sans muted) |
muted |
Coupe le son par défaut — requis pour l'autoplay dans la plupart des navigateurs |
loop |
Rejoue la vidéo en boucle à la fin |
playsinline |
Empêche le plein écran automatique sur iOS |
poster |
Image affichée avant le lancement de la vidéo |
preload |
Contrôle le chargement : none, metadata, auto |
width / height |
Définir les dimensions en HTML (améliore le CLS) |
/* Conteneur vidéo responsive 16:9 */
.video-wrapper {
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: 8px;
}
/* Ratio 4:3 pour les vidéos classiques */
.video-wrapper.classic {
aspect-ratio: 4 / 3;
}
/* Ratio 21:9 pour le cinéma */
.video-wrapper.cinema {
aspect-ratio: 21 / 9;
}
object-fit fonctionne exactement comme pour les images — il contrôle comment la vidéo remplit son conteneur.
video {
width: 100%;
height: 100%;
object-fit: cover; /* Remplit le conteneur, coupe le surplus */
object-position: center; /* Centre la vidéo */
}
/* Alternative : contain pour voir toute la vidéo */
video {
object-fit: contain; /* Vidéo entière, espaces restants */
background-color: #000; /* Fond noir pour les bandes */
}
| Valeur | Comportement |
|---|---|
fill |
Remplit le conteneur (défaut) — peut déformer la vidéo |
contain |
Affiche la vidéo entière — peut laisser des bandes noires |
cover |
Remplit le conteneur en conservant le ratio — coupe le surplus |
none |
Aucun ajustement — taille originale |
scale-down |
Comme none ou contain, whichever est le plus petit |
/* Conteneur avec overlay */
.video-container {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: 8px;
cursor: pointer;
}
.video-container video {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* Overlay sombre */
.video-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.3s ease;
}
.video-container:hover .video-overlay {
background: rgba(0, 0, 0, 0.6);
}
/* Bouton play */
.play-btn {
width: 72px;
height: 72px;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease, background 0.3s ease;
}
.play-btn::after {
content: "";
display: block;
width: 0;
height: 0;
border-style: solid;
border-width: 12px 0 12px 22px;
border-color: transparent transparent transparent #1e293b;
margin-left: 4px;
}
.video-container:hover .play-btn {
transform: scale(1.1);
background: white;
}
/* Titre superposé */
.video-title-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
color: white;
padding: 2rem 1.5rem 1.5rem;
}
.video-title-overlay h3 {
margin: 0;
font-size: 1.4rem;
}
.video-title-overlay p {
margin: 0.5rem 0 0;
opacity: 0.8;
font-size: 0.9rem;
}
/* Masquer les contrôles natifs */
video::-webkit-media-controls {
display: none !important;
}
video::-moz-media-controls {
display: none !important;
}
/* Barre de progression personnalisée */
.video-progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
cursor: pointer;
}
.video-progress-fill {
height: 100%;
background: #ef4444;
width: 0%;
transition: width 0.1s linear;
}
.video-progress:hover {
height: 6px;
}
/* Contrôles custom */
.video-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 0.75rem 1rem;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
display: flex;
align-items: center;
gap: 1rem;
opacity: 0;
transition: opacity 0.3s ease;
}
.video-container:hover .video-controls {
opacity: 1;
}
.video-controls button {
background: none;
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
padding: 0.25rem;
}
.video-controls .time {
color: rgba(255, 255, 255, 0.8);
font-size: 0.85rem;
font-variant-numeric: tabular-nums;
}
/* Style en plein écran */
.video-container:fullscreen {
background: black;
display: flex;
align-items: center;
justify-content: center;
}
.video-container:fullscreen video {
width: 100%;
height: 100%;
object-fit: contain;
}
/* Support Safari */
.video-container:-webkit-full-screen {
background: black;
display: flex;
align-items: center;
justify-content: center;
}
/* Section hero avec vidéo de fond */
.hero-video-section {
position: relative;
width: 100%;
height: 100vh;
min-height: 400px;
overflow: hidden;
}
.hero-video-section video {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
min-width: 100%;
min-height: 100%;
transform: translate(-50%, -50%);
object-fit: cover;
}
.hero-video-section .hero-content {
position: relative;
z-index: 2;
color: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
padding: 2rem;
background: rgba(0, 0, 0, 0.3);
}
<!-- Chargement différé (performance) -->
<video
loading="lazy"
decoding="async"
preload="none"
poster="thumbnail.jpg"
>
<source src="video.mp4" type="video/mp4">
</video>
<!-- Autoplay muted (背景 vidéo, pas besoin de son) -->
<video autoplay muted loop playsinline>
<source src="bg-video.mp4" type="video/mp4">
</video>
preload="none" et poster pour les vidéos hors écran. Le navigateur ne chargera la vidéo que lorsqu'elle sera visible, ce qui améliore considérablement les performances.
/* Conteneur responsive pour iframe YouTube/Vimeo */
.iframe-wrapper {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: 8px;
}
.iframe-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
/* Utilisation */
<div class="iframe-wrapper">
<iframe
src="https://www.youtube.com/embed/VIDEO_ID"
title="Titre de la vidéo"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
width et height fixes ne sont pas responsives. Utilisez toujours la technique aspect-ratio: 16/9 avec un position: absolute sur l'iframe.
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.video-thumb {
position: relative;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: 8px;
cursor: pointer;
}
.video-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.video-thumb:hover img {
transform: scale(1.05);
}
/* Badge durée */
.video-thumb .duration {
position: absolute;
bottom: 0.5rem;
right: 0.5rem;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 0.15rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
font-variant-numeric: tabular-nums;
}
.video-card {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.video-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
}
.video-card .card-thumbnail {
position: relative;
aspect-ratio: 16 / 9;
overflow: hidden;
}
.video-card .card-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.video-card .card-info {
padding: 1rem;
display: flex;
gap: 0.75rem;
}
.video-card .card-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
flex-shrink: 0;
}
.video-card .card-title {
font-weight: 600;
font-size: 0.95rem;
margin-bottom: 0.25rem;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.video-card .card-meta {
font-size: 0.8rem;
color: #64748b;
}
Comparaison visuelle des valeurs de object-fit pour les vidéos (ratio 4:3 dans un conteneur carré)
Superposition des couches dans un composant vidéo avec overlay
Grille de miniatures vidéo avec overlay play, durée et métadonnées
video {
width: 100%;
max-width: 800px;
height: auto;
display: block;
border-radius: 8px;
margin: 0 auto;
}
Rendu :
Vidéo responsive — width: 100% + height: auto
.video-16-9 {
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: 8px;
background: #000;
}
.video-16-9 video {
width: 100%;
height: 100%;
object-fit: cover;
}
Rendu :
16:9 — aspect-ratio: 16/9
.video-container {
position: relative;
aspect-ratio: 16 / 9;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
}
.play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 72px;
height: 72px;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease;
}
.play-btn::after {
content: "";
border-style: solid;
border-width: 12px 0 12px 22px;
border-color: transparent transparent transparent #1e293b;
margin-left: 4px;
}
.video-container:hover .play-btn {
transform: translate(-50%, -50%) scale(1.15);
}
Rendu :
.video-with-title {
position: relative;
aspect-ratio: 16 / 9;
border-radius: 8px;
overflow: hidden;
}
.video-title-bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
color: white;
padding: 2rem 1.5rem 1.5rem;
transform: translateY(10px);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.video-with-title:hover .video-title-bar {
transform: translateY(0);
opacity: 1;
}
Rendu :
Survolez pour révéler le titre
.video-card {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.video-card:hover {
transform: translateY(-4px);
}
.video-card .thumb {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.video-card .thumb img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.video-card:hover .thumb img {
transform: scale(1.05);
}
.video-card .info {
padding: 0.75rem;
display: flex;
gap: 0.75rem;
}
.video-card .avatar {
width: 36px;
height: 36px;
border-radius: 50%;
}
.video-card .title {
font-weight: 600;
font-size: 0.95rem;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.video-card .meta {
font-size: 0.8rem;
color: #64748b;
margin-top: 0.25rem;
}
Rendu :
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
.video-thumb {
position: relative;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: 8px;
cursor: pointer;
}
.video-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.video-thumb:hover img {
transform: scale(1.05);
}
.video-thumb .duration {
position: absolute;
bottom: 0.5rem;
right: 0.5rem;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 0.15rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
}
.video-thumb .play-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.3);
opacity: 0;
transition: opacity 0.3s ease;
}
.video-thumb:hover .play-overlay {
opacity: 1;
}
Rendu :
.hero-video {
position: relative;
height: 100vh;
min-height: 400px;
overflow: hidden;
}
.hero-video video {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
min-width: 100%;
min-height: 100%;
transform: translate(-50%, -50%);
object-fit: cover;
}
.hero-video .hero-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.4);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
text-align: center;
padding: 2rem;
}
.hero-video .hero-overlay h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
.hero-video .hero-overlay p {
font-size: 1.2rem;
opacity: 0.9;
max-width: 600px;
}
Rendu :
.iframe-wrapper {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.iframe-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
/* Centré avec max-width */
.iframe-center {
max-width: 800px;
margin: 0 auto;
}
Rendu :
<iframe> YouTube/Vimeo responsive
aspect-ratio: 16/9 + position: absolute
.custom-progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
cursor: pointer;
transition: height 0.2s ease;
}
.custom-progress:hover {
height: 6px;
}
.custom-progress-fill {
height: 100%;
background: #ef4444;
width: 35%;
border-radius: 0 3px 3px 0;
position: relative;
}
.custom-progress-fill::after {
content: "";
position: absolute;
right: -6px;
top: 50%;
transform: translateY(-50%);
width: 12px;
height: 12px;
background: #ef4444;
border-radius: 50%;
opacity: 0;
transition: opacity 0.2s ease;
}
.custom-progress:hover .custom-progress-fill::after {
opacity: 1;
}
Rendu :
Survolez la barre → thumb indicateur apparaît
<!-- Chargement optimisé -->
<video
class="lazy-video"
controls
preload="none"
poster="poster.jpg"
width="800"
height="450"
>
<source src="video.mp4" type="video/mp4">
<source src="video.webm" type="video/webm">
<p>Votre navigateur ne supporte pas la vidéo HTML5.</p>
</video>
/* Style du poster avant chargement */
.lazy-video {
width: 100%;
height: auto;
border-radius: 8px;
background-color: #000;
object-fit: contain;
}
/* Placeholder animé pendant le chargement */
.lazy-video:not([src]) {
background: linear-gradient(90deg, #1e293b 25%, #334155 50%, #1e293b 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
Rendu :
preload="none" + poster = chargement différé optimal
Les iframes YouTube/Vimeo avec width et height fixes cassent le responsive design. Utilisez aspect-ratio: 16/9 avec position: absolute sur l'iframe.
/* ❌ MAUVAIS — Dimensions fixes, pas responsive */
<iframe width="560" height="315" src="..."></iframe>
/* ✅ BON — Responsive avec aspect-ratio */
.video-wrapper {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
}
.video-wrapper iframe {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
border: none;
}
Les navigateurs modernes bloquent l'autoplay avec son. Pour une vidéo d'arrière-plan, combinez toujours autoplay, muted, loop et playsinline.
/* ❌ MAUVAIS — Bloqué par le navigateur */
<video autoplay><source src="bg.mp4"></video>
/* ✅ BON — autoplay autorisé */
<video autoplay muted loop playsinline>
<source src="bg.mp4" type="video/mp4">
</video>
object-fit n'a aucun effet si la vidéo a sa taille naturelle. Il faut imposer width et height (ou aspect-ratio).
/* ❌ MAUVAIS — object-fit sans effet */
video { object-fit: cover; }
/* ✅ BON — Dimensions imposées */
video {
width: 100%;
height: 100%;
object-fit: cover;
}
Sans poster, l'utilisateur voit un écran noir avant le lancement. Ajoutez toujours une image de prévisualisation.
Par défaut, preload="auto" charge toute la vidéo. Pour les vidéos hors écran, utilisez preload="none" ou preload="metadata".
L'attribut title sur les iframes est essentiel pour l'accessibilité. Les lecteurs d'écran en ont besoin pour décrire le contenu.
/* ❌ MAUVAIS — Pas d'accessibilité */
<iframe src="..."></iframe>
/* ✅ BON — Titre descriptif */
<iframe src="..." title="Vidéo: Introduction au CSS"></iframe>
Même avec CSS, définir width et height en HTML sur <video> évite les décalages de mise en page (CLS) pendant le chargement.
aspect-ratio: 16/9 remplace le hack padding-top: 56.25%. C'est plus lisible et maintenable.
Quand les vidéos ont des ratios différents, object-fit: cover garantit un rendu uniforme dans la grille.
Les navigateurs bloquent l'autoplay avec son. muted est obligatoire pour les vidéos décoratives.
L'image poster améliore l'UX et le CLS. Choisissez une image représentative et optimisée.
Économisez la bande passante des utilisateurs en ne chargeant les vidéos que lorsqu'elles sont proches du viewport.
Les iframes YouTube ne se chargent qu'au scroll avec loading="lazy", ce qui améliore significativement le temps de chargement initial.
Définissez width et height sur <video> et les iframes pour éviter les décalages CLS.
Accessibilité obligatoire. Décrivez le contenu de la vidéo pour les lecteurs d'écran.
/* Checklist d'une vidéo optimisée */
<video
controls
width="800"
height="450"
preload="none"
poster="poster.jpg"
playsinline
>
<source src="video.mp4" type="video/mp4">
<source src="video.webm" type="video/webm">
<p>Alternative textuelle</p>
</video>
/* Styles de base */
video {
width: 100%;
height: auto;
border-radius: 8px;
background-color: #000;
}
Défi n°1 — Lecteur vidéo custom
Objectif : Créer un conteneur vidéo avec :
aspect-ratioDéfi n°2 — Grille de miniatures
Objectif : Créer une grille responsive de miniatures vidéo :
auto-fill et minmax(280px, 1fr)aspect-ratio: 16/9Défi n°3 — Section hero vidéo
Objectif : Créer une section hero avec vidéo d'arrière-plan :
object-fit: coverConsigne :
Créez une mini-plateforme vidéo qui combine les concepts vus dans ce chapitre :
| Concept | Propriétés | Usage principal |
|---|---|---|
| Responsive | aspect-ratio, width: 100% |
Vidéos qui s'adaptent au conteneur |
| Ajustement | object-fit, object-position |
Recadrage intelligent dans un conteneur fixe |
| Overlays | position: absolute, opacity, transition |
Bouton play, titre, gradient |
| Contrôles | appearance: none, pseudo-éléments |
Barre de progression personnalisée |
| Plein écran | :fullscreen, object-fit |
Mode plein écran stylé |
| Fond vidéo | Positionnement central, overlay | Sections hero avec vidéo d'arrière-plan |
| Performances | loading="lazy", preload, poster |
Chargement différé et optimisé |
| Iframe | aspect-ratio, position: absolute |
YouTube, Vimeo responsive |
| Grilles | CSS Grid, auto-fill |
Grilles de miniatures vidéo |
autoplay et muted pour les vidéos d'arrière-plan ?object-fit ne fonctionne-t-il pas sans dimensions imposées ?muted débloque l'autoplay.aspect-ratio: 16 / 9 (remplace le hack padding-top: 56.25%)aspect-ratio: 16/9 + iframe en position: absolute fillpreload="none" (ou loading="lazy" pour les iframes)object-fit n'a d'effet que lorsque l'élément a des dimensions différentes de sa taille naturelle/* Conteneur vidéo responsive */
.video-wrapper {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: 8px;
}
.video-wrapper video,
.video-wrapper iframe {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
border: none;
object-fit: cover;
}
/* Overlay au survol */
.video-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.video-wrapper:hover .video-overlay {
opacity: 1;
}
/* Vidéo d'arrière-plan */
.hero-video video {
position: absolute;
top: 50%; left: 50%;
width: 100%; min-width: 100%;
min-height: 100%;
transform: translate(-50%, -50%);
object-fit: cover;
}
/* Chargement optimisé */
<video controls preload="none" poster="poster.jpg"
width="800" height="450">
/* Iframe YouTube responsive */
<div style="aspect-ratio:16/9; position:relative">
<iframe style="position:absolute; inset:0; width:100%; height:100%">
</div>