Obrócone elementy w CSS, które poprawnie wpływają na wzrost rodzica

119

Powiedzmy, że mam kilka kolumn, z których niektóre chciałbym obracać wartościami:

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>

Z tym CSS:

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

Kończy się tak:

Seria czterech elementów blokowych.  Tekst trzeciego elementu jest obracany.

Czy można napisać dowolny CSS, który spowoduje, że obrócony element wpłynie na wysokość swojego rodzica, tak że tekst nie będzie zachodził na inne elementy? Coś takiego:

Tekst trzeciej kolumny wpływa teraz na wysokość jej ramki nadrzędnej w taki sposób, że tekst mieści się w ramce.

Chris
źródło
1
może być pomocny stackoverflow.com/questions/7565542/ ...
Pete
7
Tryb pisania jest teraz dostępny dla większości przeglądarek (używaj funkcji IE5). Zrobiłbym teraz jsfiddle.net/MTyFP/698, patrz: developer.mozilla.org/en-US/docs/Web/CSS/writing-mode
G-Cyrillus
1
@ G-Cyr writing-modejest dostępny dla większości przeglądarek, ale ma straszne błędy. Wracając do pokrewnego pytania, przeszedłem przez wiele iteracji, próbując obejść błędy specyficzne dla przeglądarki, zanim się poddałem; możesz zobaczyć moją serię niepowodzeń na stackoverflow.com/posts/47857248/revisions , gdzie zaczynam od czegoś, co działa w Chrome, ale nie w Firefoksie lub Edge, a następnie psuję Chrome w trakcie naprawiania pozostałych dwóch i kończę na odwróceniu mojego odpowiedzieć i oznaczyć go jako tylko Chrome. Bądź ostrożny, jeśli chcesz iść tą ścieżką. (Zauważ również, że nie ma to sensu w przypadku elementów nietekstowych.)
Mark Amery,
1
Tryb pisania afaik działa tylko na tekście ...
Fredrik Johansson

Odpowiedzi:

51

Zakładając, że chcesz obrócić o 90 stopni, jest to możliwe, nawet w przypadku elementów nietekstowych - ale podobnie jak wiele interesujących rzeczy w CSS wymaga odrobiny sprytu. Moje rozwiązanie również technicznie wywołuje niezdefiniowane zachowanie zgodnie ze specyfikacją CSS 2 - więc chociaż przetestowałem i potwierdziłem, że działa w Chrome, Firefox, Safari i Edge, nie mogę obiecać, że nie zepsuje się w przyszłości wydanie przeglądarki.

Krótka odpowiedź

Biorąc pod uwagę taki kod HTML, w którym chcesz obrócić .element-to-rotate...

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

... wprowadź dwa elementy opakowujące wokół elementu, który chcesz obrócić:

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

... a następnie użyj poniższego CSS, aby obrócić w lewo (lub zobacz komentarz w komentarzu, transformaby dowiedzieć się, jak zmienić go na obrót w prawo):

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

Demo fragmentu stosu

Jak to działa?

Zamieszanie w obliczu zaklęć, których użyłem powyżej, jest uzasadnione; dużo się dzieje, a ogólna strategia nie jest prosta i wymaga znajomości zagadnień CSS, aby ją zrozumieć. Przejdźmy przez to krok po kroku.

Sedno problemu polega na tym, że transformacje zastosowane do elementu za pomocą jego transformwłaściwości CSS następują po utworzeniu układu. Innymi słowy, użycie transformna elemencie w ogóle nie wpływa na rozmiar ani położenie jego rodzica ani żadnych innych elementów. Nie ma absolutnie żadnego sposobu, aby zmienić ten fakt, jak transformdziała. Tak więc, aby stworzyć efekt obracania elementu i uwzględnienia jego wysokości nadrzędnej względem obrotu, musimy wykonać następujące czynności:

  1. W jakiś sposób skonstruuj inny element, którego wysokość jest równa szerokości.element-to-rotate
  2. Napisz naszą transformację .element-to-rotatetak, aby nałożyć ją dokładnie na element z kroku 1.

Elementem z kroku 1 będzie .rotation-wrapper-outer. Ale jak możemy spowodować jego wysokość była równa .element-to-rotatejest szerokości ?

