15

There seems to be a lot of questions regarding the same issue but can't seem to find a satisfactory answer ... I have:

1 container (flex) (green)
   2 columns (block) -> left: red, right: orange
      in the left column, I have two divs (green) who follow each other 'menu1''menu2'

These two menus are themselves wrapped in a div (black) so that when I rotate it, the two menus are vertical rather than horizontal (rotation by 90 deg)

The goal is to have the top wrapper/container (green) to take on the height of the vertical black wrapper, and the left column wrapper to be no larger than the 'width' of the rotated black wrapper.

What I get is illustrated in the following example:

https://jsfiddle.net/pg373473/

<div id='container' style='display: flex; border: 3px solid green; flex-direction=row'>
  <div id='leftbox' style='position: relative; display: block; border: 3px solid red'>
    <div id='textwrapper' style='transform-origin: bottom left; transform: translateY(-100%) rotate(90deg); border: 2px solid black;'>
      <div style='border: 3px solid green; display: inline-block'>menu 1</div>
      <div style='border: 3px solid green; display: inline-block'>menu 2</div>
    </div>
  </div>
  <div id='rightbox' style='position: relative; display: flex; flex: 1 1 auto; border: 3px solid orange';>
    xx
  </div>
</div>

enter image description here

Is this possible at all?

By default, the rotation seems to be applied after all the width/height for all divs have been calculated. I personally find this behavior to be against what anybody would expect, but maybe someone who knows the specs very well can explain why this is so? But in the end, I am more interested to know if there is a solution to what I try to achieve (thx).

EDIT

Rotate elements in CSS that affect their parents... is asking something about rotations but the explanation is not satisfactory and the problem slightly different since it's only about being sure that the div of the rotated child take into account the height of the rotated child. The questions asked in this post has 3 constraints:

  • you have 2 divs in a row that are rotated by 90 degrees (or more than 2)
  • the wrapper container needs to take on:
    • the width
    • and the height of children rotated elements

The aforementioned question is only asking about the height and does not solve the width problem. Additionally, the jsfiddle doesn't work either.

The only good part about this other question is the mentioned in the comments of the writing-mode but I haven't managed to make it work with this either. I tried this option and while I can fix the height issue, I can't make it work to fix the width problem...

<div id='container' style='display: flex; border: 3px solid green; flex-direction: row'>
  <div style='display: flex; flex-direction: column; border: 3px solid red; flex: 1 1 auto;'>
    <div style='flex: 0 0 auto; display: inline-block; flex-direction: row; writing-mode: vertical-rl; border: 3px solid black;'>
      <div style='flex: 0 0 auto; border: 1px solid green; display: inline-block'>menu 1</div>
      <div style='flex: 0 0 auto; border: 1px solid green; display: inline-block'>menu 2</div>
    </div>
  </div>
  <div id='rightbox' style='display: flex; flex: 1 1 auto; border: 3px solid orange';>
    Right Box
  </div>
</div>

enter image description here

https://jsfiddle.net/dyae4xru/

For clarity here is what I want:

enter image description here

ANSWER / EDIT 2

There is no solution to this problem at this point in time. CSS/HTML/Browsers doesn't support that out of the box. I fixed the problem by writing a small JS function that gives me the exact width and height of the div when horizontal and used the values to set the width of the div once rotated by 90 degrees (using writing-mode: vertical-rl).

