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

119

Disons que j'ai quelques colonnes, dont certaines que 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:

Une série de quatre éléments de niveau bloc.  Le texte du troisième élément pivote.

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

Le texte de la troisième colonne affecte désormais la hauteur de sa boîte parente de sorte que le texte tienne dans la boîte.

Chris
la source
1
pourrait être utile stackoverflow.com/questions/7565542/…
Pete
7
Le mode d'écriture est maintenant disponible pour la plupart des navigateurs (il s'agissait d'une fonctionnalité IE5) Je ferais de nos jours jsfiddle.net/MTyFP/698 voir: developer.mozilla.org/en-US/docs/Web/CSS/writing-mode
G-Cyrillus
1
@ G-Cyr writing-modeest disponible pour la plupart des navigateurs, mais horriblement bogué. Sur une question connexe, je suis passé par plusieurs itérations pour essayer de contourner les bogues spécifiques au navigateur avant d'abandonner; vous pouvez voir ma succession d'échecs sur stackoverflow.com/posts/47857248/revisions , où je commence par quelque chose qui fonctionne dans Chrome mais pas Firefox ou Edge, puis je casse Chrome en train de réparer les deux autres, et finis par revenir sur mon répondez et étiquetez-le comme Chrome uniquement. Soyez prudent, si vous voulez emprunter cette voie. (Notez également que cela ne sert à rien avec les éléments non textuels.)
Mark Amery
1
afaik writing-mode ne fonctionne que sur le texte ...
Fredrik Johansson

Réponses:

51

En supposant que vous souhaitiez faire pivoter de 90 degrés, cela est possible, même pour les éléments non textuels - mais comme beaucoup de choses intéressantes dans CSS, cela nécessite un peu de ruse. Ma solution invoque également techniquement un comportement indéfini selon la spécification CSS 2 - donc, bien que j'aie testé et confirmé qu'elle fonctionne dans Chrome, Firefox, Safari et Edge, je ne peux pas vous promettre qu'elle ne se cassera pas à l'avenir. version du navigateur.

Réponse courte

Compte tenu du HTML comme celui-ci, où vous voulez faire pivoter .element-to-rotate...

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

... introduisez deux éléments wrapper autour de l'élément que vous souhaitez 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>

... puis utilisez le CSS suivant, pour tourner dans le sens anti-horaire (ou voir le commenté transformpour un moyen de le changer en une rotation dans le sens 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;
}

Démo d'extraits de pile

Comment cela marche-t-il?

La confusion face aux incantations que j'ai utilisées ci-dessus est raisonnable; il se passe beaucoup de choses, et la stratégie globale n'est pas simple et nécessite une certaine connaissance des anecdotes CSS pour la comprendre. Passons en revue étape par étape.

Le cœur du problème auquel nous sommes confrontés est que les transformations appliquées à un élément à l'aide de sa transformpropriété CSS se produisent toutes après la mise en page. En d'autres termes, l'utilisation transformsur un élément n'affecte pas du tout 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 comment transformfonctionne. Ainsi, pour créer l'effet de rotation d'un élément et que la hauteur de son parent respecte la rotation, nous devons faire les choses suivantes:

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

L'élément de l'étape 1 doit être .rotation-wrapper-outer. Mais comment faire en sorte que sa hauteur soit égale à .element-to-rotatela largeur de s ?

