TD6 – Responsive Design
Introduction
Les smartphones, les tablettes et tous les appareils de la mobilité demandent de repenser le web d’aujourd’hui. Concevoir un site ou une application Web s’adressant à tous ces médias n’est pas une tâche triviale. Voici un aperçu des nombreuses solutions existantes :
-
Une solution en pur CSS. En effet, le CSS est le langage de base pour la mise en page des pages Web. Il est donc en constante évolution (CSS3 en cours d’élaboration) pour s’adapter aux nouveaux besoins.
-
Coder deux sites en HTML/CSS : un pour les ordinateurs et un pour les smartphones (et un pour les tablettes ? et un pour les smartwatch ?). Exemple lemonde.fr et mobile.lemonde.fr.
-
Des applications natives pour chaque système (Android, iOS, Windows Phone, …)
-
L’utilisation de la puissance de JavaScript pour faire des mises en page adaptatives.
-
Faire le site en Flash (très mauvaise idée car il n’est plus supporté sur Android ni iOS).
Plus pour nous positionner dans cette jungle que par purisme, nous prendrons deux hypothèses de départ :
- “Il n’y a pas besoin d’applications pour cela !” : Pas besoin de faire du natif Android ou iOS.
- “Il n’y a pas besoin de faire deux sites web pour cela !” : On s’interdit de faire un site Web pour mobile et un pour ordinateur.
Nous adopterons dans ce TD une approche itérative, en rajoutant au fur et à mesure des contraintes pour arriver à ce qui se fait aujourd’hui dans le responsive design.
CSS2
Certaines propriétés n’ont pas attendu ni les nouveaux médias ni le CSS3 pour s’imposer aux développeurs. Elles prenaient déjà tout leur sens sur des sites particulièrement fournis et/ou sur des petits écrans 15 pouces.
Les pourcentages ‘%’
On peut commencer par exprimer toutes les tailles en relatif. C’est ce que nous
avons déjà fait en utilisant des dimensions en %
. Rappelez-vous que la largeur d’un
élément en display:block
ou en display:flex
se calcule par rapport à son
containing block, c’est-à-dire son plus proche ancêtre block
ou flex
. Par
défaut, les éléments en block
ou flex
prennent toute la largeur de leur
containing block. Et si on fixe une largeur en pourcentage, ce sera
relativement à la largeur du containing block.
- Donnez à
<body>
lawidth
de100%
. Donnez à la propriétémargin
la valeurauto
si ce n’est pas le cas. - Enlevez au besoin la marge de gauche de
10%
sur le<aside>
et changez les dimensions relatives de<article>
et<aside>
à respectivement67%
et33%
.
Note : Si adapter les largeurs en pourcentage marche bien, ce n’est pas le
cas des hauteurs. Cela est dû au fait que la largeur de la page est connue
(c’est la largeur de la fenêtre d’affichage du navigateur) mais sa hauteur ne
l’est pas encore… (puisque la page n’est pas encore affichée et que la
hauteur va dépendre de la quantité de contenu). Autrement dit, la hauteur de la
zone du navigateur où s’affiche la page (viewport height
) n’est pas forcément
la hauteur de la page 1 (et c’est lié à la présence d’un
ascenseur à droite pour descendre dans la page).
max-width
et min-width
Les règles précédentes permettent d’avoir un rapport homogène, mais pas un rendu optimal :
- sur de petits écrans d’ordinateur, des éléments ne peuvent pas être correctement affichés (des images, des colonnes, …)
- sur de très grands écrans, le texte devient illisible : les yeux fatiguent à cause des lignes trop longues.
Pour contraindre les dimensions maximales et minimales, nous utiliserons
max-width
, max-height
, min-width
et min-height
, qui prennent le même
type de valeur que width
et height
.
- Ajoutez une limite maximum de largeur à
<article>
et à<aside>
de500px
et de250px
. - Ajoutez une limite minimum de largeur à
<article>
et à<aside>
de200px
et150px
.
Overconstraint
Mais que se passe-t-il quand on mélange min-width
/max-width
et des tailles en
pourcentage ? Prenons l’exemple suivant
<div style="display:flex">
<div style="width:50%;max-width:400px;">
Div1
</div>
<div style="width:50%;">
Div2
</div>
</div>
qui s’affiche comme suit
Div1
Div2
Remarquez en changeant la largeur de votre fenêtre (ou en zoomant) que :
- les deux
<div>
prennent toute la largeur quand<body>
a une largeur inférieure à800px
(donc le premier<div>
a une largeur de moins de400px
) - mais quand la largeur de
<body>
est supérieure à800px
, alors les deux<div>
ne remplissent plus toute la largeur de<body>
. Div2 garde une largeur de 50% mais Div1 ne peut plus prendre les50%
restant car il est contraint par sa largeur maximale de400px
.
Nous allons utiliser display:flex
pour mieux mélanger les tailles relatives et
les contraintes min-width
/max-width
. Nous avions déjà vu les règles de la
colonne de gauche de
cette super page sur FlexBox
; c’étaient celles qui s’appliquaient au parent. Nous allons maintenant nous
pencher sur la colonne de droite qui s’applique aux balises enfants. Regardez
particulièrement les propriétés flex-shrink
(valeur par défaut 1
),
flex-grow
(valeur par défaut 0
). Pour l’instant, nous ne toucherons pas à
flex-basis
(qui gardera donc son comportement par défaut auto
).
Voyons comment flex-grow:1;
nous permet de résoudre le problème précédent :
<div style="display:flex">
<div style="width:50%;max-width:400px;">
Div1
</div>
<div style="width:50%;flex-grow:1;">
Div2
</div>
</div>
s’affiche comme suit
Div1
Div2
Remarquez en changeant la largeur de votre fenêtre (ou en zoomant)
que lorsque la largeur de <body>
est supérieure à 800px
, alors le second
<div>
voit sa largeur augmenter grâce à flex-grow:1;
. Ainsi, les deux <div>
remplissent toujours la largeur de <body>
.
La propriété flex-grow
détermine comment les éléments enfants d’un conteneur en display: flex
doivent se partager l’espace disponible en trop, c’est-à-dire l’espace libre restant après que les éléments aient pris leur taille initiale. Cet espace disponible est calculé dans la direction du flex
(par exemple, horizontalement si flex-direction
est en ligne).
Si la somme des tailles des éléments enfants est inférieure à l’espace disponible du conteneur, les éléments peuvent se dilater pour occuper cet espace restant, qu’on appelle aussi “espace excédentaire”. Ce n’est qu’après le calcul des dimensions initiales des éléments que le flex-grow
intervient, en répartissant cet espace excédentaire.
Un élément avec un flex-grow
de 0 ne s’agrandira pas (c’est la valeur par défaut). Les autres éléments vont se répartir l’espace restant proportionnellement à leur valeur de flex-grow
.
Voici un exemple où le Div1 prend toujours 600px
, le Div2 prend 1/3 de la taille restante et le Div3 prend 2/3 de la taille restante :
<div style="display:flex;border:1px solid black;">
<div style="width:600px;flex-grow:0;background-color:orange;">
Div1
</div>
<div style="width:0px;flex-grow:1;background-color: cornflowerblue;">
Div2
</div>
<div style="width:0px;flex-grow:2;background-color: purple;">
Div3
</div>
</div>
qui s’affiche comme suit
Div1
Div2
Div3
- Augmentez et diminuez la largeur de la fenêtre pour constater le fonctionnement de l’exemple précédent.
La propriété flex-shrink
fonctionne de manière complémentaire : elle spécifie comment les éléments rétrécissent si leur taille totale dépasse celle du conteneur en display: flex
. Dans ce cas, les éléments vont se réduire pour éviter de déborder du conteneur. Plus valeur de flex-shrink
est élevée, plus l’élément peut perdre de taille.
Cependant, la répartition de la réduction d’espace est plus complexe que pour flex-grow
. La réduction est proportionnelle à la valeur de flex-shrink
de chaque élément, mais elle tient aussi compte de sa taille initiale. En effet, un élément ne peut pas rétrécir au-delà de sa taille d’origine, contrairement à l’expansion, qui peut dépasser la taille initiale. Par défaut, flex-shink
vaut 1 donc les éléments peuvent diminuer leur taille pour ne pas déborder du conteneur flex
.
Pour plus de détails, vous pouvez lire la section sur flex-shrink
et flex-grow
dans le guide de FlexBox ou tout
autre page Web. N’hésitez pas à parler de votre compréhension avec votre professeur.
-
Changez les largeurs de
<article>
et<aside>
pour qu’elles soient par défaut de300px
et200px
. Mettez les propriétésflex-shrink
etflex-grow
pour ces deux éléments à0
.
Bougez la largeur de la page. Est-ce que les largeurs de<article>
et<aside>
changent ? -
Nous souhaitons que quand l’écran est trop large, l’espace restant soit réparti entre
<article>
et<aside>
de telle sorte que l’espace gagné par<article>
soit 3 fois plus grand que celui gagné par<aside>
.
Quelle propriété CSS devez-vous utiliser pour avoir ce comportement ? Implémentez ce comportement. -
Pour vérifier que vous avez bien répondu à la question précédente, redimensionnez la fenêtre du navigateur pour que la largeur de
<body>
soit de620px
.
Quelles devraient être selon vous les largeurs de<article>
et<aside>
? Inspectez maintenant les largeurs de<article>
et<aside>
pour vérifier votre calcul. -
Donnez un
flex-shrink
de 2 à<article>
et de 1 à<aside>
pour que l’article rende beaucoup plus d’espace que l’aside
si besoin.
Problèmes plus complexes
Il arrive un moment où diminuer encore la taille n’a plus de sens. Il faut
prendre des mesures draconiennes, par exemple passer <aside>
sous <article>
ou carrément supprimer <aside>
.
Les règles CSS déjà vues en TDs ne permettent pas de coder ce genre de comportement. Il nous faut une façon d’écrire du CSS qui ne sera valide que dans des cas précis. Nous verrons dans la prochaine section comment cela est possible en CSS3.
Votre site de Chuck Norris sur mobile
Que fait mon navigateur Web sur téléphone par défaut pour un site ?
Comment peut-on rendre un site internet compatible mobile à moindre coût ?
Voici l’algorithme (simplifié) opérant par défaut :
- Générer le site sur une taille d’écran virtuel, disons d’une largeur de
980px
; - Faire un zoom arrière de manière à faire rentrer le site dans l’écran du smartphone ; (oui, cela fait de petits éléments)
- Supposer que l’utilisateur connaît le pinch to zoom pour naviguer dans le site :
Et ça marche ! De fait, c’est ce que font par défaut les smartphones quand ils tombent sur un site non responsive.
- Dans les outils développeurs (
F12
) de Chrome/Chromium, passez dans le device mode“Samsumg Galaxy S8+” sous Chrome/Chromium et rechargez votre page. Constatez que le site s’affiche en tout petit.
- Vérifiez que la taille du
<body>
est de980px
, ce qui signifie que l’algorithme précédent a été utilisé pour l’affichage.
Remarquez aussi que la largeur de l’affichage est de360px
, ce qui signifie d’un zoom arrière est effectué.
Même si cela marche, on ne peut pas dire que cela soit optimal. L’utilisateur mobile n’a pas la même attente que l’utilisateur sur ordinateur, il veut avoir accès rapidement aux informations essentielles, sans fioritures. On imagine que sur une smartwatch par exemple un site comme Méteo France serait largement plus dépouillé (sur une smartwatch, elle afficherait un nuage ou un soleil et la température par exemple).
Typiquement, nous voulons enlever des parties entières du site suivant la taille de l’écran.
La première chose à faire est donc de demander aux navigateurs de ne plus faire
l’algorithme précédent (puisqu’on va le gérer nous-mêmes) dans la balise
<head>
:
<meta name="viewport" content="width=device-width, initial-scale=1">
Ajouter cette instruction au site de Chuck Norris et visualisez avec Chrome en
choisissant un smartphone. Inspectez la largeur de <body>
. Que constatez-vous ?
Vous devez constater que l’algorithme précédent ne s’applique plus. En gros le navigateur n’essaie plus d’être intelligent : il vous laisse prendre le relai.
La solution technique CSS3 : les media queries
Les media queries sont un jeu d’options ajoutées à la norme CSS3 et qui permettent de définir des règles CSS qui ne s’appliqueront que sous certaines conditions spécifiques.
Il existe deux manières différentes de faire appel à une media query :
-
Soit vous souhaitez un fichier CSS spécifique en entier, mais uniquement dans certaines conditions. Alors il faut ajouter à l’en-tête
<head>
de la page web une déclaration de fichier CSS standard, à laquelle on rajoute l’attributmedia
et une condition spécifique.<link rel="stylesheet" media="condition" href="mon_css_special.css"/>
N’oubliez pas que le dernier CSS chargé prend le pas sur les précédents. Pensez donc à toujours déclarer vos media queries en dernier, sinon elles seront systématiquement écrasées par votre CSS “standard”.
-
Soit vous souhaitez que juste certaines règles s’appliquent sous certaines conditions. Alors vous englobez vos règles avec la syntaxe suivante :
@media(ma_condition) { div {background-color:white;} ... }
Une media query fonctionne de la manière suivante : la condition est évaluée et retourne une valeur “vrai” ou “faux”. Si la valeur est vraie, le fichier CSS/ la règle CSS (selon la méthode employée) est appliquée.
Chaque condition est formée à partir d’une ou plusieurs conditions de base. Parmi l’ensemble des conditions possibles, nous allons nous intéresser particulièrement aux suivantes :
-
min-width
,max-width
,min-height
etmax-height
: permettent de renseigner une dimension minimale/maximale à remplir pour que la condition soit vraie. Par exemple,@media(min-width:100px) { div {background-color:white;} }
-
orientation
: prend les valeurslandscape
(écran horizontal) ouportrait
(écran vertical)
Les conditions de base peuvent être combinées ensemble pour former des conditions complexes en utilisant les opérateurs logiques :
- “and” (ET) : les deux conditions de base doivent être vraies pour que la condition soit vraie
- “,” (OU) : au moins l’un des conditions de base doit être vraie pour que la condition soit vraie
- “not” (NON) : la condition de base qui suit le not doit être fausse pour que la condition soit vraie
Exemple : la règle (min-width: 500px) and (min-height: 800px)
n’est
vraie que si la largeur de l’affichage est supérieure (ou égale) à 500px
et la
hauteur supérieure à 800px
.
- Allez sur le site Bootstrap. Faites varier la largeur du navigateur et constatez que la mise en page change (menu variable, texte qui prend toute la largeur ou non, icône qui disparait, …).
- Utilisez le device mode pour afficher les media queries (cliquer sur les 3 points verticaux du device mode pour afficher ces media queries).
- Que se passe-t-il visuellement dans le menu lorsque vous redimensionnez la
fenêtre autour du point de rupture situé à
768px
?
Remarque : Il existe une nouvelle syntaxe plus lisible qui autorise à écrire 450px<width <= 600px
. Cependant, elle n’a que récémment été adoptée par certains navigateurs majeurs (Firefox: 2018, Chrome: 2022, Safari: 2023). Et nous n’allons pas les utiliser pour l’instant puisque les vieilles versions non-compatibles de ces navigateurs représentent encore une part non négligeable du trafic (un peu plus de 6% en novembre 2024; source ).
Les points de ruptures.
Afin d’organiser nos media queries, on utilise en général 3 à 4 valeurs de largeur d’écrans, par exemple :
480px
(Smartphone)768px
(Tablette)992px
(écran d’ordinateur “Standard”)1200px
(écran d’ordinateur “Large”)
- en dessous de 768px ne plus afficher la table de comparaison (de toute façon s’il ne doit rester qu’un seul, ce sera Chuck Norris).
- en dessous de 480px faire en sorte qu’
<aside>
et<article>
soient en colonne et non plus en ligne. - (Optionnel) Sur une smartwatch (width
168px
), n’affichez que les citations de Chuck Norris contenues dans le<aside>
.
Mettez-moi un burger au menu !
Quand la taille de l’écran est limitée, une bonne pratique en responsive design est de changer l’affichage du menu pour un bouton burger :
Lorsque l’on cliquera dessus, le menu apparaîtra. Cela permet de ne pas perdre de place sur la page lorsque le menu est fermé. Pour fixer les idées, nous voulons un menu qui apparaît latéralement à la manière des exemples suivants :
Dans l’exercice suivant, nous allons coder un menu burger HTML/CSS (donc sans JavaScript) qui apparait quand la souris passe au-dessus du burger (la gestion du clic nécessiterait du JavaScript). (Notez au passage que codepen.io est un très bon outil pour découvrir de nouvelles techniques !)
Nous allons coder un deuxième menu, identique au premier dans son contenu, mais avec une mise en page différente. Nous allons afficher l’un ou l’autre des menus en fonction de la taille de l’écran.
- Ajouter un
<div>
de classeburger
contenant l’image de burger (largeur50px
) juste avant le menu<nav>
. - À partir du point de rupture ‘Smartphone’, faites disparaître l’ancien menu
et faites apparaître le
<div class="burger">
à droite de la page (à la place du menu). -
Implémentez le deuxième menu avec les caractéristiques suivantes :
-
Son contenu est
<div class="burger"> <img src="images/burger.png" alt="burger" width="50"> <div id="menu2"> <div><a href="./index.html">Accueil</a></div> <div><a href="./facts.html">Facts</a></div> <div><a href="./news.html">Actualités</a></div> <div><a href="./contact.html">Contact</a></div> </div> </div>
- il se positionne par rapport à la fenêtre d’affichage (quelle valeur de
position
faut-il mettre ?), tout en haut à gauche. - il est visuellement au-dessus des autres éléments du site (cherchez sur le
Web la propriété
z-index
) - les sous-menus sont disposés verticalement.
-
- Cachez par défaut en CSS le
menu2
. Au survol du<div class="burger">
, faites-le réapparaître.
Voilà ce qu’on devrait pouvoir obtenir :
Note : Le hover sur les sous-menus n’a pas de sens sur téléphone portable (mais certains explorateurs contournent cela, par exemple en déclenchant le hover avec un “clique” sur l’élément à hover). Il faudra gérer le clic avec du JS. La bonne solution est … suspense, vous la verrez en 2ème année.
La mobilité en général
Les contraintes liées à la mobilité ne se limitent pas au responsive design. Pour vous donner une idée plus complète, voici d’autres exemples de contraintes fortes qui viennent s’ajouter :
- débit du réseau
- faible capacité du média (processeur, mémoire vive,…)
- mode offline (on peut passer sous un pont… il faut prévoir pour le cas échéant un cache local qui stocke les opérations courantes, afin de les consommer lorsque le réseau revient)
- changement de paradigme de l’interface homme machine (mouse over, menu déroulant,… )
-
L’unité
vh
permet maintenant de définir une taille de 0 à 100 relative au viewport. ↩