How to style a square div dynamically based on the height? - html

I already read this Q&A and the blog posts that it links to. The problem here is that the referred-to methods only work if you have a set width and are setting the height dynamically. But how can you do it if you need the opposite--the height is known but the width is not?
I have a visual element, specified in CSS, that I need to appear consistently in multiple contexts. (For the purposes of this exercise it may as well be a red square.) The contexts have set heights--e.g. a nav with height x, a list-item with height y, etc. The width of the element, therefore, needs to be based on the height -- not the other way around.
I tried reversing the method mentioned in the linked articles (to use height instead of width, and padding-right instead of padding-top), but it doesn't work -- the element winds up with no width, and thus the red square (.my-box .content) doesn't appear:
HTML:
<div class="outer">
<div class="my-box">
<div class="content">
</div>
</div>
<div class="more-stuff">
Some more stuff goes here
</div>
</div>
CSS:
.outer {
background-color: white;
height: 72px;
padding: 12px;
display: flex;
flex-direction: row;
}
.my-box {
height: 100%;
position: relative;
}
.my-box::before {
content: "";
display: block;
padding-left: 100%;
}
.content {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: red;
}
.more-stuff {
flex: 1 1 auto;
margin-left: 12px;
}
JSFiddle for the above
Does anyone know if this is possible without JS or hardcoding the dimensions every time? Googling finds nothing that works for dynamic height--even with quotation marks. :(

JS is the only reliable option in your case.
In CSS box model (and HTML in general) width and height are not symmetric.
In general HTML has "endless tape" layout model - width is fixed (by view/window) but height is not known upfront - needs to be calculated.
CSS does layout in following steps:
do horizontal layout inside left and right bounds. At the end this will give you content height.
If height:auto (by default) set element height computed at step #1.
Do vertical alignment if needed (table-cell and flexbox elements).
Mathematically speaking height = F(width,content) and F here is a step function - different input width values may give you same output height value. Step function has no determined inverse function - there is no such an inverse function F' that will allow you to calculate width = F'(height,content).
(My pardon for the math on pure CSS subject, but I don't know how to explain it otherwise.)

A little bit a hack, but still a solution.
We create a box with ratio 1:1, set the width and rotate box transform: rotateZ(90deg).
*,
*:before,
*:after {
box-sizing: inherit;
}
html {
box-sizing: border-box;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
body {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
background-color: #F72F4E;
overflow: hidden;
}
.Box {
width: 50vmin; /* or 400px for ex. */
position: relative;
background-color: rgba(0,0,0,.2);
overflow: hidden;
position: relative;
-webkit-transform: rotateZ(90deg);
transform: rotateZ(90deg);
}
.Box:before {
content: "";
display: block;
height: 0;
padding-top: 100%;
}
.Box__content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 12px;
-webkit-transform: rotateZ(-90deg);
transform: rotateZ(-90deg);
}
<div class="Box">
<div class="Box__content">Box</div>
</div>
P.S.: SVG can be useful in that case, i hope.

Related

How can I make a resizable flex item retain its aspect ratio?

I have an effect on my website, and it only works within a 16:9 aspect ratio. This means I need to keep it within that aspect ratio. I wanted to make a box that was vertically and horizontally centered which could resize proportionally to contain the effect. I looked up many tutorials and guides on flex resizing, but i still cant get it to work properly. The padding in the that contains the box is lopsided, and it doesnt align properly either. It scrolls horizontally even though im using 100vh/vw?? Does 100% of the viewport's height really mean what it says?
I'm really not sure what to do...
Codepen example of my code below:
https://codepen.io/Ktashi/pen/KKeOJey
html
<div class="flex-align">
<div class="aspect-ratio-box"></div>
</div>
css
body {
padding: 0;
margin: 0;
}
.flex-align {
width: 100vw;
height: 100vh;
margin: 0;
padding: 1vw;
display: flex;
align-items: center;
justify-content: center;
}
.aspect-ratio-box {
height: auto;
aspect-ratio: 16/9;
background: red;
flex-grow: 1;
flex-shrink: 1;
flex-basis: 94vw;
max-height: 94vw;
max-width: 94vw;
}
I tried changing the flex-grow: property's value, along with flex-shrink: and flex-basis: but that didn't help much. I'm very stuck as I've only really been coding with html and css for about a year off and on.
You can use the CSS media query to test whether the item will fit within the parent which has 100vw/100vh dimensions.
This snippet is just to give the idea.
It does a couple of things - makes the parent's padding be part of its dimensions by setting box-sizing border-box and sets the height or width as % of the parent dimensions.
.aspect-ratio-box {
aspect-ratio: 16/9;
background: red;
}
#media (max-aspect-ratio: 16 / 9) {
.aspect-ratio-box {
width: 94%;
}
}
#media (min-aspect-ratio: 16 / 9) {
.aspect-ratio-box {
height: 94%;
}
}
body {
padding: 0;
margin: 0;
background: black;
}
.flex-align {
background: blue;
width: 100vw;
height: 100vh;
margin: 0;
padding: 1vw;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
<div class="flex-align">
<div class="aspect-ratio-box"></div>
</div>

Shrink a row of images to fit container height and maintain aspect ratios

I'd like to know how to shrink a row of images so that they all fit within a div with an unspecified height. The images should never scale up beyond their native height, and they must maintain their aspect ratio. Also, I'd like the height of the containing div to be limited to the native height of the tallest image. The image tags have no height or width attributes.
Here's a fiddle with what I have so far. I approached this using flexbox and object-fit: scale-down. The row of images in question are gray and are in the div with the green background. They currently do not scale at all, but they are at least centered vertically and horizontally how I'd like them to be. Here are before and after images of the effect I'd like to achieve (sorry for switching the green background to yellow in the images). Additional details below the code snippet, but that about sums up the basic question.
body {
font-family: arial;
font-size: 26px;
text-align: center;
color: black;
background-color: white;
}
.smallhint {
font-size: 16px;
color: #8c8c8c;
}
img {
padding: 0;
margin: 0;
font-size: 0;
display: block;
object-fit: scale-down;
min-height: 0;
}
.flex-column {
display: flex;
flex-direction: column;
padding: 0px;
margin: 0px;
height: 90vh;
flex-grow: 0;
min-width: 0;
min-height: 0;
}
.flex-row {
display: flex;
flex-direction: row;
flex: 0 1.5 auto;
justify-content: center;
align-items: center;
background-color: green;
}
.context {
display: flex;
flex-direction: column;
max-height: 100%;
background-color: blue;
}
.primary {
position: relative;
z-index: 0;
right: 0;
bottom: 0;
padding: 0;
font-size: 0;
min-height: 0;
align-items: end;
background-color: orange;
}
.primary img {
margin-left: auto;
margin-right: auto;
border-style: solid;
border-width: 3px;
border-color: black;
height: calc(100% - 2*3px);
}
.mask {
position: absolute;
z-index: 1;
top: 0;
right: 0;
width: 100%;
height: 100%;
font-size: 0;
}
.nonimage {
padding-top: 5px;
display: inline;
background-color: pink;
}
<div class="flex-column">
<div class="primary">
<img src="https://via.placeholder.com/200">
<div class="mask">
<img src="https://via.placeholder.com/200/FF000">
</div>
</div>
<div class="flex-row">
<div class="context">
<img src="https://via.placeholder.com/75x150">
</div>
<div class="context">
<img src="https://via.placeholder.com/150x75">
</div>
</div>
<div class="nonimage">
<div class="smallhint">Some Text<br>Other Text</div>
</div>
</div>
I'm working on a (fixed-height) interface styled with CSS and will likely be asking a series of questions. I'm not great at CSS, so I'm open to approaches that are very different from my failed attempt!
At the top is a single centered image ("primary image"), below that are two other images ("secondary images") in a row, and below that is some text. Eventually, I'd like both sets of images to be responsive to changes in the height and width of the browser. However, I'd like to preferentially scale down the secondary images more than the primary image when the browser is too short to contain everything at native dimensions. For this, it seemed like flexbox containers with various flex-grow values would work here; it seems to work with the primary image somewhat, but the secondary images refuse to scale.
Also, I'm aware that, even if my current approach worked, the object-fit: scale-down strategy would leave behind some unwanted "padding" that will result in visual space between the secondary images. I have a feeling a very different approach may be required to get the effect that I want in the end, since I want the images to sit adjacent to each other without extra space around them. Furthermore, there also seems to be an issue with the container itself when the browser becomes very thin, since a scrollbar appears but it should always be 90vh.
Thank you all for the input!
Add a min-height: 0; rule for .flex-row. I guess that means it was pretty close to working when I asked the question.
This solution retains the issue I mention in my question about the additional "padding" created around images when object-fit: scale-down (or cover) is used. So, that means I'll be asking another question about that topic!

How to reduce, using CSS, a div's width once its max-height has been reached?

I'm trying to create a menu that can contain an undefined amount of objects; said objects' sizes are dependent on the available width (the width of the menu). The menu's height should never exceed certain value and still contain all the child objects, meaning that the child elements can shrink, while maintaining proportions, but never overflow the "containment area".
Something like this:
add = () => {
const menuObject = document.createElement('div')
menuObject.classList.add('menu-element')
const menu = document.getElementById('menu')
menu.appendChild(menuObject)
}
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
button{
padding: 40px;
}
.bounds{
position: absolute;
border: 2px dashed black;
width: 10%;
right: 8px;
bottom: 8px;
height: 60%;
box-sizing: content-box;
}
.menu{
position: absolute;
width: 10%;
right: 10px;
bottom: 10px;
background-color: chocolate;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.menu-element{
width: 100%;
background-color: dodgerblue;
margin: 5%;
}
.menu-element::before{
content: "";
padding-top: 100%;
display: block;
}
<body>
<button onclick="add()">Add item</button>
<div class="bounds">
Items should never leave this box
</div>
<div id="menu" class="menu"></div>
</body>
I have tried setting a max-height attribute to the menu, but that doesn't modify its width which is, ultimately, the value that controls the whole sizing schema. I'm looking for a CSS only solution, if it's at all possible, and any light that can be shed on the issue will be greatly appreciated.
If you want the menu to be contained by some bounds, it should be placed within these bounds so it can expand into them.
As the menu is display: flex, we can have child elements share the available space within the menu by adding flex: 1 to them.
We can optionally add a max-height value to the menu items if we want to.
add = () => {
const menuObject = document.createElement('div')
menuObject.classList.add('menu-element')
const menu = document.getElementById('menu')
menu.appendChild(menuObject)
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
button {
padding: 40px;
}
.bounds {
position: absolute;
border: 2px dashed black;
width: 10%;
right: 8px;
bottom: 8px;
height: 60%;
box-sizing: content-box;
}
.menu {
width: 100%; /* Expand into boundaries */
height: 100%; /* Expand into boundaries */
background-color: chocolate;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.menu-element {
flex: 1; /* Makes elements expand into available space */
max-height: 25%; /* Height cap in case we want to have a set initial size */
width: 100%;
background-color: dodgerblue;
outline: 1px solid red; /* Help visualize boundaries */
}
<button onclick="add()">Add item</button>
<div class="bounds">
<div id="menu" class="menu"></div> <!-- Move menu into boundaries -->
</div>
Set the container div's overflow to overflow: auto
.bounds { overflow: auto; } // for example
This will give scroll bars should the items inside exit the container borders.
Also, the flex-shrink property specifies how the item will shrink relative to the rest of the flexible items inside the same container. If the element is not a flexible item, the flex-shrink property has no effect.
Set element flexible with display: flex;
flex-shrink: number|initial|inherit; // Syntax
for example if you had flexible paragraphs:
p:nth-of-type(2){flex-shrink: 3;} //Second flex-item shrinks 3x more than the rest
number = A number specifying how much the item will shrink relative to the rest of the flexible items. Default value is 1.
initial = Sets this property to its default value.
inherit = Inherits this property from its parent element.
https://www.w3schools.com/CSSref/css3_pr_flex-shrink.asp flex-shrink guide
https://www.w3schools.com/CSSref/pr_pos_overflow.asp overflow guide

CSS circle without using fixed width and height

I want to display the notification count inside a circle but I don't want it to have a fixed width so the circle can expand when there is a bigger number/text inside the circle.
.circle {
height: 20px;
width: 20px;
line-height: 20px;
border-radius: 50%;
background-color: red;
color: white;
text-align: center;
}
<div class="circle">5</div>
<br>
<div class="circle">102</div>
See this CSS only solution. Set the same value of min-width and min-height for 1 digit number. Use a pseudo element for vertical alignment and to maintain the square shape. With border-radius applies to the container for the circle.
.circle {
display: inline-block;
border-radius: 50%;
min-width: 20px;
min-height: 20px;
padding: 5px;
background: red;
color: white;
text-align: center;
line-height: 1;
box-sizing: content-box;
white-space: nowrap;
}
.circle:before {
content: "";
display: inline-block;
vertical-align: middle;
padding-top: 100%;
height: 0;
}
.circle span {
display: inline-block;
vertical-align: middle;
}
<div class="circle"><span>8</span></div>
<div class="circle"><span>64</span></div>
<div class="circle"><span>512</span></div>
<div class="circle"><span>4096</span></div>
This is so hacky, but it seems to check out on all the major browsers' latest versions, so I'll post it anyway. The basic principle is that percent-based padding (even top and bottom padding) are relative to the width of the parent. Setting it to 100% with a width and height of 0 would theoretically mean that the height of the element would always be equal to the width. Combine that with a pseudo element and you don't even need to change the markup. I used flexbox to correct the centering of the content. It seems to work on the browsers I tested it on, but this is definitely dependent on recent versions because it uses flexbox and display:table. I also had to add a min-width to ensure it doesn't appear out of shape for too little of content.
.circle {
background-color: red;
color: white;
text-align: center;
position: relative;
border-radius: 50%;
min-width: 1.25em;
display: inline-flex;
align-items: center;
justify-content: center;
}
.circle:after {
content: '';
padding-top: 100%;
display:table;
}
<div class="circle">5</div>
<br>
<div class="circle">102</div>
<br>
<div class="circle">4298347918</div>
Simple CSS for circles that works almost ever:
.circle {
position: relative;
border-radius: 50%;
width: 100%;
height: auto;
padding-top: 100%;
}
The trick is that the padding top is calculated on the width so you can use it for makinh height equals width
Try using border-radius:50% and set max-width and height
Here is a quick example where you can see how to dynamically maintain a circle with css and js.
As Jagjit Singh pointed out here, you can achieve a circle using border-radius: 50%; instead of a fixed-pixel value.

Make child width equal to parent height with CSS only

As described by the image there are two elements: A parent (dark gray) and child (not so dark gray). The width and height of the parent is fluid. The ratio of the child i 1:1 or y:y where y is equal to the height of the parent.
I've tried to find ways to solve this using flex, calc, padding etc but have reached the end of the road. Any ideas how to solve this with pure CSS are much appreciated.
EDIT: I realize now I should have added more details regarding the usage of this scenario. As well as what I consider to be a dynamic height. Dynamic height for me suggests that the height is decided by the amount of content it contains. So I added some HTML to clarify. The .content div may be unnecessary if you can put the content directly in the .container div. But that depends on how you write the CSS:
<div class="container">
<div class="content">
Here is some text. It can be long and it can be short.
It will affect the height of the .container thus also
the height and width of the .square.
</div>
<div class="square">1:1</div>
</div>
I think it is not possible to do what you try!You can't get parents height without JS. But maybe there is another solution. Does your parent container also has a fixed proportion?
This question was quite old. But today I found a quite-tricky solution that may help. That is, I utilize the property of image (svg here) that preserve the aspect ratio while scaling. So I insert an empty svg and make its height fit the parent. Then we have its width equals to its height. (You can change the 1 1 in the part <svg viewBox="0 0 1 1" > to change the ratio).
See the example below. Sorry for my bad English.
.outer {
display: flex;
/* This is just for the example */
width: 700px; /* x */
height: 100px; /* y */
font-size: 18px;
font-family: Verdana, Geneva, sans-serif;
}
.left {
flex-grow: 1
/* This is just for the example */
color: #cddfc9;
background-color: #636363;
padding: 10px;
height: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.child {
height: 100%;
position: relative;
display: inline-flex;
}
/* This is the trick */
.child svg {
height: 100%;
width: 100%;
}
.child > .content {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
/* This is just for the example */
color: white;
background-color: #8a8a8a;
display: flex;
align-items: center;
justify-content: center;
}
<div class="outer">
<div class="left">
Text of various length be here...
</div>
<div class="child">
<svg viewBox="0 0 1 1" ></svg>
<div class="content">
yxy
</div>
</div>
</div>
you can use the vh property for this. Set the height of your parent div in vh and then use the same vh value for the width of your child div and set the height of the child div to 100%.
#parent{
position: absolute;
left: 0;
top:0;
width: 400px;
height: 50vh;
background-color: red;
}
#child{
position: relative;
float: right;
height: 100%;
width: 50vh;
background-color: blue;
}
<div id="parent">
<div id="child"></div>
</div>