L'élément clé de notre stratégie ici est le padding: 50% 0sur .rotation-wrapper-inner. Cet exploit est un détail excentrique de la spécification pourpadding : que les pourcentages de rembourrage, même pour padding-topet padding-bottom, sont toujours définis comme des pourcentages de la largeur du conteneur de l'élément . Cela nous permet d'effectuer l'astuce en deux étapes suivante:

  1. Nous avons mis display: tablesur .rotation-wrapper-outer. Cela lui donne une largeur de rétrécissement , 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 pris en charge, nous pourrions y parvenir plus proprement avec width: max-content, mais à partir de décembre 2017, ce max-contentn'est toujours pas pris en charge dans Edge .)
  2. Nous définissons le heightof .rotation-wrapper-innersur 0, puis définissons son remplissage sur 50% 0(c'est-à-dire 50% haut et 50% bas). Cela l'amène à prendre un espace vertical égal à 100% de la largeur de son parent - ce qui, grâce à l'astuce de l'étape 1, est égal à la largeur de .element-to-rotate.

Ensuite, il ne reste plus qu'à effectuer la rotation et le positionnement réels de l'élément enfant. Naturellement, transform: rotate(-90deg)fait la rotation; nous utilisons transform-origin: top left;pour faire en sorte que la rotation se produise autour du coin supérieur gauche de l'élément pivoté, ce qui rend la translation ultérieure plus facile à raisonner, car elle laisse l'élément pivoté directement au-dessus de l'endroit où il aurait été dessiné autrement. On peut ensuite utiliser translate(-100%)pour faire glisser l'élément vers le bas d'une distance égale à sa largeur de pré-rotation.

Cela n'obtient toujours pas le bon positionnement, car nous devons toujours compenser le rembourrage supérieur de 50% .rotation-wrapper-outer. Nous y parvenons en nous assurant que .element-to-rotatehas display: block(pour que les marges fonctionnent correctement dessus), puis en appliquant un -50% margin-top- notez que les marges en pourcentage sont également définies 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 tirée de la définition des pourcentages de rembourrage et des marges dans la spécification (mine en gras):

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

Étant donné que toute l'astuce tournait autour de rendre le remplissage de l'élément enveloppe interne par rapport à la largeur de son conteneur qui dépendait à son tour de la largeur de son enfant, nous remplissons cette condition et invoquons un comportement non défini. Cependant, cela fonctionne actuellement dans les 4 principaux navigateurs, contrairement à certains ajustements apparemment conformes aux spécifications que j'ai essayés, comme changer .rotation-wrapper-innerpour être un frère .element-to-rotateplutôt qu'un parent.

Mark Amery
la source
1
J'ai accepté cela comme la réponse pour le moment. Les writing-modesolutions seront la meilleure approche si IE 11 n'a pas besoin d'être pris en charge.
Chris
1
Prise notable: ne fonctionne pas pour d'autres rotations comme 45deg.
E. Villiger
bien qu'acceptée, ce n'est pas la bonne réponse à jour. Voir ci-dessous pour les réponses en utilisant le mode écriture.
GilShalit
@ mark-amery, j'ai dû ajuster le CSS pour mon code: imgur.com/a/ZgafkgE 1ère image avec votre CSS, 2ème avec des ajustements J'ai supprimé transformOrigin: 'en haut à gauche', & changé de transformation: 'translate (-100% ) ', pour transformer:' translate (20%) ', <div style = {{flexDirection:' column ', alignItems:' center ',}}> <div style = {{display:' table ', margin: 30 ,}}> <div style = {{padding: '50% 0 ', height: 0}}> <img src = {image} style = {{display:' block ', marginTop:' -50% ', transformer : 'rotate (-90deg) translate (20%)', maxWidth: 300, maxHeight: 400,}} /> </div> </div> <div> {title} </div> </div>
Dan Bitter
44

Comme le souligne à juste titre le commentaire de G-Cyr , le soutien actuel writing-modeest 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>

Bram Vanroy
la source
1
Heureux d'avoir fait défiler vers le bas - cela m'a sauvé d'un monde de douleur en utilisant des rotations et des traductions.
James McLaughlin
3
Pour obtenir la rotation demandée dans la question, utilisezwriting-mode: vertical-lr; transform: rotate(180deg);
James McLaughlin
41

Malheureusement (?) C'est ainsi que cela 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 sa taille lorsque vous faites pivoter les choses.

Imaginez une rotation de moins de 90 ° (par exemple transform: rotate(45deg)): vous devriez introduire une telle boîte invisible qui a maintenant des dimensions ambiguës basées sur les dimensions d'origine de l'objet que vous faites tourner et la valeur de rotation réelle.

Objet pivoté

Du coup, vous n'avez pas seulement le widthet heightde l'objet que vous avez tourné, mais vous avez également le widthet heightde la "boîte invisible" autour de lui. Imaginez demander la largeur extérieure de cet objet - que renverrait-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 corriger ce comportement (ou devrais-je dire, «automatiser»). Bien sûr, vous pouvez augmenter la taille de votre conteneur parent à la main ou écrire du JavaScript pour gérer cela.

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

Comme décrit dans la spécification :

Dans l'espace de noms HTML, la propriété transform 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 que vous transformez, sauf si vous les faites manuellement.

La seule chose qui est prise en compte lors de la transformation de votre objet est la zone de débordement:

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

Voir ce jsfiddle pour en savoir plus.

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


Si vous souhaitez gérer cela en utilisant JavaScript, jetez un œil à cette question.

MMM
la source
8
Je dirais que dans un cadre d'interface utilisateur normal, la largeur et la hauteur de vos parents doivent être enroulées autour de la largeur et de la hauteur de la zone de délimitation de l'enfant qui est affectée comme vous l'avez mentionné par les rotations de l'enfant. La largeur et la hauteur des enfants sont leur largeur et hauteur avant rotation et la largeur et la hauteur des parents sont définies par la bbox des enfants. C'est très basique et je dirais que CSS / HTML devrait fonctionner de cette façon ... Si c'était le cas, personne ne poserait de questions sur ce sujet qui ne peut être corrigé que de manière insatisfaisante avec des hacks.
user18490
25

Utilisez des pourcentages pour paddinget un pseudo élément pour pousser 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/

Litek
la source
3
Le problème avec cette solution est que l'élément conserve sa largeur d'origine, donc dans (par exemple) un tableau, s'il s'agissait de la cellule la plus large, il pousserait cette colonne large avec des espaces inutiles qui auraient été pris si le texte avait pas été tourné. C'est ennuyeux.
Jez
@litek, pourriez-vous jeter un œil à jsfiddle.net/MTyFP/7 ? J'ai presque le même problème avec l'image. Question: stackoverflow.com/questions/31072959/…
Awlad Liton
11

Veuillez voir la version mise à jour dans mon autre réponse . Cette réponse n'est plus valable 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 de l' .getBoundingClientRect()objet et ses valeurs widthet height, en combinaison avec la possibilité d'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.)

getBoundingClientRectcalcule la largeur et la hauteur réelles de la boîte de l'élément. Nous pouvons donc tout simplement 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 en boucle et découvrir lequel est le plus grand et définir la hauteur du parent sur l'enfant le plus grand. Cependant, j'ai décidé de rendre l'exemple plus simple. Vous pouvez également ajouter un remplissage du parent au height si vous le souhaitez, ou vous pouvez utiliser une box-sizingvaleur pertinente en CSS.)

