Повернутые элементы в CSS, которые правильно влияют на высоту своего родителя


119

Скажем, у меня есть пара столбцов, в некоторых из которых я бы хотел повернуть значения:

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>

С помощью этого CSS:

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

В итоге это выглядит так:

Серия из четырех блочных элементов.  Текст третьего элемента поворачивается.

Можно ли написать какой-либо CSS, который заставит повернутый элемент влиять на высоту своего родительского элемента, чтобы текст не перекрывал другие элементы? Что-то вроде этого:

Текст третьего столбца теперь влияет на высоту его родительского блока, так что текст помещается внутри блока.


1
может быть полезным stackoverflow.com/questions/7565542/…
Пит

7
режим записи теперь доступен для большинства браузеров (раньше это была функция IE5). Сейчас я бы сделал jsfiddle.net/MTyFP/698, см .: developer.mozilla.org/en-US/docs/Web/CSS/writing-mode
G-

1
@ G-Cyr writing-modeэто доступно для большинства браузеров, но ужасно глючит. По поводу связанного с этим вопроса я прошел несколько итераций, пытаясь обойти специфичные для браузера ошибки, прежде чем сдаться; вы можете увидеть мою последовательность сбоев на stackoverflow.com/posts/47857248/revisions , где я начинаю с чего-то, что работает в Chrome, но не с Firefox или Edge, затем ломаю Chrome в процессе исправления двух других и в конечном итоге возвращаю свой ответьте и отметив его как Chrome-only. Будьте осторожны, если хотите пойти по этому пути. (Также обратите внимание, что это бесполезно с нетекстовыми элементами.)
Марк Эмери

1
afaik writing-mode работает только с текстом ...
Фредрик Йоханссон

Ответы:


51

Предполагая, что вы хотите повернуть на 90 градусов, это возможно даже для нетекстовых элементов, но, как и многие другие интересные вещи в CSS, это требует некоторой хитрости. Мое решение также технически вызывает неопределенное поведение в соответствии со спецификацией CSS 2 - поэтому, хотя я протестировал и подтвердил, что оно работает в Chrome, Firefox, Safari и Edge, я не могу обещать вам, что оно не сломается в будущем. выпуск браузера.

Короткий ответ

Учитывая такой HTML, где вы хотите повернуть .element-to-rotate...

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

... введите два элемента-оболочки вокруг элемента, который вы хотите повернуть:

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

... а затем используйте следующий CSS для вращения против часовой стрелки (или посмотрите закомментированный transformспособ изменить его на вращение по часовой стрелке):

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

Демонстрация фрагмента стека

Как это работает?

Замешательство перед заклинаниями, которые я использовал выше, разумно; там много всего происходит, и общая стратегия непростая и требует некоторых знаний в мелочах CSS для понимания. Давайте рассмотрим это шаг за шагом.

Суть проблемы, с которой мы сталкиваемся, заключается в том, что все преобразования, применяемые к элементу с использованием его transformсвойства CSS, происходят после того, как произошел макет. Другими словами, использование transformэлемента не влияет на размер или положение его родительского элемента или любых других элементов. Нет абсолютно никакого способа изменить этот факт transformработы. Таким образом, чтобы создать эффект вращения элемента и чтобы высота его родительского элемента соответствовала повороту, нам необходимо сделать следующее:

  1. Как-нибудь построить какой-нибудь другой элемент, высота которого равна ширине.element-to-rotate
  2. Напишите наше преобразование .element-to-rotateтак, чтобы оно было точно наложено на элемент из шага 1.

Элемент из шага 1 должен быть .rotation-wrapper-outer. Но как мы можем вызвать его высота будет равна .element-to-rotate«s ширина ?

Ключевым компонентом нашей стратегии здесь является padding: 50% 0включение .rotation-wrapper-inner. Этот эксплойт представляет собой эксцентричную деталь спецификацииpadding : процентные отступы, даже для padding-topи padding-bottom, всегда определяются как проценты от ширины контейнера элемента . Это позволяет нам выполнить следующий двухэтапный трюк:

  1. Мы ставим display: tableна .rotation-wrapper-outer. Это приводит к тому, что он имеет ширину , уменьшающуюся до размера , что означает, что его ширина будет установлена ​​на основе внутренней ширины его содержимого, то есть на основе внутренней ширины .element-to-rotate. (Что касается поддержки браузеров, мы могли бы добиться этого более четко width: max-content, но по состоянию на декабрь 2017 года max-contentон все еще не поддерживается в Edge .)
  2. Мы устанавливаем heightof .rotation-wrapper-innerравным 0, а затем устанавливаем его отступы 50% 0(то есть 50% сверху и 50% снизу). Это приводит к тому, что он занимает вертикальное пространство, равное 100% ширины его родительского элемента, который, с помощью уловки на шаге 1, равен ширине .element-to-rotate.

