mardi 23 août 2005, 21:32
Centrer un float en CSS
align="center"
, et le contenu est un tableau d'une seule ligne dont le nombre de cellules varie de 1 à 5 (le nombre maximum de boites pour tenir sur une seule ligne). Mais comment faire ça en CSS ?
Problématique
L'effet recherché ressemble à ça :
Ma première idée, pour placer les boîtes est d'utiliser le positionnement float avec un hr
et la propriété clear : both;
comme expliqué dans ce très bon article sur OpenWeb.
Au niveau code ça donne :
<div class="galerie"> <div class="boîte">boîte 1</div> <div class="boîte">boîte 2</div> <div class="boîte">boîte 3</div> <div class="boîte">boîte 4</div> <div class="boîte">boîte 5</div> <hr class="clear" /> </div>
Appliquons le style suivant :
.galerie { background : #ccc; width : 360px; /* (50 + (5 * 2) + 10) * 5 + 10 */ } .boîte { background : #69c; float : left; margin : 10px 0 10px 10px; padding : 5px; width : 50px; text-align : center; } .clear { clear : both; visibility : hidden; }
Ça donne :
Avec 3 boîtes uniquement :
Vous pouvez tester ce premier exemple.
Le positionnement est celui recherché lorsque toutes les boîtes sont présentes, mais, malheureusement, dès qu'il manque des boîtes, celles restantes sont calées à gauche. C'est tout à fait logique puisqu'elles sont flottantes à gauche, mais ce n'est pas ce que nous voulons !
Un deuxième conteneur pour le centrage
Au départ, j'ai simplement essayé quelques techniques de centrage css appliquées sur les fiches ou sur le conteneur. Mais rien de tout ça ne fonctionne.
Le problème est qu'on ne peut pas centrer un flottant, à cause de sa conception même. Comme écrit très justement dans l'article d'Openweb : Une boîte flottante est retirée du flux normal, et placée le plus à droite (float: right) ou le plus à gauche (float: left) possible dans son conteneur.
Il est donc logique que les techniques de centrage ne puisse pas s'appliquer (centrer par rapport à quoi, puisque la boîte est retirée du flux ?).
Par contre, un élément non-flottant peut, lui, être centré (par exemple avec la technique des marges auto
). Imaginons que l'on crée un autre conteneur, qui soit centré par rapport au premier (la galerie), et qui contienne les fiches...
Au niveau code ça donne :
<div class="galerie"> <div class="conteneur"> <div class="boîte">boîte 1</div> <div class="boîte">boîte 2</div> <div class="boîte">boîte 3</div> <div class="boîte">boîte 4</div> <div class="boîte">boîte 5</div> <hr class="clear" /> </div> </div>
On applique le même style que précédemment. Mais il faut rajouter de quoi centrer le conteneur :
.galerie { text-align : center; /* uniquement pour IE */ } .conteneur { margin : 0 auto; }
Là se pose un problème - LE problème devrais-je dire - : pour que ça fonctionne il faut que le conteneur enveloppe parfaitement les boîtes, et donc soit exactement à la bonne taille. La formule donnant la taille est facile à trouver :
taille = nombre de boîtes * ( width + padding + margin-left ) + margin-right
Mais je n'ai strictement aucune idée de comment faire ça en CSS. C'est théoriquement possible de le fixer dans un style inline (dans la page) appliqué au moment où le script qui génére la page affiche les boîtes. Une autre solution est d'utiliser DOM pour manipuler en Javascript les styles de la page.
Implémentation en Javascript/DOM
Le script doit se charger de calculer la taille en fonction du nombre d'éléments contenus. Pour ça il faut :
- Trouver le conteneur à agrandir ; on utilisera la méthode
getElementsById
- Compter le nombre de boîtes contenues :
- Trouver les éléments
div
avec la méthodegetElementsByTagName
- Eliminer ceux qui ne sont pas directement fils du conteneur (si les boîtes contiennent d'autres divs par exemple
- Trouver les éléments
- Calculer la taille et agrandir le conteneur
Mon implémentation (qui n'est sûrement pas optimale) :
<script type="text/javascript"> // <![CDATA[ function setContainerSize(truc) { var navroot = document.getElementById(truc); if ( navroot ) { var lis = navroot.getElementsByTagName("div"); /* XXX : y a-t-il un moyen plus simple de ne détecter que les divs de rang 1 ? * ou, détecter selon une classe ? */ var ok = 0; var nok = 0; for ( i = 0; i < lis.length; i++ ) { if ( lis[i].parentNode != navroot ) nok++; else ok++; } navroot.style.width = ok * 70 + 10 + 'px'; } } // ]]> </script>
Maintenant, pour utiliser cette fonction, on effectue les modifications suivantes :
<body onload="setContainerSize('c1');"> ... <div class="conteneur" id="c1">
Ça donne :
Vous pouvez consulter cet exemple.
Limites et pistes de reflexions
Cette méthode a été testée avec succès sur IE 6, Firefox 1.x, Safari 1.3 et Camino 0.9.
Une limitation principale de cette méthode est l'impossibilité de centrer sur plusieurs lignes. Par exemple, si la première ligne contient 5 boîtes et la deuxième 2, les 2 seront calées à gauche. Je n'ai pas vraiment cherché comment résoudre ce problème, car ça n'était pas mon besoin.
L'autre amélioration possible concerne l'implémentation du script, et notamment la façon de compter les éléments div
qui n'est sûrement pas optimale. Il serait également intéressant de pouvoir ne pas coder en dur les tailles des div dans le script, de manière à avoir du code réutilisable. Mais là ça dépasse mes connaissance en DOM :)
Enfin, étant donné que le centrage se fait par un script, les boîtes ne sont pas centrées tant que la page n'est pas complètement chargée. Dans certains cas ça provoque un petit décalage à l'écran le temps que le chargement se termine...
Quelques liens supplémentaires
Merci à Scara, MrGecko et tout le chan #asw pour la relecture ;)
Par cgo2, dans Développement web
Commentaires
Comme sur le chat, mais ici au moins c'est persistant. :)
Tu reprends ton modèle de avec le conteneur mais tu lui mets le style suivant :
.conteneur {
display : table;
margin : 0px auto;
}
Et on oubie pas d'applaudir ze masteur ov séaissèsse.
(Qu'il est loin le temps du troll xsl/css...)
Je vais avoir droit à un greet ? :D
Celelibi
Tien tant que j'y suis, on va "corriger" le script JavaScript même si en CSS ça fait plus classe. :)
Donc, pour connaitre la classe d'un élément il y a plusieurs moyens.
Soit la propriété className, ou alors on peut faire du lourd avec un lis.getAttribute("class") .
Sinon, pour parcourir la liste des noeud enfant sur seulement un niveau il faut y aller à coup de nextSibling. Petit exemple :
for (myNode=navroot.firstChild; myNode; myNode=myNode.nextSibling) {
// Si le noeud est un élément
if(myNode.nodeType == 2) ok++
else nok++
}
En fait ici moz et sa famille de pandas rouges considèrent que de simples espaces constituent un noeud de text, alors que IE ne les considère pas. :)
Il serait aussi possible de faire un test sur l'attribut nodeName, voir même pourquoi pas, className.
Un petit lien en passant fr.selfhtml.org/javascrip...
Et puis pourquoi modifier l'attribut style de l'élément, quand tu peux modifier la règles CSS. Mais bon j'ai un peu la flème de ce soir (oui il est 07h20 du soir).
Aller encore un petit lien www.mozilla.org/docs/dom/...
Bref, de toutes façon je désactive javascript par défaut, et puis la solution CSS elle rox.
Pour le display : table, je ne connaissais pas du tout, c'est ce dont j'avais besoin.
Malheureusement, après un test de 3 minutes avant de partir au boulot j'ai constaté :
1) ça crée un espace supplémentaire en dessous des boites (bon, ça doit peut-être se regler avec une autre propriété)
2) c'est pas compatible IE...
Je jetterais à oeil à ton script tout à l'heure :)
... de toutes façons rien ne vaudra jamais l'alliance CSS/XHTML... euh... je crois ? allez faire comprendre ça à nos chers ingénieurs microsoftiens :) (j'espère au moins qu'IE7 calculera correctement les paddings et qu'ils sera compatible avec position:fixed; que je passe plus dix plombes à créer des erreurs puis des corrections dans mes feuilles de style...)
J'avais tenté un réajustement de blocs avec javascript, et j'en avait conclu que c'était trop lourd : à chaque réajustement de la taille des fenêtres, le contenu mettait du temps à se remettre en place, et je plantais complètement IE dans certains cas.
C'est déconseillé d'utiliser DOM dans ce cas je pense, il faut trouver une solution alterne, et compatible avec IE. Je crois que sur Alsacréations il y a un tutoriel qui explique cette démarche de centrage de blocs, compatible tous navigateurs.
Bon vent,
Jk