Supondo que você queira girar 90 graus, isso é possível, mesmo para elementos que não sejam de texto - mas, como muitas coisas interessantes em CSS, isso requer um pouco de astúcia. Minha solução também invoca tecnicamente o comportamento indefinido de acordo com as especificações CSS 2 - então, embora eu tenha testado e confirmado que funciona no Chrome, Firefox, Safari e Edge, não posso prometer que não vai quebrar no futuro versão do navegador.
Resposta curta
Dado um HTML como este, onde você deseja girar .element-to-rotate
...
<div id="container">
<something class="element-to-rotate">bla bla bla</something>
</div>
... introduza dois elementos wrapper em torno do elemento que você deseja girar:
<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>
... e, em seguida, use o seguinte CSS para girar no sentido anti-horário (ou veja o comentado transform
para ver uma maneira de mudar para a rotação no sentido horário):
.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;
}
Demonstração de snippet de pilha
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>
Como é que isso funciona?
A confusão diante dos encantamentos que usei acima é razoável; há muita coisa acontecendo e a estratégia geral não é direta e requer algum conhecimento de trivialidades de CSS para ser entendida. Vamos examinar isso passo a passo.
O cerne do problema que enfrentamos é que as transformações aplicadas a um elemento usando sua transform
propriedade CSS acontecem após o layout ter ocorrido. Em outras palavras, usar transform
em um elemento não afeta o tamanho ou a posição de seu pai ou de quaisquer outros elementos. Não há absolutamente nenhuma maneira de mudar esse fato de como transform
funciona. Assim, para criar o efeito de girar um elemento e fazer com que a altura de seu pai respeite a rotação, precisamos fazer o seguinte:
- De alguma forma, construa algum outro elemento cuja altura seja igual à largura do
.element-to-rotate
- Escreva nossa transformação em de
.element-to-rotate
modo a sobrepor exatamente no elemento da etapa 1.
O elemento da etapa 1 deve ser .rotation-wrapper-outer
. Mas como podemos fazer com que a sua altura para ser igual ao .element-to-rotate
de largura ?
O principal componente de nossa estratégia aqui é o padding: 50% 0
on .rotation-wrapper-inner
. Este exploit é um detalhe excêntrico da especificação parapadding
: os preenchimentos de porcentagem, mesmo para padding-top
e padding-bottom
, são sempre definidos como porcentagens da largura do contêiner do elemento . Isso nos permite realizar o seguinte truque de duas etapas:
- Montamos
display: table
em .rotation-wrapper-outer
. Isso faz com que ele tenha uma largura reduzida para caber , o que significa que sua largura será definida com base na largura intrínseca de seu conteúdo - ou seja, com base na largura intrínseca de .element-to-rotate
. (On suporte a navegadores, podemos conseguir isso de forma mais limpa com width: max-content
, mas a partir de Dezembro de 2017, max-content
é ainda não é suportado em Borda .)
- Definimos
height
de .rotation-wrapper-inner
como 0 e, em seguida, definimos seu preenchimento como 50% 0
(ou seja, 50% superior e 50% inferior). Isso faz com que ele ocupe um espaço vertical igual a 100% da largura de seu pai - que, por meio do truque da etapa 1, é igual à largura de .element-to-rotate
.
Em seguida, tudo o que resta é executar a rotação e o posicionamento reais do elemento filho. Naturalmente, transform: rotate(-90deg)
faz a rotação; usamos transform-origin: top left;
para fazer com que a rotação aconteça ao redor do canto superior esquerdo do elemento girado, o que torna a translação subsequente mais fácil de raciocinar, uma vez que deixa o elemento girado diretamente acima de onde, de outra forma, teria sido desenhado. Podemos então usar translate(-100%)
para arrastar o elemento para baixo por uma distância igual à largura de pré-rotação.
Isso ainda não acertou o posicionamento, porque ainda precisamos compensar para o preenchimento superior de 50% ativado .rotation-wrapper-outer
. Conseguimos isso garantindo que .element-to-rotate
tenha display: block
(para que as margens funcionem corretamente nele) e, em seguida, aplicando um -50% margin-top
- observe que as margens de porcentagem também são definidas em relação à largura do elemento pai.
E é isso!
Por que isso não é totalmente compatível com as especificações?
Por causa da seguinte nota da definição de preenchimentos percentuais e margens na especificação (negrito):
A porcentagem é calculada em relação à largura do bloco que contém a caixa gerada , mesmo para 'padding-top' e 'padding-bottom' . Se a largura do bloco contido depender deste elemento, então o layout resultante é indefinido no CSS 2.1.
Como todo o truque girava em torno de fazer o preenchimento do elemento de invólucro interno ser relativo à largura de seu contêiner que, por sua vez, dependia da largura de seu filho, estamos atingindo essa condição e invocando um comportamento indefinido. No momento, ele funciona em todos os quatro navegadores principais - ao contrário de alguns ajustes aparentemente compatíveis com as especificações para abordar que tentei, como mudar .rotation-wrapper-inner
para ser irmão em .element-to-rotate
vez de pai.