Далее все, что остается, - это выполнить фактическое вращение и позиционирование дочернего элемента. Естественно, transform: rotate(-90deg)делает вращение; мы используем, transform-origin: top left;чтобы заставить вращение происходить вокруг верхнего левого угла повернутого элемента, что облегчает последующий перевод, поскольку он оставляет повернутый элемент прямо над тем местом, где он в противном случае был бы нарисован. Затем мы можем использовать, translate(-100%)чтобы перетащить элемент вниз на расстояние, равное его ширине до поворота.

Это все еще не совсем правильно позиционирует, потому что нам все еще нужно смещение для 50% верхнего отступа .rotation-wrapper-outer. Мы достигаем этого, гарантируя, что он .element-to-rotateимеет display: block(чтобы поля правильно работали на нем), а затем применяя -50% margin-top- обратите внимание, что процентные поля также определяются относительно ширины родительского элемента.

И это все!

Почему это не полностью соответствует спецификации?

Из-за следующего примечания из определения процентных отступов и полей в спецификации (выделено жирным шрифтом):

Процент рассчитывается относительно ширины содержащего блок сгенерированного блока , даже для 'padding-top' и 'padding-bottom' . Если ширина содержащего блока зависит от этого элемента, то результирующий макет не определен в CSS 2.1.

Поскольку весь трюк сводился к тому, чтобы заполнение внутреннего элемента оболочки было относительно ширины его контейнера, который, в свою очередь, зависел от ширины его дочернего элемента, мы выполняем это условие и вызываем поведение undefined. Однако в настоящее время он работает во всех 4 основных браузерах - в отличие от некоторых, казалось бы, совместимых со спецификациями настроек подхода, которые я пробовал, таких как изменение .rotation-wrapper-innerна родство, .element-to-rotateа не на родительский.


1
На данный момент я принял это как ответ. Эти writing-modeрешения будут наилучший подход , если IE 11 не нуждается в поддержке.
Крис

1
Примечательная загвоздка: не работает для других вращений, таких как 45 градусов.
Э. Виллигер

хотя и принят, это не актуальный правильный ответ. См. Ниже ответы с использованием режима письма.
GilShalit

@ mark-amery, мне пришлось настроить CSS для моего кода: imgur.com/a/ZgafkgE 1-е изображение с вашим CSS, 2-е с настройками Я удалил transformOrigin: 'top left' и изменил transform: 'translate (-100% ) ', чтобы преобразовать:' 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% ', преобразовать : 'rotate (-90deg) translate (20%)', maxWidth: 300, maxHeight: 400,}} /> </div> </div> <div> {title} </div> </div>
Дэн Горький

44

Как справедливо указывает комментарий G-Cyr , текущая поддержка writing-modeболее чем приличная . В сочетании с простым поворотом вы получите именно тот результат, который хотите. См. Пример ниже.

.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
Рад, что прокрутил вниз - это спасло меня от мира боли, связанного с вращениями и переводами.
Джеймс Маклафлин

3
Чтобы получить ротацию, запрошенную в вопросе, используйтеwriting-mode: vertical-lr; transform: rotate(180deg);
Джеймс Маклафлин

41

К сожалению (?) Именно так это должно работать, даже если вы поворачиваете свой элемент, он все еще имеет определенную ширину и высоту, которые не меняются после вращения. Вы визуально меняете его, но нет невидимой обертки, которая меняет свой размер при повороте вещей.

Представьте, что вы поворачиваете его менее чем на 90 ° (например transform: rotate(45deg)): вам нужно будет ввести такой невидимый прямоугольник, который теперь имеет неоднозначные размеры, основанные на исходных размерах объекта, который вы вращаете, и фактическом значении поворота.

Повернутый объект

Внезапно у вас появляется не только widthи heightобъекта, который вы повернули, но также widthиheight «невидимая рамка» вокруг него. Представьте, что вы запрашиваете внешнюю ширину этого объекта - что он вернет? Ширина объекта или наш новый бокс? Как бы нам провести различие между ними?

Следовательно, не существует CSS, который можно было бы написать, чтобы исправить это поведение (или, лучше сказать, «автоматизировать» его). Конечно, вы можете вручную увеличить размер родительского контейнера или написать код JavaScript для этого.

(Для ясности, вы можете попробовать использовать, element.getBoundingClientRectчтобы получить прямоугольник, упомянутый ранее).

Как описано в спецификации :

В пространстве имен HTML свойство преобразования не влияет на поток содержимого, окружающего преобразованный элемент.

Это означает, что никакие изменения не будут внесены в контент, окружающий объект, который вы трансформируете, если вы не сделаете их вручную.

Единственное, что принимается во внимание при трансформации объекта, это область переполнения:

(...) размер области переполнения учитывает преобразованные элементы. Это поведение похоже на то, что происходит, когда элементы смещаются посредством относительного позиционирования .

См. Этот jsfiddle, чтобы узнать больше.

