Gedrehte Elemente in CSS, die die Höhe ihrer Eltern korrekt beeinflussen

119

Angenommen, ich habe einige Spalten, von denen einige die Werte von:

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>

Mit diesem CSS:

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

Am Ende sieht es so aus:

Eine Reihe von vier Elementen auf Blockebene.  Der Text des dritten Elements wird gedreht.

Ist es möglich, CSS zu schreiben, das bewirkt, dass das gedrehte Element die Höhe des übergeordneten Elements beeinflusst, sodass der Text die anderen Elemente nicht überlappt? Etwas wie das:

Der Text der dritten Spalte wirkt sich nun auf die Höhe des übergeordneten Felds aus, sodass der Text in das Feld passt.

Chris
quelle
1
könnte hilfreich sein stackoverflow.com/questions/7565542/…
Pete
7
Der Schreibmodus ist jetzt für die meisten Browser verfügbar (früher eine IE5-Funktion). Ich würde dies heute tun. jsfiddle.net/MTyFP/698 siehe: developer.mozilla.org/en-US/docs/Web/CSS/writing-mode
G-Cyrillus
1
@ G-Cyr writing-modeist verfügbar für die meisten Browser, aber schrecklich fehlerhaft. Bei einer verwandten Frage habe ich mehrere Iterationen durchlaufen, in denen ich versucht habe, browserspezifische Fehler zu umgehen, bevor ich aufgegeben habe. Sie können meine Abfolge von Fehlern unter stackoverflow.com/posts/47857248/revisions sehen , wo ich mit etwas beginne , das in Chrome funktioniert, aber nicht in Firefox oder Edge, und dann Chrome bei der Behebung der beiden anderen Probleme kaputt mache und am Ende meine zurücksetze Beantworten und kennzeichnen Sie es als "Nur Chrome". Seien Sie vorsichtig, wenn Sie diesen Weg gehen möchten. (Beachten Sie auch, dass es für Nicht-Text-Elemente nicht von Nutzen ist.)
Mark Amery
1
afaik Schreibmodus funktioniert nur auf Text ...
Fredrik Johansson

Antworten:

51

Angenommen, Sie möchten um 90 Grad drehen, ist dies auch für Nicht-Text-Elemente möglich - aber wie viele interessante Dinge in CSS erfordert es ein wenig List. Meine Lösung ruft auch technisch undefiniertes Verhalten gemäß der CSS 2-Spezifikation auf. Während ich getestet und bestätigt habe, dass es in Chrome, Firefox, Safari und Edge funktioniert, kann ich Ihnen nicht versprechen, dass es in Zukunft nicht mehr funktioniert Browser-Release.

Kurze Antwort

Bei HTML wie diesem, wo Sie drehen möchten .element-to-rotate...

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

... führen Sie zwei Wrapper-Elemente um das Element ein, das Sie drehen möchten:

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

... und verwenden Sie dann das folgende CSS, um gegen den Uhrzeigersinn zu drehen (oder lesen Sie im Kommentar aus transform, wie Sie es in eine Drehung im Uhrzeigersinn ändern können):

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

Stack-Snippet-Demo

Wie funktioniert das?

Verwirrung angesichts der Beschwörungsformeln, die ich oben verwendet habe, ist vernünftig; Es ist viel los, und die Gesamtstrategie ist nicht einfach und erfordert einige Kenntnisse der CSS-Trivia, um sie zu verstehen. Lassen Sie uns Schritt für Schritt durchgehen.

Der Kern des Gesichts Problem , das wir ist , dass Transformationen auf ein Element angewendet unter Verwendung seiner transformCSS - Eigenschaft alle geschehen , nachdem das Layout stattgefunden hat. Mit anderen Worten, die Verwendung transformeines Elements wirkt sich überhaupt nicht auf die Größe oder Position seines übergeordneten Elements oder anderer Elemente aus. Es gibt absolut keine Möglichkeit, diese Tatsache zu ändern, wie es transformfunktioniert. Um den Effekt des Drehens eines Elements zu erzielen und die Höhe seines Elternteils der Drehung zu entsprechen, müssen wir die folgenden Dinge tun:

  1. Konstruieren Sie irgendwie ein anderes Element, dessen Höhe der Breite des entspricht.element-to-rotate
  2. Schreiben Sie unsere Transformation .element-to-rotateso auf, dass sie genau aus Schritt 1 auf das Element gelegt wird.

Das Element aus Schritt 1 muss sein .rotation-wrapper-outer. Aber wie können wir veranlassen , seine Höhe gleich zu sein .element-to-rotate‚s Breite ?