user18490
  • 3,546
  • 4
  • 33
  • 52
  • Possible duplicate of [Rotated elements in CSS that affects their parent's height correctly](https://stackoverflow.com/questions/16301625/rotated-elements-in-css-that-affects-their-parents-height-correctly) – ncardeli Oct 12 '17 at 19:31
  • 1
    @Ncardeli: it is not, please see my edit above. A question can only be considered a duplicate if the old question has a working solution (that is not a hack), which is not the case of the one you pointed out (and that I looked at before). thx – user18490 Oct 14 '17 at 10:02
  • hacky $('parent').height($('selector').width()+border) – Josh Lin Oct 16 '17 at 02:58
  • Thanks Josh. Yes that's a hack and using jquery at that ... but yes indeed using JS can do the trick but this definitely a hack. – user18490 Oct 16 '17 at 06:47
  • Could you add an image of the layout you want to achieve! – T04435 Oct 16 '17 at 22:17
  • @T04435: done, see in the original question (bottom). thx – user18490 Oct 16 '17 at 22:28
  • _"I personally find this behavior to be against what anybody would expect, but maybe someone who knows the specs very well can explain why this is so?"_ - performance considerations and technical requirements ... ability to use hardware acceleration, to hand "heavy" stuff over to the GPU, reducing the necessity/ frequency of repaints and reflows, etc. pp. Just because the transformation you are applying in this specific instance is incredibly simply, doesn't mean that the whole implementation doesn't need to be able to handle much more complex/extreme cases. – CBroe Oct 16 '17 at 22:51
  • @CBroe. I understand your point but I am a C++ developer working with real-time apps using Vulkan API. I know that stuff well. I don't see what's the problem in computing the parent's width and height based on the children width and height. This is not a performance problem at all. Look at Android Studio and how complex the relationships between widgets are in order for a given layout to look good on any device. It's at least if not more complex than the current problem at hand. Sorry, point not taken. – user18490 Oct 17 '17 at 06:16
  • Don't think it's possible with only css check this answer [Overflow behavior after using CSS3 transform](https://stackoverflow.com/questions/21248111/overflow-behavior-after-using-css3-transform/21305283#21305283) – blecaf Oct 19 '17 at 13:29
  • @biecaf: I think you are right, I hope someone in charge of the CSS specs will read that ... and that they will address this point. – user18490 Oct 20 '17 at 08:19

5 Answers5

5

There may be hacky solutions, yet I'd say CSS transforms are not built for something like this.

If the property has a value different than none, a stacking context will be created. In that case the object will act as a containing block for position: fixed elements that it contains.

Source: MDN

(See this post for a scaling transform question.)


CSS Writing Mode

I suggest you use the newer CSS writing mode - see here for browser support:

The writing-mode CSS property defines whether lines of text are laid out horizontally or vertically and the direction in which blocks progress.

Use writing-mode: vertical-lr on the div - see demo below that works in Chrome:

#container {
  display: flex;
  border: 1px solid green;
  flex-direction: row;
}

#container>div:first-child {
  position: relative;
  display: block;
  border: 1px solid red;
  writing-mode: vertical-lr; /* ADDED */
  -webkit-writing-mode: vertical-lr;
}

#container>div>div {
  /*transform-origin: bottom left;
  transform: translateY(-100%) rotate(90deg);*/
  border: 2px solid black;
}

#container>div>div>div {
  border: 1px solid green;
  display: inline-block;
}

#rightbox {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  border: 1px solid orange;
}
<div id='container'>
  <div>
    <div>
      <div>menu 1</div>
      <div>menu 2</div>
    </div>
  </div>
  <div id='rightbox'>
    xx
  </div>
</div>

Issue in Firefox

Note that this doesn't work in Firefox, as flex items do not behave well for vertical writing mode - there are open issues:

The above issue for vertical mode in flexboxes was fixed in Firefox 60, and now you can see that the above demo works in both Chrome & Firefox.

kukkuz
  • 41,512
  • 6
  • 59
  • 95
  • 1
    Thx a lot, I already tried writing-mode as mentioned in my post and while I got it to work for the height that doesn't solve the width issue, as illustrated by your code snippet. It could be that there is no solution to this problem. I'd say as mentioned in the post as well, that CSS transform and writing-mode should behave that way. It should work out of the box and the behavior right now is not consistent with the way you can embed blocks into blocks in HTML. – user18490 Oct 16 '17 at 06:46
  • @user18490 updated answer to reflect current situation, thank you! – kukkuz Apr 03 '19 at 02:05
3

I doubt that this task can be solved without JavaScript because transformed elements are drawn on separate "layers" and their visual appearance doesn't affect DOM properties. However since your transformation is fixed - you can calculate resulted visual size of element and update size of parent element accordingly. Moreover you can use mutation observers to update size of your container in a case if its contents will be changed in runtime. This example displays correct container size and reacts on runtime mutations of menu items. Tested into Firefox and Chrome

    document.addEventListener('DOMContentLoaded', function () {
        const container = document.querySelector('#container .menu-container');
        const menu = document.querySelector('#container .menu-items');
        let items = [];

        const updateItems = () => {
            const nodes = document.querySelectorAll('#container .menu-item');
            for (let node of nodes) {
                if (items.indexOf(node) === -1) {
                    items.push(node);
                    new MutationObserver(updateSize).observe(node, {attributes: true, characterData: true, subtree: true});
                }
            }
            updateSize();
        }

        const updateSize = () => {
            container.style.width = menu.offsetHeight + 'px';
            container.style.height = menu.offsetWidth + 'px';
        }

        new MutationObserver(updateSize).observe(menu, {attributes: true, characterData: true});
        new MutationObserver(updateItems).observe(menu, {childList: true});
        updateItems();
        updateSize();
    });
#container {
    display: flex;
    border: 1px solid green;
    flex-direction: row;
}

.menu-container {
    position: relative;
    display: block;
    border: 1px solid red;
}

.menu-items {
    transform-origin: bottom left;
    transform: translateY(-100%) rotate(90deg);
    border: 2px solid black;
    display: flex;
    position: absolute;
}

.menu-item {
    border: 1px solid green;
    display: inline-block;
    white-space: nowrap;
}

#rightbox {
    position: relative;
    display: flex;
    flex: 1 1 auto;
    border: 1px solid orange;
}
<div id="container">
    <div class="menu-container">
        <div class="menu-items">
            <div class="menu-item">menu 1</div>
            <div class="menu-item">menu 2</div>
        </div>
    </div>
    <div id="rightbox">
        xx
    </div>