На самом деле неплохо сравнить эту ситуацию со смещением объекта, используя: position: relative- окружающее содержимое не изменяется, даже если вы перемещаете свой объект ( пример ).


Если вы хотите решить эту проблему с помощью JavaScript, взгляните на этот вопрос.


8
Я бы сказал, что в обычной структуре пользовательского интерфейса ширина и высота ваших родителей должны быть обернуты вокруг ширины и высоты ограничивающего прямоугольника ребенка, на который, как вы упомянули, влияют вращения ребенка. Ширина и высота дочерних элементов - это их ширина и высота до вращения, а ширина и высота родителей определяется bbox дочерних элементов. Это очень просто, и я бы сказал, что CSS / HTML должны работать именно так ... Если бы это было, никто бы не задавал вопросов по этой теме, которые можно исправить только неудовлетворительным образом с помощью хаков.
user18490

25

Используйте проценты для paddingи псевдоэлемент для продвижения содержимого. В JSFiddle я оставил псевдоэлемент красным, чтобы показать его, и вам придется компенсировать сдвиг текста, но я думаю, что это правильный путь.

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

Описание этого решения можно найти здесь: http://kizu.ru/en/fun/rotated-text/


3
Проблема с этим решением заключается в том, что элемент сохраняет свою исходную ширину, поэтому в (например) таблице, если бы это была самая широкая ячейка, он бы вытолкнул этот столбец широко с ненужными пробелами, которые были бы заняты, если бы текст был не был повернут. Это раздражает.
Jez

@litek, не могли бы вы взглянуть на jsfiddle.net/MTyFP/7 ? У меня почти такая же проблема с изображением. Вопрос: stackoverflow.com/questions/31072959/…
Awlad Liton

11

Пожалуйста, посмотрите обновленную версию в моем другом ответе . Этот ответ больше не действителен и просто больше не работает. Я оставлю это здесь по историческим причинам.


Этот вопрос довольно старый, но с текущей поддержкой .getBoundingClientRect()объекта и его widthи heightценность, в сочетании с возможностью использовать этот аккуратный метод вместе с transform, я думаю , что мое решение следует упомянуть также.

Посмотрите это в действии здесь . (Проверено в Chrome 42, FF 33 и 37.)

getBoundingClientRectвычисляет фактическую ширину рамки и высоту элемента. Итак, очень просто, мы можем перебрать все элементы и установить его min-height на фактическую высоту блока их дочерних элементов.

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

(С некоторыми изменениями вы можете перебрать дочерние элементы и выяснить, какой из них самый высокий, и установить высоту родительского элемента на самый высокий дочерний элемент. Однако я решил упростить пример. Вы также можете добавить отступ родительского элемента к height, если хотите, или можете использовать соответствующее box-sizingзначение в CSS.)

Однако обратите внимание, что я добавил translateи transform-originв ваш CSS, чтобы сделать позиционирование более гибким и точным.

transform: rotate(-90deg) translateX(-100%);
transform-origin: top left;

В самом деле? Вопрос css с ответом JQUERY, и вы называете его идеальным ответом @MichaelLumbroso? Это работает да, но нет необходимости в javascript или целой библиотеке js, такой как jQuery
Nebulosar

1
@Nebulosar Приятно копать. В то время (уже два с половиной года назад!) Это был единственный ответ, который не вызывал проблем с позиционированием или неправильной работой псевдоэлементов. К счастью, поддержка браузером некоторых свойств улучшилась. Пожалуйста, посмотрите мою правку.
Брэм Ванрой

2
По сути, это два совершенно не связанных между собой ответа в одном. Публикация нескольких ответов на один вопрос полностью соответствует правилам; Я думаю, было бы предпочтительнее, если бы ваше новое решение, добавленное в 2017 году, было новым ответом, чтобы можно было проголосовать и прокомментировать два совершенно разных ответа отдельно.
Марк Эмери

@MarkAmery Вы правы. Я отредактировал вопрос и добавил сюда новый ответ .
Брэм Ванрой

Я смог использовать этот ответ и событие загрузки изображения, чтобы получить рабочее решение для изображений, используя: $ pic.on ('load', function () {$ pic.css ('min-height', $ pic [0] .getBoundingClientRect (). height);});
Miika L.

-18

Я знаю, что это старый пост, но я нашел его, борясь с той же проблемой. Решение, которое работает для меня, - это довольно грубый "низкотехнологичный" метод, просто окружив div, который я поворачиваю на 90 градусов, большим количеством

<br>

Зная приблизительную ширину (которая становится высотой после поворота) div, я могу компенсировать разницу, добавив br вокруг этого div, чтобы содержимое сверху и снизу перемещалось соответственно.


10
Возможно, это сработает, но вам никогда не следует этого делать.
MMM

Спасибо за ваш отзыв! Да, я лучше использую свойство div margin-top для настройки высоты в пикселях при повороте объекта.
vic_sk
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.