Elementos girados em CSS que afetam a altura dos pais corretamente

119

Digamos que eu tenha algumas colunas, das quais gostaria de girar os 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>

Com este CSS:

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

Acaba ficando assim:

Uma série de quatro elementos de nível de bloco.  O texto do terceiro elemento é girado.

É possível escrever qualquer CSS que fará com que o elemento girado afete a altura de seu pai, de modo que o texto não se sobreponha aos outros elementos? Algo assim:

O texto da terceira coluna agora afeta a altura de sua caixa pai de forma que o texto caiba dentro da caixa.

Chris
fonte
7
O modo de escrita agora está disponível para a maioria dos navegadores (costumava ser um recurso IE5). Agora, eu faria jsfiddle.net/MTyFP/698, consulte: developer.mozilla.org/en-US/docs/Web/CSS/writing-mode
G-Cyrillus de
1
@ G-Cyr writing-modeestá disponível para a maioria dos navegadores, mas tem muitos erros. Sobre uma questão relacionada, passei por várias iterações tentando contornar bugs específicos do navegador antes de desistir; você pode ver minha sucessão de falhas em stackoverflow.com/posts/47857248/revisions , onde começo com algo que funciona no Chrome, mas não no Firefox ou Edge, em seguida, interrompo o Chrome no processo de consertar os outros dois e acabo revertendo meu responder e rotulá-lo como apenas Chrome. Seja cauteloso, se você quiser seguir este caminho. (Observe também que não é útil com elementos não textuais.)
Mark Amery
1
O modo de escrita afaik só funciona em texto ...
Fredrik Johansson

Respostas:

51

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

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 transformpropriedade CSS acontecem após o layout ter ocorrido. Em outras palavras, usar transformem 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 transformfunciona. 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:

  1. De alguma forma, construa algum outro elemento cuja altura seja igual à largura do.element-to-rotate
  2. Escreva nossa transformação em de .element-to-rotatemodo 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-rotatede largura ?

O principal componente de nossa estratégia aqui é o padding: 50% 0on .rotation-wrapper-inner. Este exploit é um detalhe excêntrico da especificação parapadding : os preenchimentos de porcentagem, mesmo para padding-tope 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:

  1. Montamos display: tableem .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 .)
  2. Definimos heightde .rotation-wrapper-innercomo 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-rotatetenha 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-innerpara ser irmão em .element-to-rotatevez de pai.

Mark Amery
fonte
1
Aceitei isso como a resposta no momento. As writing-modesoluções serão a melhor abordagem se o IE 11 não precisar de suporte.
Chris
1
Captura notável: não funciona para outras rotações como 45deg.
E. Villiger
embora aceita, esta não é a resposta correta em dia. Veja abaixo as respostas usando o modo de escrita.
GilShalit
@ mark-amery, tive que ajustar o CSS para meu código: imgur.com/a/ZgafkgE 1ª imagem com seu CSS, 2ª com ajustes, removi transformOrigin: 'superior esquerdo', e alterei 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 o comentário de G-Cyr corretamente aponta, o suporte atual para writing-modeé mais do que decente . Combinado com uma rotação simples, você obtém o resultado exato que deseja. Veja o exemplo abaixo.

.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
fonte
1
Ainda bem que rolei para baixo - isso me salvou de um mundo de dor usando rotações e traduções.
James McLaughlin
3
Para obter a rotação solicitada na pergunta, usewriting-mode: vertical-lr; transform: rotate(180deg);
James McLaughlin
40

Infelizmente (?) É assim que deve funcionar, embora você gire seu elemento, ele ainda tem uma certa largura e altura, que não muda após a rotação. Você o altera visualmente, mas não há nenhuma caixa de embalagem invisível que muda seu tamanho quando você gira as coisas.

Imagine girá-lo menos de 90 ° (por exemplo transform: rotate(45deg)): você teria que introduzir essa caixa invisível que agora tem dimensões ambíguas com base nas dimensões originais do objeto que você está girando e no valor de rotação real.

Objeto girado

De repente, você não tem apenas o widthe heightdo objeto que girou, mas também o widtheheight da "caixa invisível" ao seu redor. Imagine solicitar a largura externa desse objeto - o que ele retornaria? A largura do objeto ou nossa nova caixa? Como poderíamos distinguir entre os dois?

Portanto, não há CSS que você possa escrever para corrigir esse comportamento (ou devo dizer, "automatize-o"). É claro que você pode aumentar o tamanho do seu container pai manualmente ou escrever algum JavaScript para lidar com isso.

(Só para ficar claro, você pode tentar usar element.getBoundingClientRectpara obter o retângulo mencionado antes).

Conforme descrito nas especificações :

No namespace HTML, a propriedade transform não afeta o fluxo do conteúdo em torno do elemento transformado.

Isso significa que nenhuma alteração será feita no conteúdo ao redor do objeto que você está transformando, a menos que você as faça manualmente.

A única coisa que é levada em consideração quando você transforma seu objeto é a área de estouro:

(...) a extensão da área de transbordamento leva em consideração os elementos transformados. Esse comportamento é semelhante ao que acontece quando os elementos são deslocados por meio do posicionamento relativo .