Notez cependant que j'ai ajouté un translateet transform-originà votre CSS pour rendre le positionnement plus flexible et précis.

transform: rotate(-90deg) translateX(-100%);
transform-origin: top left;
Bram Vanroy
la source
Vraiment? Une question css avec une réponse JQUERY et vous l'appelez une réponse parfaite @MichaelLumbroso? Cela fonctionne oui, mais il n'y a pas besoin de javascript ou de toute une bibliothèque js comme jQuery
Nebulosar
1
@Nebulosar Belle fouille. À l'époque (il y a déjà deux ans et demi!), C'était la seule réponse qui ne posait aucun problème de positionnement ou que les pseudo éléments ne fonctionnaient pas bien. Heureusement, la prise en charge de certaines propriétés par le navigateur s'est améliorée. Veuillez voir ma modification.
Bram Vanroy
2
Il s'agit essentiellement de deux réponses totalement indépendantes en une. Il est parfaitement conforme aux règles de publier plusieurs réponses à une question; Je pense qu'il aurait été préférable que votre nouvelle solution ajoutée en 2017 ait été une nouvelle réponse, afin que les deux réponses complètement séparées puissent être votées et commentées séparément.
Mark Amery
@MarkAmery Vous avez raison. J'ai édité la question et ajouté une nouvelle réponse ici .
Bram Vanroy
J'ai pu utiliser cette réponse et l'événement de chargement d'image pour obtenir une solution fonctionnelle pour les images en utilisant: $ pic.on ('load', function () {$ pic.css ('min-height', $ pic [0] .getBoundingClientRect (). hauteur);});
Miika L.
-18

Je sais que c'est un vieux post mais je l'ai trouvé en luttant exactement avec le même problème. La solution qui fonctionne pour moi est une méthode "low-tech" plutôt rudimentaire en entourant simplement le div que je fais pivoter de 90 degrés avec beaucoup de

<br>

Connaissant la largeur approximative (qui devient la hauteur après rotation) de div, je peux compenser la différence en ajoutant des br autour de cette div afin que le contenu au-dessus et en dessous soit poussé en conséquence.

vic_sk
la source
10
Peut-être que cela fonctionne, mais vous ne devriez jamais faire cela.
MMM
Merci pour vos commentaires! Oui, une meilleure façon d'utiliser la propriété margin-top de div pour ajuster la hauteur en pixels lorsque l'objet est pivoté.
vic_sk