Elementos rotados en CSS que afectan la altura de sus padres correctamente

119

Digamos que tengo un par de columnas, algunas de las cuales me gustaría rotar los valores 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>

Con este CSS:

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

Termina luciendo así:

Una serie de cuatro elementos a nivel de bloque.  El texto del tercer elemento se rota.

¿Es posible escribir cualquier CSS que haga que el elemento girado afecte la altura de su padre, de modo que el texto no se superponga a los otros elementos? Algo como esto:

El texto de la tercera columna ahora afecta la altura de su cuadro principal, de modo que el texto cabe dentro del cuadro.

Chris
fuente
1
podría ser útil stackoverflow.com/questions/7565542/…
Pete
7
El modo de escritura ahora está disponible para la mayoría de los navegadores (solía ser una función IE5). Lo haría hoy en día jsfiddle.net/MTyFP/698 ver: developer.mozilla.org/en-US/docs/Web/CSS/writing-mode
G-Cyrillus
1
@ G-Cyr writing-modeestá disponible para la mayoría de los navegadores, pero tiene muchos errores. En una pregunta relacionada, pasé por varias iteraciones para tratar de solucionar los errores específicos del navegador antes de rendirme; puede ver mi sucesión de fallas en stackoverflow.com/posts/47857248/revisions , donde comienzo con algo que funciona en Chrome pero no en Firefox o Edge, luego rompo Chrome en el proceso de arreglar los otros dos, y termino revirtiendo mi responder y etiquetarlo como solo para Chrome. Tenga cuidado si quiere seguir este camino. (También tenga en cuenta que no sirve de nada con elementos que no son de texto).
Mark Amery
1
El modo de escritura afaik solo funciona en texto ...
Fredrik Johansson

Respuestas:

51

Suponiendo que desea rotar 90 grados, esto es posible, incluso para elementos que no son de texto, pero como muchas cosas interesantes en CSS, requiere un poco de astucia. Mi solución también invoca técnicamente un comportamiento indefinido de acuerdo con la especificación CSS 2, así que aunque he probado y confirmado que funciona en Chrome, Firefox, Safari y Edge, no puedo prometerle que no se romperá en el futuro. lanzamiento del navegador.

Respuesta corta

Dado HTML como este, donde desea rotar .element-to-rotate...

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

... introduzca dos elementos de envoltura alrededor del elemento que desea rotar:

<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>

... y luego use el siguiente CSS, para rotar en sentido antihorario (o vea el comentario transformpara una forma de cambiarlo a una rotación en sentido horario):

.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;
}

Demostración de fragmentos de pila

¿Como funciona esto?

La confusión ante los encantamientos que he usado anteriormente es razonable; están sucediendo muchas cosas, y la estrategia general no es sencilla y requiere algunos conocimientos de trivia de CSS para comprender. Veámoslo paso a paso.

El núcleo del problema al que nos enfrentamos es que las transformaciones aplicadas a un elemento utilizando su transformpropiedad CSS ocurren después de que se ha realizado el diseño. En otras palabras, el uso transformde un elemento no afecta en absoluto el tamaño o la posición de su padre ni de ningún otro elemento. No hay absolutamente ninguna manera de cambiar este hecho de cómo transformfunciona. Por lo tanto, para crear el efecto de rotar un elemento y hacer que la altura de su padre respete la rotación, debemos hacer lo siguiente:

  1. De alguna manera construya algún otro elemento cuya altura sea igual al ancho del.element-to-rotate
  2. Escriba nuestra transformación .element-to-rotatepara superponerla exactamente en el elemento del paso 1.

El elemento del paso 1 será .rotation-wrapper-outer. Pero, ¿cómo podemos hacer que su altura sea ​​igual a .element-to-rotatela anchura de ?

El componente clave de nuestra estrategia aquí es el padding: 50% 0encendido .rotation-wrapper-inner. Este exploit es un detalle excéntrico de la especificación parapadding : ese porcentaje de relleno, incluso para padding-topy padding-bottom, siempre se define como porcentajes del ancho del contenedor del elemento . Esto nos permite realizar el siguiente truco de dos pasos:

  1. Nos pusimos display: tableen marcha .rotation-wrapper-outer. Esto hace que tenga un ancho de encogimiento para ajustar , lo que significa que su ancho se establecerá en función del ancho intrínseco de su contenido, es decir, en función del ancho intrínseco de .element-to-rotate. (En apoyo a los navegadores, podríamos lograr esto de forma más limpia con width: max-content, pero a partir de diciembre de 2017, max-contentestá todavía no se admite en el borde .)
  2. Establecemos el heightde .rotation-wrapper-inneren 0, luego establecemos su relleno en 50% 0(es decir, 50% superior y 50% inferior). Esto hace que ocupe un espacio vertical igual al 100% del ancho de su padre, que, a través del truco del paso 1, es igual al ancho de .element-to-rotate.