Die Schlüsselkomponente unserer Strategie ist hier die padding: 50% 0auf .rotation-wrapper-inner. Dieser Exploit ist ein exzentrisches Detail der Spezifikation fürpadding : Diese prozentualen Auffüllungen, auch für padding-topund padding-bottom, werden immer als Prozentsätze der Breite des Containers des Elements definiert . Dies ermöglicht es uns, den folgenden zweistufigen Trick auszuführen:

  1. Wir setzen display: tableauf .rotation-wrapper-outer. Dies führt dazu, dass es eine Schrumpfbreite hat , was bedeutet, dass seine Breite basierend auf der intrinsischen Breite seines Inhalts festgelegt wird - dh basierend auf der intrinsischen Breite von .element-to-rotate. (Bei der Unterstützung von Browsern könnten wir dies sauberer erreichen width: max-content, aber ab Dezember 2017 max-contentwird dies in Edge immer noch nicht unterstützt .)
  2. Wir setzen das heightvon .rotation-wrapper-innerauf 0 und dann das Auffüllen auf 50% 0( dh 50% oben und 50% unten). Dies führt dazu, dass es einen vertikalen Raum einnimmt, der 100% der Breite seines übergeordneten Elements entspricht - was durch den Trick in Schritt 1 gleich der Breite von ist .element-to-rotate.

Als nächstes müssen nur noch die eigentliche Drehung und Positionierung des untergeordneten Elements durchgeführt werden. Natürlich transform: rotate(-90deg)macht die Rotation; Wir verwenden transform-origin: top left;, um die Drehung um die obere linke Ecke des gedrehten Elements zu veranlassen, wodurch die nachfolgende Übersetzung leichter zu begründen ist, da das gedrehte Element direkt über der Stelle verbleibt, an der es sonst gezeichnet worden wäre. Wir können dann translate(-100%)das Element um einen Abstand nach unten ziehen, der seiner Vorrotationsbreite entspricht.

Das bringt die Positionierung immer noch nicht ganz richtig, da wir immer noch die 50% ige obere Polsterung ausgleichen müssen .rotation-wrapper-outer. Dies erreichen wir, indem wir sicherstellen, dass dies der Fall .element-to-rotateist display: block(damit die Ränder ordnungsgemäß funktionieren) und dann einen Wert von -50% anwenden. margin-topBeachten Sie, dass die prozentualen Ränder auch relativ zur Breite des übergeordneten Elements definiert sind.

Und das ist es!

Warum ist dies nicht vollständig spezifikationskonform?

Aufgrund des folgenden Hinweises aus der Definition der prozentualen Auffüllungen und Ränder in der Spezifikation (fettgedruckte Mine):

Der Prozentsatz wird in Bezug auf die Breite des Blocks der generierten Box berechnet , selbst für "Auffüllen oben" und "Auffüllen unten" . Wenn die Breite des enthaltenen Blocks von diesem Element abhängt, ist das resultierende Layout in CSS 2.1 nicht definiert.

Da sich der gesamte Trick darum drehte, das Auffüllen des inneren Wrapper-Elements relativ zur Breite seines Containers zu machen, was wiederum von der Breite seines untergeordneten Elements abhängt, treffen wir diese Bedingung und rufen undefiniertes Verhalten auf. Derzeit funktioniert es jedoch in allen vier gängigen Browsern - im Gegensatz zu einigen scheinbar spezifikationskonformen Änderungen .rotation-wrapper-inner, die ich versucht habe, wie zum Beispiel ein Geschwister .element-to-rotateanstelle eines Elternteils zu werden.

Mark Amery
quelle
1
Ich akzeptierte dies als Antwort für den Moment. Die writing-modeLösungen sind der beste Ansatz, wenn IE 11 nicht unterstützt werden muss.
Chris
1
Bemerkenswerter Fang: Funktioniert nicht für andere Umdrehungen wie 45 Grad.
E. Villiger
Obwohl akzeptiert, ist dies nicht die aktuell richtige Antwort. Antworten im Schreibmodus finden Sie weiter unten.
GilShalit
@ mark-amery, ich musste das CSS für meinen Code anpassen: imgur.com/a/ZgafkgE 1. Bild mit Ihrem CSS, 2. mit Anpassungen Ich habe transformOrigin: 'oben links' entfernt und transform geändert: 'translate (-100% ) ', um zu transformieren:' 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% ', transformieren : 'drehen (-90 Grad) übersetzen (20%)', maxWidth: 300, maxHeight: 400,}} /> </ div> </ div> <div> {title} </ div> </ div>
Dan Bitter
44