Kluczowym elementem naszej strategii jest tu padding: 50% 0na .rotation-wrapper-inner. Ten exploit jest ekscentrycznym szczegółem specyfikacjipadding : że procent wypełnienia, nawet dla padding-topi padding-bottom, jest zawsze definiowany jako procent szerokości kontenera elementu . Dzięki temu możemy wykonać następującą dwuetapową sztuczkę:

  1. Stawiamy display: tablena .rotation-wrapper-outer. Powoduje to, że ma szerokość zmniejszania, aby dopasować , co oznacza, że ​​jego szerokość zostanie ustawiona na podstawie wewnętrznej szerokości jego zawartości - to znaczy na podstawie wewnętrznej szerokości .element-to-rotate. (Na wsparcie przeglądarek, możemy osiągnąć więcej z czysto width: max-content, ale w grudniu 2017 roku, max-contentjest nadal nie jest obsługiwana w Krawędzi ).
  2. Ustawiamy heightof .rotation-wrapper-innerna 0, a następnie ustawiamy jego dopełnienie na 50% 0(czyli 50% na górze i 50% na dole). To powoduje, że zajmuje on w pionie przestrzeń równą 100% szerokości swojego rodzica - która, poprzez sztuczkę z kroku 1, jest równa szerokości .element-to-rotate.

Następnie pozostaje tylko wykonać faktyczny obrót i pozycjonowanie elementu potomnego. Oczywiście, transform: rotate(-90deg)czy rotacja; używamy transform-origin: top left;do wywoływania obrotu wokół lewego górnego rogu obracanego elementu, co sprawia, że ​​późniejsze tłumaczenie jest łatwiejsze do rozważenia, ponieważ pozostawia on obrócony element bezpośrednio nad miejscem, w którym zostałby narysowany. Następnie możemy translate(-100%)przeciągnąć element w dół o odległość równą szerokości przed obróceniem.

To wciąż nie do końca zapewnia prawidłowe pozycjonowanie, ponieważ nadal musimy przesunąć 50% górnego dopełnienia .rotation-wrapper-outer. Osiągamy to, upewniając się, że .element-to-rotatema display: block(aby marginesy działały poprawnie), a następnie stosując -50% margin-top- zwróć uwagę, że marginesy procentowe są również definiowane w stosunku do szerokości elementu nadrzędnego.

I to wszystko!

Dlaczego nie jest to w pełni zgodne ze specyfikacjami?

Ze względu na następującą uwagę z definicji procentowych wypełnień i marginesów w specyfikacji (moje pogrubienie):

Procent jest obliczany w odniesieniu do szerokości bloku zawierającego wygenerowane pole , nawet dla „padding-top” i „padding-bottom” . Jeśli szerokość zawierającego bloku zależy od tego elementu, to wynikowy układ jest niezdefiniowany w CSS 2.1.

Ponieważ cała sztuczka polegała na tym, aby wypełnienie wewnętrznego elementu opakowania było zależne od szerokości jego pojemnika, co z kolei zależało od szerokości jego elementu potomnego, trafiamy w ten warunek i wywołujemy niezdefiniowane zachowanie. Obecnie działa jednak we wszystkich 4 głównych przeglądarkach - w przeciwieństwie do niektórych pozornie zgodnych ze specyfikacją poprawek, które próbowałem, takich jak zmiana .rotation-wrapper-innerna rodzeństwo .element-to-rotatezamiast rodzica.

Mark Amery
źródło
1
Na razie zaakceptowałem to jako odpowiedź. Te writing-moderozwiązania będą najlepsze podejście, jeśli IE 11 nie muszą być obsługiwane.
Chris
1
Godny uwagi haczyk: nie działa przy innych obrotach, takich jak 45 stopni.
E. Villiger
Chociaż została zaakceptowana, nie jest to aktualna, poprawna odpowiedź. Zobacz poniżej odpowiedzi w trybie pisania.
GilShalit
@ mark-ameryka, musiałem dopasować CSS do mojego kodu: imgur.com/a/ZgafkgE 1. obraz z twoim CSS, 2. z poprawkami usunąłem transformOrigin: 'top left', & zmieniłem transform: 'translate (-100% ) ', to transform:' 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 Gorzki
44

