web-dev-qa-db-fra.com

La marge de droite CSS ne fonctionne pas à l'intérieur d'une div avec défilement de débordement

J'essaie de faire deux divs, l'un dans l'autre. Le div intérieur est plus grand que le div extérieur, le div extérieur a overflow:scroll, et le div intérieur a margin:25px. Alors je fais ça:

#outer {
    width: 200px;
    height: 100px;
    overflow: scroll;
}
#inner {
    width: 400px;
    height: 200px;
    margin: 25px;
}

...

<div id="outer">
    <div id="inner">

    </div>
</div>

Au lieu que le div intérieur ait une marge de 25px tout autour comme prévu, il y a une marge de 25px sur TROIS côtés, mais sur le côté droit il n'y en a pas. C'est extrêmement contre-intuitif à mon avis.

Si j'ajoute un div du milieu avec une largeur assez grande pour contenir le div intérieur + 50 px, nous pouvons le faire bien paraître, mais cela semble être une solution de contournement hacky.

Voir mon exemple sur JSFiddle: http://jsfiddle.net/d3Nhu/16/

Cela se produit de la même manière dans tous les principaux navigateurs. Y a-t-il une bonne raison à ce comportement? Ce comportement est-il correct selon la spécification CSS?

REMARQUE: comme vous pouvez vous y attendre dans cet exemple, cela ne fait aucune différence si vous utilisez overflow:auto au lieu de overflow:scroll.

[~ # ~] modifier [~ # ~] : veuillez noter que je suis pas à la recherche d'une solution de contournement pour ce comportement. (J'en ai déjà trouvé un.) Je cherche des informations sur la raison pour ce comportement, surtout s'il est documenté dans la spécification CSS n'importe où.

41
Sean the Bean

TL; DR:

Les marges permettent de déplacer un élément depuis le wrapper, et non de développer le wrapper vers l'extérieur.

La longue explication:

Ce comportement est cohérent avec la spécification d'un width en plus d'un margin horizontal n'importe où dans le document. Pour le décomposer, considérez l'extrait de code suivant, où je spécifie un wrapper sans propriété overflow, et le margin ne développe pas l'élément wrapper.

body {
    padding: 20px;
}
.outer {
    width: 400px;
    border: 1px solid black;
}
.inner {
    width: 400px;
    height: 40px;
    margin: 0 20px;
    background: grey;
}
<div class="outer">
    <div class="inner">
        
    </div>
</div>

Comme vous pouvez le voir, le margin n'a pas fait augmenter la taille du wrapper, l'élément a juste continué à déborder. Ce comportement est documenté sous les détails du modèle de formatage visuel du est documenté dans la spécification CSS 2.1.

Extrait de la section " Éléments de niveau bloc non remplacés dans le flux normal " de " Détails du modèle de formatage visuel ":

Les contraintes suivantes doivent tenir parmi les valeurs utilisées des autres propriétés:

'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = largeur du bloc contenant

[...]

Si tous les éléments ci-dessus ont une valeur calculée autre que "auto", les valeurs sont dites "sur-contraintes" et l'une des valeurs utilisées devra être différente de sa valeur calculée. Si la propriété 'direction' du bloc contenant a la valeur 'ltr', la valeur spécifiée de 'margin-right' est ignorée et la valeur est calculée de manière à rendre l'égalité vraie. Si la valeur de "direction" est "rtl", cela se produit plutôt à "marge gauche".

Cet extrait est assez dense, donc simplement, ignorons la largeur de border et padding, les deux étant 0, nous laissant avec width, margin-left, et margin-right.

Maintenant, puisque vous avez un width fixe et des valeurs pour margin-left et margin-right, les valeurs sont "trop ​​contraintes". Maintenant, dans notre exemple, puisque la direction est ltr par défaut, le margin-right est obligé de compenser.

Pour voir les effets de la direction, essayons d'ajouter un dir="rtl" attribut à l'élément wrapper.

body {
    padding: 20px;
}
.outer {
    width: 400px;
    border: 1px solid black;
}
.inner {
    width: 400px;
    height: 40px;
    margin: 0 20px;
    background: grey;
}
<div class="outer" dir="rtl">
    <div class="inner">
        
    </div>
</div>

L'élément déborde maintenant vers la gauche. Voyons voir si ce dir="rtl" l'attribut a le même effet sur votre overflow: scroll exemple.

#outer {
    border: 1px solid #00F;
    width: 200px;
    height: 100px;
    overflow: scroll;
}
#inner {
    border: 1px solid #F0F;
    margin: 25px;
    width: 400px;
    height: 200px;
}
<div id="outer" dir="rtl">
    <div id="inner">
    
    </div>
</div>

Oui, c'est vrai. La marge est maintenant manquante à gauche plutôt qu'à droite.

Mais pourquoi overflow: scroll inclure les marges?

Principalement parce que la spécification ne le dit pas. Jetons un coup d'œil à la spécification CSS 2 pour la propriété overflow.

Extrait de la section " Débordement et écrêtage " de " Effets visuels ":

Chaque fois qu'un débordement se produit, la propriété 'overflow' spécifie si une boîte est coupée sur son bord de remplissage, et si c'est le cas, si un mécanisme de défilement est fourni pour accéder à tout contenu coupé.

Voyez comment cela dit spécifiquement "contenu coupé". Pour une explication du "contenu", faisons référence au graphique suivant de la spécification CSS 2.

Graphique de la section " Dimensions de la boîte " de la section " Modèle de boîte ":

box model

Comme nous pouvons le voir, le margin est distinct du content. Cependant, à ce stade, il convient de noter que le remplissage et les bordures sont inclus dans la zone de défilement, donc lorsque la spécification dit "contenu", cela fait probablement référence à border-box, ou du moins, cela semble être la façon dont elle a été interprétée .

Pourquoi display: inline-block travail?

Fondamentalement, les marges se comportent différemment sur inline-block éléments, car ils sont au niveau du contenu plutôt qu'au niveau du bloc, et ils n'ont pas la notion d'être "trop ​​contraint".

54
Alexander O'Mara

ajouter display:inline-block; à #inner div

voir ceci violon

18
Keith

Donc, les réponses ici ne résolvent pas réellement le problème! (Bien que super détaillé de la raison pour laquelle cela ne fonctionne pas)

J'avais besoin d'une solution. Voici le mien pour les futurs lecteurs. Utilisez une combinaison de display:flex; avec l'élément pseudo :: after pour simuler la présence d'un div pour fournir la marge nécessaire.

.wrapper {
  display: flex;
  width: 400px;
  height: 100%;
  padding: 40px;
  background: lightGrey;
}

.lists_container {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  overflow: auto;
  position: relative;
  background: grey;
  padding: 40px;
  margin: 40px;
  width: 100%;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 250px;
  max-width: 500px;
  height: 100px;
  margin: 50px 0;
  padding: 20px;
  background: orange;
  margin-right: 30px;
}

.card.last::after {
  content: '';
  position: absolute;
  right: -100px;
  width: 40px;
  height: 100%;
  background: red;
}
<div class="wrapper">

  <div class="lists_container">

    <div class="card">
    </div>

    <div class="card">
    </div>

    <div class="card last">
    </div>

  </div>

</div>
4
Jquestions