Wie der Kommentar von G-Cyr zu Recht hervorhebt, ist die derzeitige Unterstützung für writing-modemehr als anständig . In Kombination mit einer einfachen Drehung erhalten Sie genau das gewünschte Ergebnis. Siehe das folgende Beispiel.

.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
quelle
1
Ich bin froh, dass ich nach unten gescrollt habe - das hat mich mit Rotationen und Übersetzungen vor einer Welt voller Schmerzen bewahrt.
James McLaughlin
3
Verwenden Siewriting-mode: vertical-lr; transform: rotate(180deg);
James McLaughlin
40

Leider (?) Ist es so, dass es funktioniert, obwohl Sie Ihr Element drehen, es hat immer noch eine bestimmte Breite und Höhe, die sich nach dem Drehen nicht ändert. Sie ändern es visuell, aber es gibt keine unsichtbare Verpackungsbox, die ihre Größe ändert, wenn Sie Dinge drehen.

Stellen Sie sich vor, Sie drehen es um weniger als 90 ° (z. B. transform: rotate(45deg)): Sie müssten eine solche unsichtbare Box einführen, die jetzt mehrdeutige Abmessungen aufweist, die auf den ursprünglichen Abmessungen des zu drehenden Objekts und dem tatsächlichen Drehwert basieren.

Gedrehtes Objekt

Plötzlich haben Sie nicht nur das widthund heightdes Objekts, das Sie gedreht haben, sondern auch das widthund heightder "unsichtbaren Box" um sich herum. Stellen Sie sich vor, Sie fordern die äußere Breite dieses Objekts an - was würde es zurückgeben? Die Breite des Objekts oder unsere neue Box? Wie würden wir zwischen beiden unterscheiden?

Daher gibt es kein CSS, das Sie schreiben können, um dieses Verhalten zu beheben (oder sollte ich sagen, "automatisieren"). Natürlich können Sie den übergeordneten Container von Hand vergrößern oder JavaScript schreiben, um dies zu handhaben.

( element.getBoundingClientRectUm ganz klar zu sein, können Sie versuchen , das zuvor erwähnte Rechteck zu erhalten).

Wie in der Spezifikation beschrieben :

Im HTML-Namespace wirkt sich die Transformationseigenschaft nicht auf den Fluss des Inhalts aus, der das transformierte Element umgibt.

Dies bedeutet, dass keine Änderungen am Inhalt des zu transformierenden Objekts vorgenommen werden, es sei denn, Sie führen diese manuell aus.

Das einzige, was bei der Transformation Ihres Objekts berücksichtigt wird, ist der Überlaufbereich:

(...) Die Ausdehnung des Überlaufbereichs berücksichtigt transformierte Elemente. Dieses Verhalten ähnelt dem, was passiert, wenn Elemente über die relative Positionierung versetzt werden .

Sehen Sie diese jsfiddle, um mehr zu erfahren.

Es ist eigentlich ganz gut, diese Situation mit einem Objektversatz zu vergleichen, indem Sie: position: relative- den umgebenden Inhalt nicht ändern, obwohl Sie Ihr Objekt bewegen ( Beispiel ).


Wenn Sie dies mit JavaScript behandeln möchten, werfen Sie einen Blick auf diese Frage.

MMM
quelle
8
Ich würde sagen, dass in einem normalen UI-Framework die Breite und Höhe Ihrer Eltern um die Breite und Höhe des Begrenzungsrahmens des Kindes gewickelt werden sollte, der, wie Sie erwähnt haben, von den Rotationen des Kindes betroffen ist. Die Breite und Höhe der Kinder ist ihre Breite und Höhe vor der Drehung, und die Breite und Höhe der Eltern wird durch die bbox der Kinder definiert. Das ist sehr einfach und ich würde sagen, CSS / HTML sollte so funktionieren ... Wenn es so wäre, würde niemand Fragen zu diesem Thema stellen, die nur auf unbefriedigende Weise mit Hacks behoben werden können.
user18490
25

Verwenden Sie Prozentsätze für paddingund ein Pseudoelement, um den Inhalt zu verschieben. In der JSFiddle habe ich das Pseudoelement rot gelassen, um es anzuzeigen, und Sie müssen die Verschiebung des Textes kompensieren, aber ich denke, das ist der richtige Weg.

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

Eine Beschreibung dieser Lösung finden Sie hier: http://kizu.ru/en/fun/rotated-text/