A continuación, todo lo que queda es realizar la rotación y el posicionamiento reales del elemento hijo. Naturalmente, transform: rotate(-90deg)hace la rotación; usamos transform-origin: top left;para hacer que la rotación ocurra alrededor de la esquina superior izquierda del elemento girado, lo que hace que la traslación posterior sea más fácil de razonar, ya que deja el elemento girado directamente encima de donde de otro modo se habría dibujado. Luego, podemos usar translate(-100%)para arrastrar el elemento hacia abajo una distancia igual a su ancho de prerotación.

Eso todavía no consigue el posicionamiento correcto, porque todavía necesitamos compensar el 50% del relleno superior activado .rotation-wrapper-outer. Lo logramos asegurándonos de que .element-to-rotatetiene display: block(para que los márgenes funcionen correctamente en él) y luego aplicando un -50% margin-top; tenga en cuenta que los márgenes porcentuales también se definen en relación con el ancho del elemento principal.

¡Y eso es!

¿Por qué no cumple totalmente con las especificaciones?

Debido a la siguiente nota de la definición de porcentajes de relleno y márgenes en la especificación (negrita mía):

El porcentaje se calcula con respecto al ancho del bloque contenedor de la caja generada , incluso para 'padding-top' y 'padding-bottom' . Si el ancho del bloque contenedor depende de este elemento, entonces el diseño resultante no está definido en CSS 2.1.

Dado que todo el truco giraba en torno a hacer que el relleno del elemento de envoltura interior fuera relativo al ancho de su contenedor, que a su vez dependía del ancho de su hijo, estamos alcanzando esta condición e invocando un comportamiento indefinido. Sin embargo, actualmente funciona en los 4 navegadores principales, a diferencia de algunos ajustes de enfoque aparentemente compatibles con las especificaciones que he intentado, como cambiar .rotation-wrapper-innerpara ser hermano en .element-to-rotatelugar de padre.

Mark Amery
fuente
1
Acepté esto como la respuesta por el momento. Las writing-modesoluciones serán el mejor enfoque si IE 11 no necesita ser compatible.
Chris
1
Captura notable: no funciona para otras rotaciones como 45 grados.
E. Villiger
aunque se acepta, esta no es la respuesta correcta actualizada. Vea a continuación las respuestas usando el modo de escritura.
GilShalit
@ mark-amery, tuve que ajustar el CSS para mi código: imgur.com/a/ZgafkgE Primera imagen con su CSS, segunda con ajustes Eliminé transformOrigin: 'top left', y cambié transform: 'translate (-100% ) ', para transformar:' 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% ', transform : 'rotate (-90deg) translate (20%)', maxWidth: 300, maxHeight: 400,}} /> </div> </div> <div> {title} </div> </div>
Dan Amargo
44

Como señala con razón el comentario de G-Cyr , el soporte actual para writing-modees más que decente . Combinado con una simple rotación, obtiene el resultado exacto que desea. Vea el ejemplo a continuación.

.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
fuente
1
Me alegro de haberme desplazado hacia abajo, esto me salvó de un mundo de dolor al usar rotaciones y traducciones.
James McLaughlin
3
Para obtener la rotación solicitada en la pregunta, usewriting-mode: vertical-lr; transform: rotate(180deg);
James McLaughlin
40

Desafortunadamente (?) Así es como se supone que funciona, aunque gire su elemento, todavía tiene cierto ancho y alto, que no cambia después de la rotación. Lo cambias visualmente, pero no hay una caja de envoltura invisible que cambie su tamaño cuando rotas las cosas.

Imagínese rotándolo menos de 90 ° (por ejemplo transform: rotate(45deg)): tendría que introducir una caja invisible que ahora tiene dimensiones ambiguas basadas en las dimensiones originales del objeto que está rotando y el valor de rotación real.

Objeto girado

De repente, no solo tiene el widthy heightdel objeto que ha girado, sino que también tiene el widthy heightde la "caja invisible" a su alrededor. Imagine solicitar el ancho exterior de este objeto, ¿qué devolvería? ¿El ancho del objeto o nuestra nueva caja? ¿Cómo distinguiríamos entre ambos?

Por lo tanto, no hay CSS que pueda escribir para corregir este comportamiento (o debería decir, "automatizarlo"). Por supuesto, puede aumentar el tamaño de su contenedor principal a mano o escribir algo de JavaScript para manejar eso.

(Para que quede claro, puede intentar usar element.getBoundingClientRectpara obtener el rectángulo mencionado anteriormente).

Como se describe en la especificación :

En el espacio de nombres HTML, la propiedad transform no afecta el flujo del contenido que rodea al elemento transformado.

Esto significa que no se realizarán cambios en el contenido que rodea al objeto que está transformando, a menos que los haga a mano.

Lo único que se tiene en cuenta cuando transforma su objeto es el área de desbordamiento:

(...) la extensión del área de desbordamiento tiene en cuenta los elementos transformados. Este comportamiento es similar a lo que sucede cuando los elementos se desplazan mediante el posicionamiento relativo .

Vea este jsfiddle para obtener más información.