</div>
Flying
  • 4,422
  • 2
  • 17
  • 25
  • +1 for getting a dynamic working solution. I think this is the best answer despite having to mix JS and CSS. The problem of having the height and width swapped from the transform messed my solution up. – G.T.D. Oct 22 '17 at 14:11
1

Here is my approach to the solution:

I use flex on the container as well as the LEFT BOX, then user writing-mode vertical left to right and finally flex direction column so they stack.

*,
*:before,
*:after {
  /* So 100% means 100% */
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.container {
  display: flex;
  border: 2px solid tomato;
}

.box-left {
  flex: 0 2.5%; // change % value for the size you want it!
}

.box-right {
  flex: 1 auto;
  border: 2px solid green;
}

.box-left ul {
  display: flex;
  flex-direction: column;
  border: 2px solid blue;
}

.box-left ul li {
  list-style: none;
  writing-mode: vertical-lr;
  margin-bottom: 0.5em;
}
<div class="container">
  <div class="box-left">
    <ul>
      <li>menu01</li>
      <li>menu02</li>
    </ul>
  </div>
  <div class="box-right">
    box right
  </div>
</div>
T04435
  • 12,507
  • 5
  • 54
  • 54
  • I try it on Firefox(57.0b7) and Chrome(59.0.3071.115) in Linux. – T04435 Oct 17 '17 at 06:09
  • It's a nice effort but your force the width of the rotated divs. I can do this too. I am interested in a solution that works without having to hack the divs height and width. My point is, the correct behavior by default is that the parent width and height should "wrap" around the children's bounding box. This is simple and basic principle of UI building. So my question is: either this is possible and I don't know how or this is not possible at all and I want to point out with this post, that to my opinion this is not how the standard behavior should be. – user18490 Oct 17 '17 at 06:21
1

I modified your code and below is the out of CSS and HTML snippetenter image description here.

*,
*:before,
*:after {box-sizing: border-box;margin: 0;padding: 0;}

.container {display: flex;border: 2px solid green;}

.left-box {flex: 0 2.5%;}

.right-box {flex: 1 auto;border: 2px solid orange;}

.left-box {flex: 0 5.5%;border: 2px solid red;}

.menu {border: 1.5px solid green;}

.inner-left-box ul {display: flex;flex-direction: column;border: 2px solid black;}

.inner-left-box ul li {list-style: none;writing-mode: vertical-lr;}
<div class="container">
  <div class="left-box">
   <div class="inner-left-box">
    <ul>
      <li class="menu">menu 1</li>
      <li class="menu">menu 2</li>
    </ul>
    </div> 
  </div>
  <div class="right-box">Right Box</div>
</div>
Pratiyush Kumar Singh
  • 1,977
  • 3
  • 19
  • 39
  • Thx for the effort. Please see my comments above and below though. I look for a solution that is automatic and not one where you see the width or height of the divs in CSS. thx – user18490 Oct 20 '17 at 17:45
1

The answer is in the question's edited answer for the writing-mode solution actually. The only problem was the flex: 1 1 auto style on the red-border div, removing it solves the width problem.

HTML w/ Style Tag Variant

<div id='container' style='display: flex; border: 3px solid green; flex-direction: row'>
    <div style='display: flex; flex-direction: column; border: 3px solid red;'>
        <div style='flex: 0 0 auto; display: inline-block; flex-direction: row; writing-mode: vertical-rl; border: 3px solid black;'>
            <div style='flex: 0 0 auto; border: 1px solid green; display: inline-block'>
                menu 1
            </div>
            <div style='flex: 0 0 auto; border: 1px solid green; display: inline-block'>
                menu 2
            </div>
        </div>
    </div>
    <div id='rightbox' style='display: flex; flex: 1 1 auto; border: 3px solid orange';>
        Right Box
    </div>
</div>

CSS Classes Variant

#container
{
  display: flex;
  flex-direction: row;
  border: 3px solid green;
}

#leftbox
{
  display: flex;
  flex-direction: column;
  border: 3px solid red;
}

#inner-leftbox
{
  display: inline-block;
  flex-direction: row;
  flex: 0 0 auto;
  writing-mode: vertical-rl;
  border: 3px solid black;
}

#rightbox
{
  display: flex;
  flex: 1 1 auto;
  border: 3px solid orange;
}

.menu
{
  display: inline-block;
  flex: 0 0 auto;
  border: 1px solid green;
}
<div id="container">
    <div id="leftbox">
        <div id="inner-leftbox">
            <div class="menu">menu 1</div>
            <div class="menu">menu 2</div>
        </div>
    </div>
    <div id="rightbox">
        Right Box
    </div>
</div>
G.T.D.
  • 386
  • 1
  • 5
  • 21