web-dev-qa-db-fra.com

Éléments pivotés en CSS qui affectent correctement la hauteur de leurs parents

Disons que j'ai quelques colonnes, certaines desquelles j'aimerais faire pivoter les valeurs de:

http://jsfiddle.net/MTyFP/1/

<div class="container">
    <div class="statusColumn"><span>Normal</span></div>
    <div class="statusColumn"><a>Normal</a></div>
    <div class="statusColumn"><b>Rotated</b></div>
    <div class="statusColumn"><abbr>Normal</abbr></div>
</div>

Avec ce CSS:

.statusColumn b {
  writing-mode: tb-rl;
  white-space: nowrap;
  display: inline-block;
  overflow: visible;
  transform: rotate(-90deg);
  transform-Origin: 50% 50%;
}

Cela finit par ressembler à ceci:

A series of four block-level elements. The third element's text is rotated.

Est-il possible d'écrire n'importe quel CSS qui fera que l'élément pivoté affecte la hauteur de son parent, de telle sorte que le texte ne chevauche pas les autres éléments? Quelque chose comme ça:

The third column's text now affects its parent box's height such that the text fits within the box.

98
Chris

En supposant que vous souhaitiez effectuer une rotation de 90 degrés, cela est possible, même pour des éléments autres que du texte. Toutefois, comme beaucoup de choses intéressantes en CSS, cela nécessite un peu de ruse. Ma solution invoque également techniquement un comportement indéfini conformément à la spécification CSS 2. Ainsi, même si j'ai testé et confirmé son fonctionnement sous Chrome, Firefox, Safari et Edge, je ne peux pas vous promettre que cela ne se cassera pas dans le futur. version du navigateur.

Réponse courte

Étant donné le code HTML comme celui-ci, où vous souhaitez faire pivoter .element-to-rotate ...

<div id="container">
  <something class="element-to-rotate">bla bla bla</something>
</div>

... introduisez deux envelopper les éléments autour de l'élément que vous voulez faire pivoter:

<div id="container">
  <div class="rotation-wrapper-outer">
    <div class="rotation-wrapper-inner">
      <something class="element-to-rotate">bla bla bla</something>
    </div>    
  </div>
</div>

... et utilisez ensuite le code CSS suivant pour effectuer une rotation anti-horaire (ou consultez le commentaire transform pour obtenir un moyen de le modifier en rotation horaire):

.rotation-wrapper-outer {
  display: table;
}
.rotation-wrapper-inner {
  padding: 50% 0;
  height: 0;
}
.element-to-rotate {
  display: block;
  transform-Origin: top left;
  /* Note: for a CLOCKWISE rotation, use the commented-out
     transform instead of this one. */
  transform: rotate(-90deg) translate(-100%);
  /* transform: rotate(90deg) translate(0, -100%); */
  margin-top: -50%;

  /* Not vital, but possibly a good idea if the element you're rotating contains
     text and you want a single long vertical line of text and the pre-rotation
     width of your element is small enough that the text wraps: */
  white-space: nowrap;
}

Stack Snippet Demo

p {
  /* Tweak the visuals of the paragraphs for easier visualiation: */
  background: pink;
  margin: 1px 0;
  border: 1px solid black;
}
.rotation-wrapper-outer {
  display: table;
}
.rotation-wrapper-inner {
  padding: 50% 0;
  height: 0;
}
.element-to-rotate {
  display: block;
  transform-Origin: top left;
  /* Note: for a CLOCKWISE rotation, use the commented-out
     transform instead of this one. */
  transform: rotate(-90deg) translate(-100%);
  /* transform: rotate(90deg) translate(0, -100%); */
  margin-top: -50%;

  /* Not vital, but possibly a good idea if the element you're rotating contains
     text and you want a single long vertical line of text and the pre-rotation
     width of your element is small enough that the text wraps: */
  white-space: nowrap;
}
<div id="container">
  <p>Some text</p>
  <p>More text</p>
  <div class="rotation-wrapper-outer">
    <div class="rotation-wrapper-inner">
      <p class="element-to-rotate">Some rotated text</p>
    </div>    
  </div>
  <p>Even more text</p>
  <img src="https://i.stack.imgur.com/ih8Fj.png">
  <div class="rotation-wrapper-outer">
    <div class="rotation-wrapper-inner">
      <img class="element-to-rotate" src="https://i.stack.imgur.com/ih8Fj.png">
    </div>
  </div>
  <img src="https://i.stack.imgur.com/ih8Fj.png">