Litek
quelle
3
Das Problem bei dieser Lösung besteht darin, dass das Element seine ursprüngliche Breite beibehält. Wenn es beispielsweise in einer Tabelle die breiteste Zelle wäre, würde es diese Spalte mit unnötigen Leerzeichen, die bei Verwendung des Textes belegt worden wären , weit herausschieben nicht gedreht worden. Das nervt.
Jez
@litek, Könnten Sie bitte einen Blick auf jsfiddle.net/MTyFP/7 werfen ? Ich habe fast das gleiche Problem mit dem Bild. Frage: stackoverflow.com/questions/31072959/…
Awlad Liton
11

Bitte beachten Sie die aktualisierte Version in meiner anderen Antwort . Diese Antwort ist nicht mehr gültig und funktioniert einfach nicht mehr. Ich werde es aus historischen Gründen hier lassen.


Diese Frage ist ziemlich alt, aber mit der aktuellen Unterstützung für das .getBoundingClientRect()Objekt und seine widthund heightWerte in Kombination mit der Fähigkeit, diese saubere Methode zusammen mit zu verwenden transform, denke ich, dass meine Lösung auch erwähnt werden sollte.

Sehen sie in Aktion hier . (Getestet in Chrome 42, FF 33 und 37.)

getBoundingClientRectberechnet die tatsächliche Kastenbreite und -höhe des Elements. Ganz einfach können wir also alle Elemente durchlaufen und ihre Mindesthöhe auf die tatsächliche Kastenhöhe ihrer Kinder einstellen.

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

(Mit einigen Änderungen können Sie die Kinder durchlaufen und herausfinden, welches das größte ist, und die Größe des Elternteils auf das größte Kind einstellen. Ich habe mich jedoch entschlossen, das Beispiel einfacher zu gestalten. Sie können auch das Polster des Elternteils zum hinzufügen Höhe, wenn Sie möchten, oder Sie können eine relevante verwenden box-sizing Wert in CSS verwenden.)

Beachten Sie jedoch, dass ich Ihrem CSS ein translateund hinzugefügt habe transform-origin, um die Positionierung flexibler und genauer zu gestalten.

transform: rotate(-90deg) translateX(-100%);
transform-origin: top left;
Bram Vanroy
quelle
"Ja wirklich?" Eine CSS-Frage mit einer JQUERY-Antwort und Sie nennen es eine perfekte Antwort @MichaelLumbroso? Es funktioniert ja, aber es gibt keine Notwendigkeit für Javascript oder eine ganze js-Bibliothek wie jQuery
Nebulosar
1
@ Nebulosar Schön graben. Zu der Zeit (bereits vor zweieinhalb Jahren!) War dies die einzige Antwort, die keine Probleme mit der Positionierung oder Pseudoelementen verursachte, die nicht gut funktionierten. Glücklicherweise hat sich die Browserunterstützung für einige Eigenschaften verbessert. Bitte sehen Sie meine Bearbeitung.
Bram Vanroy
2
Dies sind im Wesentlichen zwei völlig unabhängige Antworten in einer. Es gehört perfekt zu den Regeln, mehrere Antworten auf eine Frage zu veröffentlichen. Ich denke, es wäre besser gewesen, wenn Ihre 2017 hinzugefügte neue Lösung eine neue Antwort gewesen wäre, damit die beiden völlig getrennten Antworten getrennt abgestimmt und kommentiert werden könnten.
Mark Amery
@ MarkAmery Du hast recht. Ich bearbeitet die Frage und fügte eine neue Antwort hier .
Bram Vanroy
Ich konnte diese Antwort und das Ereignis zum Laden von Bildern verwenden, um eine funktionierende Lösung für Bilder zu erhalten: $ pic.on ('load', function () {$ pic.css ('min-height', $ pic [0] .getBoundingClientRect (). height);});
Miika L.
-18

Ich weiß, dass es ein alter Beitrag ist, aber ich habe ihn gefunden, als ich mit genau dem gleichen Problem zu kämpfen hatte. Die Lösung, die für mich funktioniert, ist eine ziemlich grobe "Low-Tech" -Methode, bei der einfach die Div, die ich um 90 Grad drehe, mit viel umgeben wird

<br>

Wenn ich die ungefähre Breite (die nach der Drehung zur Höhe wird) von div kenne, kann ich den Unterschied ausgleichen, indem ich brs um dieses div hinzufüge, damit der Inhalt oben und unten entsprechend verschoben wird.

vic_sk
quelle
10
Vielleicht funktioniert das, aber Sie sollten das niemals tun.
MMM
Vielen Dank für Ihr Feedback! Ja, eine bessere Möglichkeit, die margin-top-Eigenschaft von div zu verwenden, um die Höhe in Pixel anzupassen, wenn das Objekt gedreht wird.
vic_sk