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.



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.


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>


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.


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/


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;

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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.