De hecho, es bastante bueno comparar esta situación con el desplazamiento de un objeto utilizando: position: relative- el contenido circundante no cambia, aunque esté moviendo el objeto ( ejemplo ).


Si desea manejar esto usando JavaScript, eche un vistazo a esta pregunta.

MMM
fuente
8
Yo diría que en un marco de interfaz de usuario normal, el ancho y la altura de sus padres deben estar envueltos alrededor del ancho y alto del cuadro delimitador del niño, que se ve afectado como mencionó por las rotaciones del niño. El ancho y alto de los hijos es su ancho y alto antes de la rotación y el ancho y alto de los padres está definido por el bbox de los hijos. Eso es muy básico y yo diría que CSS / HTML debería funcionar de esa manera ... Si lo fuera, nadie haría preguntas sobre este tema que solo se pueden solucionar de manera insatisfactoria con los trucos.
user18490
25

Utilice porcentajes para paddingy un pseudo elemento para impulsar el contenido. En JSFiddle dejé el pseudo elemento rojo para mostrarlo y tendrás que compensar el desplazamiento del texto, pero creo que ese es el camino a seguir.

.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/

Se puede encontrar un informe de esta solución aquí: http://kizu.ru/en/fun/rotated-text/

Litek
fuente
3
El problema con esta solución es que el elemento mantiene su ancho original, por lo que en (por ejemplo) una tabla, si fuera la celda más ancha, empujaría esa columna hacia afuera con espacios en blanco innecesarios que se habrían ocupado si el texto hubiera no se ha girado. Eso es molesto.
Jez
@litek, ¿podría echar un vistazo a jsfiddle.net/MTyFP/7 ? Tengo casi el mismo problema con la imagen. Pregunta: stackoverflow.com/questions/31072959/…
Awlad Liton
11

Consulte la versión actualizada en mi otra respuesta . Esta respuesta ya no es válida y simplemente ya no funciona. Lo dejo aquí por razones históricas.


Esta pregunta es bastante antigua, pero con el soporte actual para el .getBoundingClientRect()objeto y sus valores widthy height, en combinación con la capacidad de usar este método ordenado junto con transform, creo que mi solución también debería mencionarse.

Véalo en acción aquí . (Probado en Chrome 42, FF 33 y 37.)

getBoundingClientRectcalcula el ancho y alto de la caja real del elemento. Entonces, simplemente, podemos recorrer todos los elementos y establecer su altura mínima a la altura real de la caja de sus hijos.

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

(Con algunas modificaciones, puede recorrer los niños y averiguar cuál es el más alto y establecer la altura del padre en el hijo más alto. Sin embargo, decidí hacer el ejemplo más sencillo. También puede agregar el relleno del padre al altura si lo desea, o puede usar un box-sizingvalor relevante en CSS).

Sin embargo, tenga en cuenta que agregué un translatey transform-origina su CSS para hacer que el posicionamiento sea más flexible y preciso.

transform: rotate(-90deg) translateX(-100%);
transform-origin: top left;
Bram Vanroy
fuente
De Verdad? ¿Una pregunta CSS con una respuesta JQUERY y la llamas respuesta perfecta @MichaelLumbroso? Funciona, sí, pero no hay necesidad de javascript o una biblioteca js completa como jQuery
Nebulosar
1
@Nebulosar Buena excavación. En ese momento (¡ya hace dos años y medio!), Esta fue la única respuesta que no causó ningún problema con el posicionamiento o los pseudo elementos que no funcionaban bien. Afortunadamente, ha mejorado la compatibilidad del navegador con algunas propiedades. Por favor vea mi edición.
Bram Vanroy
2
Se trata esencialmente de dos respuestas en una que no guardan ninguna relación. Está perfectamente dentro de las reglas publicar múltiples respuestas a una pregunta; Creo que hubiera sido preferible que su nueva solución agregada en 2017 hubiera sido una nueva respuesta, de modo que las dos respuestas completamente separadas pudieran votarse y comentarse por separado.
Mark Amery
@MarkAmery Tienes razón. Edité la pregunta y agregué una nueva respuesta aquí .
Bram Vanroy
Pude usar esta respuesta y el evento de carga de imagen para obtener una solución funcional para imágenes usando: $ pic.on ('load', function () {$ pic.css ('min-height', $ pic [0] .getBoundingClientRect (). altura);});
Miika L.
-18

Sé que es una publicación antigua, pero la encontré mientras luchaba con exactamente el mismo problema. La solución que me funciona es un método bastante tosco de "baja tecnología", simplemente rodeando el div. Giro 90 grados con una gran cantidad de

<br>

Sabiendo el ancho aproximado (que se convierte en altura después de la rotación) de div, puedo compensar la diferencia agregando br alrededor de este div para que el contenido de arriba y de abajo se empuje en consecuencia.

vic_sk
fuente
10
Quizás esto funcione, pero nunca deberías hacer esto.
MMM
¡Gracias por tus comentarios! Sí, una mejor manera es que uso la propiedad margin-top de div para ajustar la altura en píxeles cuando se gira el objeto.
vic_sk