Veja este jsfiddle para saber mais.

Na verdade, é muito bom comparar esta situação a um deslocamento de objeto usando: position: relative- o conteúdo ao redor não muda, mesmo que você esteja movendo seu objeto ( exemplo ).


Se você quiser lidar com isso usando JavaScript, dê uma olhada nesta questão.

MMM
fonte
8
Eu diria que em uma estrutura de IU normal, a largura e a altura de seus pais devem ser ajustadas ao redor da largura e altura da caixa delimitadora da criança, que é afetada, como você mencionou, pelas rotações da criança. A largura e altura dos filhos é sua largura e altura antes da rotação e a largura e altura dos pais são definidas pela caixa dos filhos. Isso é muito básico e eu diria que CSS / HTML deveria funcionar dessa forma ... Se fosse, ninguém faria perguntas sobre esse assunto que só podem ser corrigidas de maneiras insatisfatórias com hacks.
user18490
25

Use porcentagens para paddinge um pseudoelemento para enviar o conteúdo. No JSFiddle deixei o pseudo elemento vermelho para mostrá-lo e você terá que compensar o deslocamento do texto, mas acho que é o caminho a percorrer.

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

Uma descrição desta solução pode ser encontrada aqui: http://kizu.ru/en/fun/rotated-text/

Litek
fonte
3
O problema com esta solução é que o elemento mantém sua largura original, então em (por exemplo) uma tabela, se fosse a célula mais larga, ele empurraria essa coluna para fora com espaços em branco desnecessários que seriam ocupados se o texto tivesse não foi girado. Isso é irritante.
Jez
@litek, você poderia dar uma olhada em jsfiddle.net/MTyFP/7 ? Estou tendo quase o mesmo problema com a imagem. Pergunta: stackoverflow.com/questions/31072959/…
Awlad Liton
11

Por favor, veja a versão atualizada em minha outra resposta . Esta resposta não é mais válida e simplesmente não funciona mais. Vou deixar aqui por motivos históricos.


Esta questão é bastante antiga, mas com o suporte atual para o .getBoundingClientRect() objeto e suas widthe heightvalores, em combinação com a capacidade de usar este método puro juntamente com transform, eu acho que a minha solução deve ser mencionado também.

Veja em ação aqui . (Testado no Chrome 42, FF 33 e 37.)

getBoundingClientRectcalcula a caixa real largura e a altura do elemento. Muito simplesmente, então, podemos percorrer todos os elementos e definir sua altura mínima para a altura real da caixa de seus filhos.

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

(Com algumas modificações, você pode percorrer os filhos e descobrir qual é o mais alto e definir a altura do pai para o filho mais alto. No entanto, decidi tornar o exemplo mais direto. Você também pode adicionar preenchimento do pai ao altura se quiser, ou você pode usar um box-sizingvalor relevante em CSS.)

Observe, porém, que adicionei um translatee transform-originao seu CSS para tornar o posicionamento mais flexível e preciso.

transform: rotate(-90deg) translateX(-100%);
transform-origin: top left;
Bram Vanroy
fonte
Realmente? Uma pergunta css com uma resposta JQUERY e você chama de resposta perfeita @MichaelLumbroso? Funciona sim, mas não há necessidade de javascript ou de uma biblioteca js inteira como jQuery
Nebulosar
1
@Nebulosar Boa escavação. Na época (já há dois anos e meio!) Esta era a única resposta que não causou problemas de posicionamento ou pseudoelementos não funcionavam bem. Felizmente, o suporte do navegador para algumas propriedades melhorou. Por favor, veja minha edição.
Bram Vanroy
2
Estas são essencialmente duas respostas completamente não relacionadas em uma. Está perfeitamente dentro das regras postar várias respostas para uma pergunta; Eu acho que teria sido preferível se sua nova solução adicionada em 2017 tivesse sido uma nova resposta, de modo que as duas respostas completamente separadas pudessem ser votadas e comentadas separadamente.
Mark Amery
@MarkAmery Você está certo. Editei a pergunta e adicionei uma nova resposta aqui .
Bram Vanroy
Consegui usar esta resposta e o evento de carregamento de imagem para obter uma solução de trabalho para imagens usando: $ pic.on ('load', function () {$ pic.css ('min-height', $ pic [0] .getBoundingClientRect (). height);});
Miika L.
-18

Eu sei que é um post antigo, mas eu o encontrei enquanto lutava exatamente com o mesmo problema. A solução que funciona para mim é um método "de baixa tecnologia" bastante rudimentar, simplesmente circundando a div que giro em 90 graus com muitos

<br>

Sabendo a largura aproximada (que se torna altura após a rotação) de div, posso compensar a diferença adicionando br's ao redor desse div para que o conteúdo acima e abaixo seja empurrado de acordo.

vic_sk
fonte
10
Talvez isso funcione, mas você nunca deveria estar fazendo isso.
MMM
Obrigado pelo seu feedback! Sim, uma maneira melhor de usar a propriedade margin-top de div para ajustar a altura em pixels quando o objeto é girado.
vic_sk