</div>

Comment cela marche-t-il?

La confusion devant les incantations que j'ai utilisées ci-dessus est raisonnable; il y a beaucoup de choses en cours, et la stratégie globale n'est pas simple et nécessite une certaine connaissance de trivia CSS pour comprendre. Passons en revue étape par étape.

Le problème principal auquel nous sommes confrontés est que les transformations appliquées à un élément à l'aide de sa propriété CSS transform se produisent toutes après la mise en page. En d'autres termes, l'utilisation de transform sur un élément n'a aucune incidence sur la taille ou la position de son parent ou de tout autre élément. Il n'y a absolument aucun moyen de changer ce fait de la façon dont transform fonctionne. Ainsi, afin de créer l'effet de rotation d'un élément et de faire en sorte que la hauteur de son parent respecte la rotation, nous devons procéder comme suit:

  1. Construisez en quelque sorte un élément autre dont la hauteur est égale à la largeur du .element-to-rotate
  2. Ecrivez notre transformation sur .element-to-rotate De manière à la superposer exactement à l'élément de l'étape 1.

L'élément de l'étape 1 doit être .rotation-wrapper-outer. Mais comment pouvons-nous faire en sorte que ses hauteur soient égaux à .element-to-rotate largeur?

Le composant clé de notre stratégie ici est le padding: 50% 0 Sur .rotation-wrapper-inner. Cet exploit n détail excentrique de la spécification pour padding : ce pourcentage de remplissage, même pour padding-top Et padding-bottom, Est toujours défini en tant que pourcentage du largeur du conteneur de l'élément. Cela nous permet d'effectuer le tour suivant en deux étapes:

  1. Nous plaçons display: table Sur .rotation-wrapper-outer. Cela le fait avoir largeur de retrait , ce qui signifie que sa largeur sera définie en fonction de la largeur intrinsèque de son contenu - c'est-à-dire en fonction de la largeur intrinsèque de .element-to-rotate. (Sur les navigateurs compatibles, nous pourrions réaliser cela plus proprement avec width: max-content, Mais à partir de décembre 2017, max-content Est toujours pas pris en charge dans Edge .)
  2. Nous définissons le height de .rotation-wrapper-inner Sur 0, puis nous réglons son remplissage sur 50% 0 (C'est-à-dire 50% en haut et 50% en bas). Cela lui fait occuper un espace vertical égal à 100% de la largeur de son parent - ce qui, par le truc de l'étape 1, est égal à la largeur de .element-to-rotate.

Il ne reste ensuite qu’à effectuer la rotation et le positionnement réels de l’élément enfant. Naturellement, transform: rotate(-90deg) effectue la rotation; nous utilisons transform-Origin: top left; pour que la rotation se produise autour du coin supérieur gauche de l'élément pivoté, ce qui permet de raisonner facilement la traduction suivante, car il laisse l'élément pivoté directement au-dessus de l'endroit où il aurait autrement été dessiné . Nous pouvons ensuite utiliser translate(-100%) pour faire glisser l'élément vers le bas d'une distance égale à sa largeur de pré-rotation.

Le positionnement n’est pas encore tout à fait correct, car nous devons toujours compenser le remplissage supérieur à 50% sur .rotation-wrapper-outer. Nous y parvenons en veillant à ce que .element-to-rotate Dispose de display: block (Pour que les marges fonctionnent correctement), puis en appliquant un -50% margin-top - notez que les marges de pourcentage sont also défini par rapport à la largeur de l'élément parent.

Et c'est tout!

Pourquoi n'est-ce pas entièrement conforme aux spécifications?

En raison de la note suivante de la définition des pourcentages de remplissage et des marges dans la spécification (mine en gras):

Le pourcentage est calculé par rapport à largeur de la boîte générée bloc contenant , même pour 'padding-top' et 'padding-bottom' . Si la largeur du bloc contenant dépend de cet élément, la présentation résultante n'est pas définie en CSS 2.1.

Puisque tout le truc était de rendre le rembourrage de l'élément d'enveloppe interne relatif par rapport à la largeur de son conteneur, qui dépendait à son tour de la largeur de son enfant, nous atteignons cette condition et invoquons un comportement non défini. Cependant, il fonctionne actuellement dans les 4 principaux navigateurs - contrairement à certains ajustements apparemment conformes aux spécifications que j'ai essayé, comme changer de .rotation-wrapper-inner Pour devenir un frère de .element-to-rotate Au lieu d'un parent.

37
Mark Amery

Malheureusement (?) C'est ainsi qu'il est supposé fonctionner même si vous faites pivoter votre élément, il a toujours une certaine largeur et hauteur, cela ne change pas après la rotation. Vous le modifiez visuellement, mais il n'y a pas de boîte d'emballage invisible qui change de taille lorsque vous faites pivoter les éléments.

Imaginez que vous le faites pivoter à moins de 90 ° (par exemple, transform: rotate(45deg)): vous devez introduire un tel boîtier invisible, qui a maintenant des dimensions ambiguës basées sur les dimensions d'origine de l'objet que vous faites pivoter et la valeur de rotation réelle.

Rotated object

Du coup, vous n’avez pas seulement les width et height de l’objet tourné, mais également les width et height du "invisible". boîte "autour de lui. Imaginez-vous demander la largeur extérieure de cet objet - que retournera-t-il? La largeur de l'objet ou notre nouvelle boîte? Comment distinguerions-nous les deux?

Par conséquent, il n'y a pas de CSS que vous pouvez écrire pour résoudre ce problème (ou devrais-je dire, "automatisez-le"). Bien sûr, vous pouvez augmenter la taille de votre conteneur parent à la main ou écrire du code JavaScript pour gérer cela.

(Juste pour être clair, vous pouvez essayer d'utiliser element.getBoundingClientRect pour obtenir le rectangle mentionné précédemment).

Comme décrit dans la spécification :

Dans l'espace de noms HTML, la propriété de transformation n'affecte pas le flux du contenu entourant l'élément transformé.

Cela signifie qu'aucune modification ne sera apportée au contenu entourant l'objet en cours de transformation, à moins que vous ne le fassiez à la main.

La seule chose qui est prise en compte lorsque vous transformez votre objet est la zone de débordement:

(...) l'étendue de la zone de débordement prend en compte les éléments transformés. Ce comportement est similaire à ce qui se produit lorsque les éléments sont décalés via positionnement relatif .

Voir ce jsfiddle pour en savoir plus.

En fait, il est bon de comparer cette situation à un décalage d'objet avec: position: relative - le contenu environnant ne change pas, même si vous déplacez votre objet ( exemple ).


Si vous voulez gérer cela en utilisant JavaScript, jetez un oeil à this question.

40
MMM

Utilisez les pourcentages pour padding et un pseudo-élément pour transmettre le contenu. Dans le JSFiddle, j'ai laissé le pseudo-élément en rouge pour le montrer et vous devrez compenser le décalage du texte, mais je pense que c'est la voie à suivre.

.statusColumn {
    position: relative;
    border: 1px solid #ccc;
    padding: 2px;
    margin: 2px;
    width: 200px;
}

.statusColumn i, .statusColumn b, .statusColumn em, .statusColumn strong {
  writing-mode: tb-rl;
  white-space: nowrap;
  display: inline-block;
  overflow: visible;
  -webkit-transform: rotate(-90deg);
  -moz-transform: rotate(-90deg);
  -ms-transform: rotate(-90deg);
  -o-transform: rotate(-90deg);
  transform: rotate(-90deg);
  -webkit-transform: rotate(-90deg);

  /* also accepts left, right, top, bottom coordinates; not required, but a good idea for styling */
  -webkit-transform-Origin: 50% 50%;
  -moz-transform-Origin: 50% 50%;
  -ms-transform-Origin: 50% 50%;
  -o-transform-Origin: 50% 50%;
  transform-Origin: 50% 50%;

  /* Should be unset in IE9+ I think. */
  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
}
.statusColumn b:before{ content:''; padding:50% 0; display:block;  background:red; position:relative; top:20px
}
<div class="container">
    <div class="statusColumn"><span>Normal</span></div>
    <div class="statusColumn"><a>Normal</a></div>
    <div class="statusColumn"><b>Rotated</b></div>
    <div class="statusColumn"><abbr>Normal</abbr></div>
</div>

http://jsfiddle.net/MTyFP/7/

Une description de cette solution peut être trouvée ici: http://kizu.ru/en/fun/rotated-text/

25
Litek

Comme le commentaire de G-Cyr souligne à juste titre, support actuel pour writing-mode est plus que décent . Combiné à une simple rotation, vous obtenez le résultat exact que vous souhaitez. Voir l'exemple ci-dessous.

.statusColumn {
  position: relative;
  border: 1px solid #ccc;
  padding: 2px;
  margin: 2px;
  width: 200px;
}

.statusColumn i,
.statusColumn b,
.statusColumn em,
.statusColumn strong {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
  display: inline-block;
  overflow: visible;
}
<div class="container">
  <div class="statusColumn"><span>Normal</span></div>
  <div class="statusColumn"><a>Normal</a></div>
  <div class="statusColumn"><b>Rotated</b></div>
  <div class="statusColumn"><abbr>Normal</abbr></div>
</div>
21
Bram Vanroy

Veuillez voir la version mise à jour dans mon autre réponse . Cette réponse n'est plus valide et ne fonctionne tout simplement plus. Je vais le laisser ici pour des raisons historiques.


Cette question est assez ancienne, mais avec le support actuel pour l'objet .getBoundingClientRect() et ses valeurs width et height, en combinaison avec la capacité pour utiliser cette méthode soignée avec transform, je pense que ma solution devrait également être mentionnée.

Voyez-le en action ici . (Testé dans Chrome 42, FF 33 et 37.)

getBoundingClientRect calcule la largeur réelle de l'élément. Tout simplement, alors, nous pouvons parcourir tous les éléments et définir sa hauteur minimale sur la hauteur réelle de la boîte de leurs enfants.

$(".statusColumn").each(function() {
    var $this = $(this),
        child = $this.children(":first");
    $this.css("minHeight", function() {
        return child[0].getBoundingClientRect().height;
    });
});

(Avec quelques modifications, vous pouvez parcourir les enfants pour trouver lequel est le plus grand et définir la taille du parent sur le plus grand. Cependant, j’ai décidé de simplifier l’exemple. Vous pouvez également ajouter un rembourrage du parent au hauteur si vous voulez, ou vous pouvez utiliser un box-sizing valeur en CSS.)

Notez, cependant, que j’ai ajouté un translate et un transform-Origin à votre CSS pour rendre le positionnement plus flexible et précis.

transform: rotate(-90deg) translateX(-100%);
transform-Origin: top left;
10
Bram Vanroy

La propriété en mode écriture le fera. Support actuel pour writing-mode est plus que décent . Combiné à une simple rotation, vous obtenez le résultat exact que vous souhaitez. Voir l'exemple ci-dessous.

.statusColumn {
  position: relative;
  border: 1px solid #ccc;
  padding: 2px;
  margin: 2px;
  width: 200px;
}

.statusColumn i,
.statusColumn b,
.statusColumn em,
.statusColumn strong {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
  display: inline-block;
  overflow: visible;
}
<div class="container">
  <div class="statusColumn"><span>Normal</span></div>
  <div class="statusColumn"><a>Normal</a></div>
  <div class="statusColumn"><b>Rotated</b></div>
  <div class="statusColumn"><abbr>Normal</abbr></div>
</div>

(Séparer de la réponse par Bram Vanroy )

3
Simon Gibbs