Jak słusznie zwraca uwagę G-Cyr , obecne poparcie writing-modejest więcej niż przyzwoite . W połączeniu z prostym obróceniem uzyskasz dokładnie taki wynik, jaki chcesz. Zobacz poniższy przykład.

.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
źródło
1
Cieszę się, że przewinąłem w dół - to uratowało mnie od świata bólu dzięki obrotom i tłumaczeniom.
James McLaughlin
3
Aby uzyskać rotację zadaną w pytaniu, użyjwriting-mode: vertical-lr; transform: rotate(180deg);
James McLaughlin
41

Niestety (?) Tak to ma działać, mimo że obrócisz element, który nadal ma określoną szerokość i wysokość, która nie zmienia się po obróceniu. Zmieniasz to wizualnie, ale nie ma niewidocznego pudełka do pakowania, które zmienia swój rozmiar, gdy obracasz rzeczy.

Wyobraź sobie, że obracasz go o mniej niż 90 ° (np. transform: rotate(45deg)): Musiałbyś wprowadzić takie niewidoczne pudełko, które ma teraz niejednoznaczne wymiary w oparciu o oryginalne wymiary obracanego obiektu i rzeczywistą wartość obrotu.

Obrócony obiekt

Nagle, nie masz tylko widthi heightobiektu zostały obrócone, ale mieć również widthi heightz „niewidzialnym polu” wokół niego. Wyobraź sobie, że żądasz zewnętrznej szerokości tego obiektu - co by to zwróciło? Szerokość obiektu czy nasze nowe pudełko? Jak rozróżnilibyśmy oba?

Dlatego nie istnieje CSS, który można by napisać, aby naprawić to zachowanie (a raczej „zautomatyzować” to). Oczywiście możesz ręcznie zwiększyć rozmiar swojego kontenera nadrzędnego lub napisać JavaScript, aby to obsłużyć.

(Dla jasności możesz spróbować użyć, element.getBoundingClientRectaby uzyskać wspomniany wcześniej prostokąt).

Jak opisano w specyfikacji :

W przestrzeni nazw HTML właściwość transform nie wpływa na przepływ treści otaczającej transformowany element.

Oznacza to, że nie zostaną wprowadzone żadne zmiany w zawartości otaczającej obiekt, który przekształcasz, chyba że zrobisz to ręcznie.

Jedyną rzeczą, która jest brana pod uwagę podczas przekształcania obiektu, jest obszar przepełnienia:

(...) rozległość obszaru przelewowego uwzględnia elementy przekształcone. To zachowanie jest podobne do tego, co dzieje się, gdy elementy są odsunięte poprzez pozycjonowanie względne .

Zobacz to jsfiddle, aby dowiedzieć się więcej.

Właściwie całkiem dobrze jest porównać tę sytuację z przesunięciem obiektu za pomocą: position: relative- otaczająca zawartość nie zmienia się, nawet jeśli poruszasz obiektem ( przykład ).


Jeśli chcesz sobie z tym poradzić za pomocą JavaScript, spójrz na to pytanie.

MMM
źródło
8
Powiedziałbym, że w normalnym interfejsie użytkownika szerokość i wysokość twoich rodziców powinny być owinięte wokół szerokości i wysokości pola ograniczającego dziecka, na które wpływa, jak wspomniałeś, obracanie się dziecka. Szerokość i wysokość dzieci to ich szerokość i wysokość przed obróceniem, a szerokość i wysokość rodziców określa bbox dzieci. To bardzo proste i powiedziałbym, że CSS / HTML powinno działać w ten sposób ... Gdyby tak było, nikt nie zadawałby pytań na ten temat, które można naprawić tylko w niezadowalający sposób za pomocą hacków.
user18490
25

Użyj wartości procentowych dla paddingi pseudoelementu, aby przekazać zawartość. W JSFiddle zostawiłem pseudoelement czerwony, aby go pokazać i będziesz musiał skompensować przesunięcie tekstu, ale myślę, że to jest droga.

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

Opis tego rozwiązania można znaleźć tutaj: http://kizu.ru/en/fun/rotated-text/

