Golo Roden
Golo Roden

Reputation: 150654

Div as square in flexbox

I have a CSS problem that I don't know how to solve. The situation is as follows. I have a <div> which is a flex-item, and which stretches vertically so that it fills the available height of its flex-container. Depending on the size of the browser window this <div> is a rectangle, sometimes taller than wide, sometimes wider than tall.

Now I want to position another <div> inside of this rectangle, which shall be a square. It should be as large as possible with respect to the surrounding rectangle, but it must always be visible entirely.

The typical approach to create a square is to set the padding-bottom to a percent value (which is the same percent value as for width). This actually creates a square, but since in this approach the height always follows the width, it stretches the out rectangle if it's wider than tall.

How can I solve this issue, so that the square is always contained within the boundaries of the size of the rectangle that was calculated by flexbox, ideally without using JavaScript?

Update: Meanwhile, I solved it using JavaScript, but I'd still be interested in a pure CSS solution, if there is any.

Upvotes: 8

Views: 6435

Answers (2)

cjl750
cjl750

Reputation: 4629

One approach is to use viewport units and a media query.

Given:

  • A flex container of arbitrary height, set in vh units, and
  • A flex child of arbitrary width, set in vw units, which fills the height of its container

We can set our inner square to 100% width and then:

  • Give it a max-width that is equal to the same number used for the height of the container, but set in vmin units
  • Give it a max-height that is equal to the same number used for the width of the flex child (the square's immediate parent), but set in vmin units

That gets us pretty close, but we're left with a slightly rectangular inner square when our window gets to portrait orientation (i.e., when it's taller than it is wide).

So the last step we can use is to add a media query that kicks in at the same width at which the square's height becomes equal to its width, and in that query change the max-height to match the max-width, so our square isn't too tall.

The trick is figuring out how exactly to set that media query. In our example below, our inner square will cease being square when the window gets taller than it is wide. So we want to do something like

@media all and (max-width: calc(100vh - 1px))

But we can't do calc expressions in our media query, and viewport units in media queries also prove to be problematic. So probably our best bet is to use JavaScript to detect the window height and then insert a <style> element into our document with the appropriate media query.

The snippet below does just that, though it could be improved by updating the media query on window resize (or, more specifically, only if the window height changes).

function createMediaQuery() {
  var containerHeight = window.innerHeight - 1;
  var mediaQuery = '@media all and (max-width: ' + containerHeight + 'px) { ' +
    '#square { max-height: 70vmin } }'

  var head = document.head || document.getElementsByTagName('head')[0],
    style = document.createElement('style');

  style.type = 'text/css';
  if (style.styleSheet) {
    style.styleSheet.cssText = mediaQuery;
  } else {
    style.appendChild(document.createTextNode(mediaQuery));
  }
  head.appendChild(style);
}
createMediaQuery();
html, body {
  height: 100%;
  width: 100%;
  margin: 0;
}
.container {
  display: flex;
  height: 70vh;
  margin: 15vh auto;
}
.flex-item {
  background-color: midnightblue;
  width: 80vw;
  margin: 0 auto;
  display: flex;
}
.square {
  background-color: gainsboro;
  width: 100%;
  max-width: 70vmin;
  max-height: 80vmin;
  margin: 0 auto;
}
<div class="container">
  <div class="flex-item">
    <div class="square"></div>
  </div>
</div>

Upvotes: 0

Mike Trinh
Mike Trinh

Reputation: 1303

Not sure if this is what u you are looking for...

.flex {
  display: flex;
}
.item {
  width: 50%;
}

.square {
  width: 100%;
  background: red;
}

.square:after {
  content: "";
  display: block;
  padding-bottom: 100%;
}
<div class="flex">
  <div class="item">
    <div class="square">
      
    </div>
  </div>
  <div class="item">
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
  </div>
</div>

Upvotes: 1

Related Questions