Comment faire une application pleine page ?
Introduction
Une application pleine page est une interface sans barre de défilement au niveau de la page : l’ensemble du contenu reste visible en permanence, comme c’est le cas par exemple sur Figma ou sur la plupart des tableaux de bord conçus pour offrir une vision d’ensemble. Mettre en place un tel affichage est une tâche relativement courante, mais elle recèle tout un lot de difficultés dont il faut savoir prendre compte. Voici un petit florilège d’astuces et de pièges à éviter.
Nous verrons d’abord quelques pièges courants à éviter lorsqu’on tente de mettre en place un tel affichage, puis des approches possibles pour y arriver.
Les pièges à éviter
La tentation du 100vh
Une manière souvent employée pour forcer un élément à prendre
toute la hauteur de la page est d’utiliser une unité relative à la zone d’affichage ; plus spécifiquement : vh.
Par exemple, pour faire en sorte que le contenu prenne toute la
hauteur de la page, on pourrait être tenté d’utiliser : 100vh :
Code
<style> body { height: 100vh; background-color: aquamarine; }</style><body> <p>Contenu.</p></body>Résultat
Le problème est que sur les écrans de téléphone portable, la
zone d’affichage du navigateur est dynamique (elle possède une
barre d’adresse en haut et des boutons de navigation en bas)
alors que l’unité vh est statique et fait toujours référence
à la hauteur maximale possible, ce qui peut mener à des résultats
indésirables si l’on veut qu’un élément fasse exactement la hauteur de l’écran.
Une manière de palier ce problème est d’utiliser les unités d’affichage dynamiques (svh et dvh), mais comme ce sont des
unités relativement nouvelles, vous devez faire attention si
vous souhaitez assurer une compatibilité pour les plus anciens
navigateurs. Voir la compatibilité de dvh sur CanIuse.
Pour une meilleure compatibilité, je préfère généralement
utiliser height: 100%. Attention, la propriété height en CSS possède un fonctionnement très spécifique et il est très
facile de se sentir perdu si l’on ne comprend pas comment elle fonctionne.
Je vous recommande vivement cet article de Josh Comeau sur le sujet.
Les marges
Partons du principe que vous avez opté pour l’approche des pourcentages pour votre application pleine page, mais vous constatez que l’élément ne prend pas toute la hauteur comme prévu :
Code
<style> html, body { height: 100%; }
body { margin: 8px; background-color: aquamarine; }</style><body> <p>Contenu.</p></body>Résultat
Le décalage vient des marges du body. Contrairement
au remplissage (padding) ou à la bordure, une marge
s’ajoute en dehors de la boîte de l’élément. Avec
height: 100 %, le navigateur calcule donc
100 % de hauteur plus les marges, d’où le débordement
et la barre de défilement. Les navigateurs appliquent par défaut
une marge de 8 px sur le body.
La correction la plus directe est de remettre les marges à zéro
sur
html et body. Si vous souhaitez
conserver un espacement intérieur, préférez le padding et assurez-vous d’utiliser box-sizing: border-box
: sans cela, un padding s’ajouterait lui aussi à la
hauteur totale, avec le même effet indésirable.
Code
<style> *, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body { padding: 8px; background-color: aquamarine; }</style><body> <p>Contenu.</p></body>Résultat
Ici, le fond aquamarine remplit bien la page, sans barre de
défilement superflue. Le padding de 8 px est absorbé
grâce à border-box, et
height: 100 % fixe la hauteur à celle de la zone
d’affichage.
Cas pratique
Voyons ensemble un cas pratique. L’objectif est de reproduire la
structure suivante : un en-tête fixe, une barre latérale de
filtres et une zone principale avec des boutons et un diagramme.
Seules les zones de contenu défilent en interne ; le
body ne possède pas de barre de défilement. Pour y parvenir,
deux approches équivalentes existent ; nous les détaillerons chacune
dans leur propre sous-section.
La chaîne de hauteur
Contrairement à l’exemple du 100vh vu plus haut, nous
utilisons ici height: 100 %. Pour que ce
pourcentage se résolve, il faut une chaîne ininterrompue depuis
la racine :
html → body → …
On commence donc par fixer explicitement la hauteur sur
html et body :
html,body { height: 100%;} Difficulté : sans height: 100 %
sur html, le pourcentage sur body ne se
résout à rien de concret et le conteneur grandit avec son contenu.
Si vous utilisez des frameworks comme React, il faut aussi appliquer
height: 100 %
sur l'élément d'ancrage (#root, etc.) pour
prolonger cette chaîne jusqu'au composant principal.
Le body en colonne flex
Le body devient un conteneur flex en colonne qui occupe
toute la hauteur disponible :
body { display: flex; flex-direction: column; gap: 1rem; padding: 16px;}
Le padding de 16 px est absorbé grâce au
box-sizing: border-box déclaré plus haut : il est inclus
dans les 100 %, et non ajouté par-dessus.
Difficulté : si le body n’a pas de
hauteur fixée, il s’allonge avec son contenu et c’est lui qui défile.
C’est précisément ce qu’on veut éviter.
La zone principale
L’en-tête garde sa hauteur naturelle. La zone
.content reçoit flex: 1 pour occuper tout
l’espace vertical restant, puis devient un conteneur flex en ligne
pour accueillir la barre latérale et la colonne de droite :
.content { flex: 1; display: flex; gap: 1rem;}
.right-stuff { flex: 1; display: flex; flex-direction: column; gap: 1rem;}
.diagram { flex: 1;} Difficulté : la liste de filtres compte une
trentaine d’éléments. Sans intervention supplémentaire, ces
éléments imposent une hauteur minimale à leurs parents flex
(comportement par défaut : min-height: auto) et le body finit par déborder.
Approche avec min-height: 0
La première solution repose sur un duo de propriétés, à placer sur chaque maillon de la chaîne flex jusqu’aux zones qui doivent défiler :
.content,.filters,.right-stuff { min-height: 0;}
.filters,.diagram { overflow: auto;} min-height: 0 autorise un enfant flex à se comprimer
en dessous de la taille de son contenu. Sans cela,
flex: 1 ne suffit pas : l’élément refuse de rétrécir
et repousse toute la page.
overflow: auto déplace ensuite le défilement au bon
endroit : la barre latérale bleue et la zone rose du diagramme défilent
en interne, tandis que le body reste fixe à la hauteur
de la zone d’affichage.
Difficulté : min-height: 0 seul coupe
le contenu sans offrir de défilement ; overflow: auto
seul sur un parent qui n’a pas de hauteur contrainte ne produit aucun
effet. Les deux propriétés fonctionnent ensemble.
Code
<style> *, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body { display: flex; flex-direction: column; gap: 1rem; padding: 16px; }
.content { flex: 1; display: flex; gap: 1rem; min-height: 0; }
.filters { overflow: auto; min-height: 0; }
.right-stuff { flex: 1; display: flex; flex-direction: column; gap: 1rem; min-height: 0; }
.diagram { flex: 1; overflow: auto; }</style><body> <header>HEADER</header> <div class="content"> <div class="filters">…</div> <div class="right-stuff"> <div class="toggles">…</div> <div class="diagram">…</div> </div> </div></body>Résultat
Approche avec height: 100%
Une alternative consiste à remplacer min-height: 0
par une chaîne de height: 100 % sur chaque maillon,
complétée par overflow: auto là où c’est nécessaire :
.content,.filters,.right-stuff { height: 100%;}
.content,.filters,.diagram { overflow: auto;} height: 100 % propage la hauteur du parent sur
chaque niveau (.content, puis
.filters ou .right-stuff). En
revanche,
overflow: auto n’est pas requis sur tous ces maillons :
seulement là où l’élément doit à la fois se contraindre et défiler.
.content en a besoin car c’est un enfant flex (flex: 1) du body : sans
overflow: auto (ou min-height: 0), sa
hauteur minimale reste celle de son contenu et la chaîne de
pourcentages ne démarre pas. Une fois .content
contraint, .right-stuff obtient une hauteur résolue
via height: 100 % seul ; le défilement est alors
géré par .diagram (flex: 1 + overflow: auto).
Difficulté : la règle « height: 100 %
+ overflow: auto sur chaque maillon » semble séduisante,
mais overflow: auto sur un conteneur intermédiaire qui
ne déborde pas lui-même (comme
.right-stuff) est superflu. Il faut distinguer les
maillons qui propagent la hauteur de ceux qui
défilent.
Code
<style> *, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body { display: flex; flex-direction: column; gap: 1rem; padding: 16px; }
.content { flex: 1; display: flex; gap: 1rem; height: 100%; overflow: auto; }
.filters { overflow: auto; height: 100%; }
.right-stuff { flex: 1; display: flex; flex-direction: column; gap: 1rem; height: 100%; }
.diagram { flex: 1; overflow: auto; }</style><body> <header>HEADER</header> <div class="content"> <div class="filters">…</div> <div class="right-stuff"> <div class="toggles">…</div> <div class="diagram">…</div> </div> </div></body>Résultat
Les deux approches produisent le même rendu visuel dans cet
exemple. min-height: 0 est l’astuce flex classique pour
autoriser la compression ;
height: 100 % peut paraître plus intuitif si l’on
raisonne déjà en chaîne de pourcentages depuis
html et body.