CSS Aspect Ratio - set width of child element based on dynamic parent height
Asked Answered
T

1

16

To begin, let me explain what I know exists and have tried variations of:

What I'm interested in is pure CSS solutions, with whatever hacky, new-fangled, experimental properties you can think of, that achieves the following results:

width based on height

The height of the card is based on content (in this case a title and a paragraph). We'll call that h. Another element within the card should be that same h height (based on the content of a sibling), and whatever that h amount is, it should also be the width of that element (to create a square).

Edit (2019-28-02): I apologize for not being clear on this next point but, in order to get the text to wrap, the area where the text sits should have a maximum width. Marc Bellêtre's solution is admittedly pretty close as of today.

This question is very similar to an older question which did not receive any responses. The difference is that the parent, in this case, is not receiving any explicit height values. The height of the parent card is purely determined by the text inside of it. I can imagine that if that question has not received any responses/solutions that this may fare no different, especially since this is slightly more difficult. However, new technologies have come around since then (ie.: flexbox/grid), that might be helpful.

Please, no JavaScript solutions. I'm very aware of how to achieve this using it.

Sometimes when I load this code, the box isn't even square. The big problem is it doesn't respect the height: 100% at all but then it will be square after fiddling with resizing the browser or content. Updating the ratio makes the box grow unexpectedly large.

.card {
  background: #ddd;
  display: inline-flex;
}

.aspect-ratio {
  border: 1px solid;
  padding: 1em;
  aspect-ratio: 1 / 1;
  background: #ccc;
}

.content {
  padding: 1em;
  flex: 1 1 200px;
}

h3,
p {
  margin: 0;
}
<div class="card">
  <div class="aspect-ratio">(h)</div>
  <div class="content">
    <h3>Title</h3>
    <p contenteditable>The more I type, the taller this box gets (h)</p>
  </div>
</div>
Thury answered 28/3, 2018 at 4:2 Comment(7)
The darker grey box is not necessarily an image, I presume?Elmiraelmo
That's correct, but it could be if that makes things easier at all.Kulsrud
I'm not entirely sure, as I try to avoid aspect ratio situations wherever possible (until a property is eventually implemented). Just asking for clarification in case someone else has a brilliant (or not) hack.Elmiraelmo
If the text is too long then the square image will take up the parent div and the text will flow over to the right. Do you need this behavior?? I Find it risky to allow the image to squeeze down the text column. If you had many such cards stacked in a column, then it wouldn't look neat.Ovi
Not concerned with the behavior of the text in relation to the growth of the square. However, it is fine to assume that the width of the box of text can be fixed.Kulsrud
I didn't find a solution for the dynamic growing - but I think the starting point is to create a container with a fixed aspect ratio. That can be accomplished with `` width: 100%; padding-top: 100%; `` for a square. In css Tricks are some good examples: css-tricks.com/aspect-ratio-boxesEvolve
I linked to that exact article about aspect ratio boxes at the top of the question.Kulsrud
H
1

You can achieve this using an image with a 1:1 aspect ratio and the flex property.

section {
  display: flex;
}

.square {
  display: grid;
  background-color: lightgrey;
}

img {
  height: 100%;
  width: auto;
  max-width: 200px;
  visibility: hidden;
}

.text {
  padding-left: 1rem;
}
<section>
  <div class="square">
    <img src="https://upload.wikimedia.org/wikipedia/commons/1/1a/1x1_placeholder.png" />
  </div>

  <div class="text">
    <h1>Title</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vulputate lectus nec ligula fermentum suscipit. Nam lobortis turpis sed augue interdum maximus.</p>
  </div>
</section>

EDIT JULY 2021:

For some reason the previous solution was not working anymore without adding display: grid to the pixel container.
There is still a bug I didn't manage to fix causing the element to have sometimes a wrong aspect ratio before resizing the window.

Also this solution has a major drawback as it breaks as soon as the text wraps into a number of lines making its height too big. The square then grows in width as it is based on the height, reducing the width left for the text which grows again in height... Ending up up in some kind of infinite loop. This can be avoided using a max-width on the image, but obviously this would break the aspect ratio.

Anyway this more a hack than a real solution to the question but I couldn't find a better way to achieve this without using Javascript.

Heptagon answered 26/2, 2019 at 15:17 Comment(5)
The .square isn't maintaining a 1:1 aspect ratio in your example. And your sample image link is broken.Kulsrud
I can see the image unless I open the link in a private window so I guess it is a cache issue. You can just check it out in a local HTML file. I just added two screenshots showing it working.Transparent
The image generated by the 1by1px website was stored in local storage. I replaced it by another one in the pen: codepen.io/anon/pen/pYvEEqTransparent
This is very close, but not exactly what I'm looking for. I also realize that my question doesn't completely explain the behavior, so I'll provide an edit.Kulsrud
The display: flex; property is a block element so it spans the width of its container. I'm expecting the text portion to hit some maximum width before beginning to wrap. Setting to display: inline-flex; in your example seems to break, unfortunately. You can see the behavior when adding a border to the container (flex parent).Kulsrud

© 2022 - 2024 — McMap. All rights reserved.