Litek
źródło
3
Problem z tym rozwiązaniem polega na tym, że element zachowuje swoją pierwotną szerokość, więc w (na przykład) tabeli, gdyby była to najszersza komórka, wypchnąłby tę kolumnę z niepotrzebnymi białymi znakami, które zostałyby zajęte, gdyby tekst nie został obrócony. To irytujące.
Jez
@litek, czy mógłbyś rzucić okiem na jsfiddle.net/MTyFP/7 ? Mam prawie ten sam problem z obrazem. Pytanie: stackoverflow.com/questions/31072959/…
Awlad Liton
11

Proszę zobaczyć zaktualizowaną wersję w mojej drugiej odpowiedzi . Ta odpowiedź jest już nieaktualna i po prostu nie działa. Zostawię to tutaj z powodów historycznych.


To pytanie jest dość stary, ale z bieżącego wsparcia dla .getBoundingClientRect()obiektu i jego widthand heightwartości, w połączeniu z możliwością wykorzystania tej metody wraz z schludny transform, myślę, że moje rozwiązanie należy wspomnieć również.

Zobacz to w akcji tutaj . (Testowane w Chrome 42, FF 33 i 37.)

getBoundingClientRectoblicza rzeczywistą szerokość i wysokość pudełka elementu. Po prostu możemy więc przejść przez wszystkie elementy i ustawić jego minimalną wysokość na rzeczywistą wysokość pudełka ich dzieci.

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

(Z pewnymi modyfikacjami możesz przeglądać dzieci i dowiedzieć się, który z nich jest najwyższy, i ustawić wzrost rodzica dla najwyższego dziecka. Postanowiłem jednak uczynić przykład prostszym. Możesz również dodać wypełnienie rodzica do wysokość, jeśli chcesz, lub możesz użyć odpowiedniej box-sizingwartości w CSS).

Zwróć jednak uwagę, że dodałem translatei transform-origindo twojego CSS, aby pozycjonowanie było bardziej elastyczne i dokładne.

transform: rotate(-90deg) translateX(-100%);
transform-origin: top left;
Bram Vanroy
źródło
Naprawdę? Pytanie CSS z odpowiedzią JQUERY i nazywasz je doskonałą odpowiedzią @MichaelLumbroso? Działa tak, ale nie jest potrzebny javascript ani cała biblioteka js, taka jak jQuery
Nebulosar
1
@Nebulosar Nice digging. W tamtym czasie (już dwa i pół roku temu!) Była to jedyna odpowiedź, która nie powodowała problemów z pozycjonowaniem ani nie działały dobrze pseudoelementy. Na szczęście poprawiła się obsługa niektórych właściwości przez przeglądarki. Zobacz moją edycję.
Bram Vanroy
2
To zasadniczo dwie zupełnie niezwiązane odpowiedzi w jednej. Postawienie wielu odpowiedzi na jedno pytanie jest całkowicie zgodne z zasadami; Myślę, że byłoby lepiej, gdyby Twoje nowe rozwiązanie dodane w 2017 roku było nową odpowiedzią, tak aby dwie całkowicie oddzielne odpowiedzi mogły być głosowane i komentowane oddzielnie.
Mark Amery,
@MarkAmery Masz rację. I edytowane pytanie i dodaje nową odpowiedź tutaj .
Bram Vanroy
Udało mi się użyć tej odpowiedzi i zdarzenia ładowania obrazu, aby uzyskać działające rozwiązanie dla obrazów przy użyciu: $ pic.on ('load', function () {$ pic.css ('min-height', $ pic [0]) .getBoundingClientRect (). wysokość);});
Miika L.,
-18

Wiem, że to stary post, ale znalazłem go, borykając się z dokładnie tym samym problemem. Rozwiązanie, które mi odpowiada, to raczej prymitywna metoda „low-tech” polegająca na umieszczeniu znacznika div, który obracam o 90 stopni,

<br>

Znając przybliżoną szerokość (która staje się wysokością po obróceniu) elementu div, mogę skompensować różnicę, dodając br's wokół tego elementu div, dzięki czemu zawartość powyżej i poniżej zostanie odpowiednio przesunięta.

vic_sk
źródło
10
Może to działa, ale nigdy nie powinieneś tego robić.
MMM,
Dziekuję za odpowiedź! Tak, w lepszy sposób mogę użyć właściwości margin-top elementu div, aby dostosować wysokość w pikselach, gdy obiekt jest obracany.
vic_sk