Maintain ratio of container and children while resize - css

I have modular grid of cards with and without image, based on flexbox.
Normally cards without image are 50% of the width, cards with image are 100% of the width, and those cards without image that doesn't have a pair are also 100%.
----------------
[[ txt ][ img ]] – card with an image
----------------
[ txt ][ txt ] – 2 cards without image
----------------
[ txt ] – card without image
----------------
[[ txt ][ img ]] – card with an image
----------------
Is it possible to maintain ratio of the cards and its content while resize, and also maintain modularity with css?
Here's the code
that let me maintain ratio, but when I try to apply this technique to cards children everything breakes
<div class="wrapper">
<div class="card">
<div class="card-text">
</div>
<div class="card-image">
</div>
</div>
</div>
.wrapper
display: flex
flex-wrap: wrap
justify-content: space-between
width: 620px
.card
height: 0
padding-bottom: 50%
display: flex
flex-direction: row
justify-content: space-between
flex: 1 1 300px
.has-card-image .card-text,
.has-card-image .card-image
height: 300px
width: 300px

I can see one way this could be done w/o script.
Either as in this case, using px values, or e.g. using vw, and the reason is that the card-text and card-image needs to know the wrapper's width to be able to size according to the expected output (which mean percent can't be used).
Fiddle demo
Stack snippet
.wrapper {
display: flex;
flex-wrap: wrap;
width: 600px;
}
.card {
display: flex;
flex: 1 300px;
}
.card .card-text,
.card .card-image {
height: 100px;
width: 300px;
}
.card .card-text:only-child {
flex: 1 300px;
}
/* demo styles */
.card .card-text, .card .card-image {
border: 1px solid;
box-sizing: border-box;
}
.card .card-text::before {
content: 'text';
}
.card .card-image::before {
content: 'image';
}
<div class="wrapper">
<div class="card">
<div class="card-text">
</div>
<div class="card-image">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
<div class="card-image">
</div>
</div>
</div>
If you need the margin, you'll need an extra wrapper
Fiddle demo
Stack snippet
.wrapper {
width: 620px;
overflow: hidden;
}
.inner {
display: flex;
flex-wrap: wrap;
margin-left: -20px;
}
.card {
display: flex;
flex: 1 300px;
}
.card .card-text,
.card .card-image {
height: 100px;
width: 300px;
margin-left: 20px;
}
.card .card-text:only-child {
flex: 1 300px;
}
/* demo styles */
.card .card-text,
.card .card-image {
border: 1px solid;
box-sizing: border-box;
}
.card .card-text::before {
content: 'text';
}
.card .card-image::before {
content: 'image';
}
<div class="wrapper">
<div class="inner">
<div class="card">
<div class="card-text">
</div>
<div class="card-image">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
<div class="card-image">
</div>
</div>
</div>
</div>
Updated
Here is a version using vw and margin, and is responsive.
Note, the make up for margin set on the wrapper can also be set on each item, though I found it simpler like this.
Fiddle demo
Stack snippet
.wrapper {
width: calc(80vw + 20px); /* 20px extra for margin */
margin: 0 auto;
overflow: hidden;
}
.inner {
display: flex;
flex-wrap: wrap;
margin-left: -20px;
}
.card {
display: flex;
flex: 1 40vw;
}
.card .card-text,
.card .card-image {
height: 100px;
width: 40vw;
margin-left: 20px;
}
.card .card-text:only-child {
flex: 1 40vw;
}
/* demo styles */
.card .card-text,
.card .card-image {
border: 1px solid;
box-sizing: border-box;
}
.card .card-text::before {
content: 'text';
}
.card .card-image::before {
content: 'image';
}
<div class="wrapper">
<div class="inner">
<div class="card">
<div class="card-text">
</div>
<div class="card-image">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
</div>
<div class="card">
<div class="card-text">
</div>
<div class="card-image">
</div>
</div>
</div>
</div>

Related

Flexbox make columns equal heights

I am trying to get 3 columns in a row and be the same height.
I am using the HTML/CSS below. I can't figure out why the boxes arent the same height.
.container {
text-align: center;
display: block;
width: 100%;
}
.grid {
width: 100%;
height: 100%;
}
.row {
display: flex;
height: 100%;
flex-direction: row;
}
.col {
background: #444;
padding: 2em;
flex: 1;
height: 100%;
border: 1px solid blue;
}
<div class="container">
<div class="data">
<div class="grid">
<div class="row">
<div class="col">
<p>col 1</p>
</div>
<div class="col">
<p>col </br>2</p>
</div>
<div class="col">
<p>col 3</p>
</div>
</div>
</div>
</div>
</div>
How can I make the boxes all the same height. thanks
Simply, remove the height: 100% from .col.
flex: 1; will do the job.
.row {
display: flex;
}
.col {
flex: 1;
background: #444;
padding: 2em;
border: 1px solid blue;
text-align: center;
}
<div class="container">
<div class="data">
<div class="grid">
<div class="row">
<div class="col">
<p>col 1</p>
</div>
<div class="col">
<p>col </br>2</p>
</div>
<div class="col">
<p>col 3</p>
</div>
</div>
</div>
</div>
</div>

How to have make a div take a minumum of 50% space but if no sibling is present take 100%

Simply put I have:
#Container {
display:'flex';
flex-wrap:'wrap';
}
.item {
flex-basis: '50%'
}
Scenario one:
<div id=Container>
<div class="item"></div> 33 %
<div class="item"></div> 33 %
<div class="item"></div> 33%
</div>
Scenario 2
<div id=Container>
<div class="item"></div> 50 %
<div class="item"></div> 50 %
</div>
scenario 3:
<div id=Container>
<div class="item"></div> 100 %
</div>
What I want in general tems is this to be fluid, the more items I put in the less space each item will have but if there is only 1 then I want it to take full space.
First you have to add display: flex; to #Container
#Container{
display: flex;
}
If you want to equally distribute the space between children then you can use flex property as
.item{
flex: 1;
}
Above CSS is minimum required styles, rest is for demo
#Container {
display: flex;
margin-top: 1rem;
}
.item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
}
.item:nth-child(1) {
background-color: red;
}
.item:nth-child(2) {
background-color: blueviolet;
}
.item:nth-child(3) {
background-color: aquamarine;
}
<div id="Container">
<div class="item">33 %</div>
<div class="item">33 %</div>
<div class="item">33 %</div>
</div>
<div id=Container>
<div class="item"> 50 % </div>
<div class="item"> 50 % </div>
</div>
<div id=Container>
<div class="item">100 %</div>
</div>
I think that this example could give you an idea of how to achieve what you want:
https://codepen.io/Eylen/pen/vYJBpMQ
.Container {
display: flex;
flex-wrap: wrap;
margin-bottom: 8px;
}
.item {
flex-grow: 1;
margin: 0 12px;
background: #f1f1f1;
}
Your main issue in the code that you gave, is that you're missing the flex item behaviour. I have just set that the item can grow to fill the space with the flex-grow:1.
You can make sure a flex child covers up the space if it can, you can provide flex-grow: 1
#Container {
display:flex;
}
.item {
width: 100%;
flex-grow: 1;
border: 1px solid;
text-align: center;
}
<h1> Scenario 1 </h1>
<div id=Container>
<div class="item">33 %</div>
<div class="item">33 %</div>
<div class="item">33%</div>
</div>
<h1> Scenario 2 </h1>
<div id=Container>
<div class="item">50 %</div>
<div class="item">50 %</div>
</div>
<h1> Scenario 3 </h1>
<div id=Container>
<div class="item">100 %</div>
</div>
Added a demo below.
$( document ).ready(function() {
$( "#add" ).click(function() {
$('#container').append('<div class="item"></div>');
});
$( "#remove" ).click(function() {
$('#container').children().last().remove();
});
});
#container {
width:100%;
height:500px;
background-color:#ebebeb;
display:flex;
flex-direction: column;
}
.item {
width:100%;
display: flex;
flex: 1;
border-bottom:1px solid #007cbe;
}
.item1 {
background:#007cbe;
}
.item2 {
background: #d60000;
}
.item3 {
background: #938412
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
<div class="item item1">1</div>
<div class="item item2">2</div>
<div class="item item3">3</div>
</div>
<button id="add"> Add </div>
<button id="remove"> Remove </div>
Apply to the below CSS to fulfill your requirement.
#Container {
display: flex;
}
.item {
width: 100%;
min-height: 100px;
border: 1px solid;
}

What am i doing wrong with flexbox trying to align these images?

So I am trying to align images one next to another with display flex but they're not working, tried a lot of things but none of them helped
.container {
display: flex;
width: 100%;
flex-wrap: wrap;
}
.image {
width: 100%;
margin-right: 10px;
}
<div class="container">
<div class="image">
<img src="photo.jpg">
</div>
<div class="image">
<img src="photo.jpg">
</div>
<div class="image">
<img src="photo.jpg">
</div>
<div class="image">
<img src="photo.jpg">
</div>
</div>
You need to remove
width: 100%;
from your .image styles.
Working Example:
.container {
display: flex;
width: 100%;
flex-wrap: wrap;
}
.image {
margin-right: 10px;
}
<div class="container">
<div class="image">
<img src="photo.jpg">
</div>
<div class="image">
<img src="photo.jpg">
</div>
<div class="image">
<img src="photo.jpg">
</div>
<div class="image">
<img src="photo.jpg">
</div>
</div>
Update:
Do you want a single row of 4 images? If so, you'll need to give each image a width or a flex-basis of equal to or less than 25% of the .container.
Otherwise an image will be assumed to have a width corresponding to its original size.
You can achieve this by inserting .image {flex: 0 1 25%;} into your CSS:
.container {
display: flex;
width: 100%;
flex-wrap: wrap;
}
.image {
flex: 0 1 25%;
margin-right: 10px;
}

Expandable box keeps growing

Browser: Chrome
I was trying to setup a row of boxes that have an item inside of them that can expand. I'd like the items on the same row to be the same height. When the expandable items are collapsed I expect the containers to shrink accordingly. Somehow I created something where when the box expands it keeps increasing its size.
This example was extracted from an application. So the multiple layer's of divs probably wont make much sense to you. I just extracted the minimal pieces needed to reproduce the issue.
const expandButtons = document.querySelectorAll('.expand');
for (let i = 0; i < expandButtons.length; i++) {
const button = expandButtons[i];
button.addEventListener("click", () => toggleExpand(button));
}
function toggleExpand(el) {
const content = el.parentElement.querySelector('.content');
content.classList.toggle('is-expanded');
}
.row {
display: flex;
}
.col {
display: flex;
}
.card {
display: flex;
flex-direction: column;
border: 1px solid black;
}
.wrapper {
height: 100%;
}
.card-block {
height: 100%;
display: flex;
flex-direction: column;
}
.content {
max-height: 0;
overflow: auto;
transition: max-height 500ms;
}
.content.is-expanded {
max-height: 6em;
}
.spacer {
display: flex;
flex-grow: 1;
}
<div class="row">
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<p>ASDF</p>
<div class="content">
<p>First</p>
<p>Second</p>
<p>Third</p>
</div>
<div class="spacer"></div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<div class="content">
<p>First</p>
<p>Second</p>
</div>
<div class="spacer"></div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
</div>
Question
Expanding the second box increases the height and it doesn't retract back when collapsed. Expanding/Collapsing the first fixes it. Why does this happen? (I'm interested in an explanation rather than a solution)
Edit
I added the part of the example that justifies the height: 100% to avoid confusion. It is so that I can keep the buttons on the bottom. This involves adding display: flex; flex-direction: column; to .card-block and adding the .spacer class and corresponding element. These do not contribute to the problem but are the reason for the height: 100%.
BTW, and easy way to fix it is to remove flex-direction: column from .card. Again, I'm interested in the "why".
Note, the odd behavior appears to be a bug in Chrome, as this does not happen in the other browsers.
No matter why this happens, when using height: 100% on .wrapper you need to ask yourself, 100% of what?
An element having height: 100% picks up those 100% from its parent, but for that to work, also the parent need a height, which your card doesn't have, hence the 100% will resolve to auto.
And since wrapper will be resolved auto, so will also the .card-block, as its parent (the wrapper) doesn't have a defined height either. (It appears to somewhat behave in Chrome, thought this won't work cross browsers.)
The solution is to use Flexbox all the way.
Stack snippet
const expandButtons = document.querySelectorAll('.expand');
for (let i = 0; i < expandButtons.length; i++) {
const button = expandButtons[i];
button.addEventListener("click", () => toggleExpand(button));
}
function toggleExpand(el) {
const content = el.parentElement.querySelector('.content');
content.classList.toggle('is-expanded');
}
.row {
display: flex;
}
.col {
display: flex;
}
.card {
display: flex;
flex-direction: column;
border: 1px solid black;
}
.wrapper {
/*height: 100%; removed */
flex: 1; /* added, fill parent height */
display: flex; /* added */
/*align-items: stretch this is the default and make its item,
the "card-block", fill its parent height */
}
.card-block {
/*height: 100%; removed */
display: flex;
flex-direction: column;
}
.content {
max-height: 0;
overflow: auto;
transition: max-height 500ms;
}
.content.is-expanded {
max-height: 6em;
}
.spacer {
display: flex;
flex-grow: 1;
}
<div class="row">
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<p>ASDF</p>
<div class="content">
<p>First</p>
<p>Second</p>
<p>Third</p>
</div>
<div class="spacer"></div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<div class="content">
<p>First</p>
<p>Second</p>
</div>
<div class="spacer"></div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
</div>
You should use height: auto, rather than height: 100%;
const expandButtons = document.querySelectorAll('.expand');
expandButtons.forEach(x => {
x.addEventListener("click", () => toggleExpand(x));
});
function toggleExpand(el) {
const content = el.parentElement.querySelector('.content');
content.classList.toggle('is-expanded');
}
.row {
display: flex;
}
.col {
display: flex;
}
.card {
display: flex;
flex-direction: column;
border: 1px solid black;
}
.wrapper {
height: auto;
}
.card-block {
height: auto;
}
.content {
max-height: 0;
overflow: auto;
transition: max-height 500ms;
}
.content.is-expanded {
max-height: 6em;
}
<div class="row">
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<p>ASDF</p>
<div class="content">
<p>First</p>
<p>Second</p>
<p>Third</p>
</div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<div class="content">
<p>First</p>
<p>Second</p>
</div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
</div>
As far as I understood you mean this. I just add align-items:baseline; to .card class. Also I remove margin-top from .card-block > p class.
const expandButtons = document.querySelectorAll('.expand');
for (let i = 0; i < expandButtons.length; i++) {
const button = expandButtons[i];
button.addEventListener("click", () => toggleExpand(button));
}
function toggleExpand(el) {
const content = el.parentElement.querySelector('.content');
content.classList.toggle('is-expanded');
}
.row {
display: flex;
}
.col {
display: flex;
}
.card {
display: flex;
flex-direction: column;
border: 1px solid black;
align-items: baseline;
}
.wrapper {
height: 100%;
}
.card-block {
height: 100%;
}
.card-block > p {
margin-top: 0;
}
.content {
max-height: 0;
overflow: auto;
transition: max-height 500ms;
}
.content.is-expanded {
max-height: 6em;
}
<div class="row">
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<p>ASDF</p>
<div class="content">
<p>First</p>
<p>Second</p>
<p>Third</p>
</div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<div class="content">
<p>First</p>
<p>Second</p>
</div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
</div>
A possible solution is to use
.wrapper {
flex: 1;
display: flex;
}
Rather than .wrapper {height: 100%}.
const expandButtons = document.querySelectorAll('.expand');
for (let i = 0; i < expandButtons.length; i++) {
const button = expandButtons[i];
button.addEventListener("click", () => toggleExpand(button));
}
function toggleExpand(el) {
const content = el.parentElement.querySelector('.content');
content.classList.toggle('is-expanded');
}
.row {
display: flex;
}
.col {
display: flex;
}
.card {
display: flex;
flex-direction: column;
border: 1px solid black;
}
.wrapper {
flex: 1;
display: flex;
}
.card-block {
height: 100%;
display: flex;
flex-direction: column;
}
.content {
max-height: 0;
overflow: auto;
transition: max-height 500ms;
}
.content.is-expanded {
max-height: 6em;
}
.spacer {
display: flex;
flex-grow: 1;
}
<div class="row">
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<p>ASDF</p>
<div class="content">
<p>First</p>
<p>Second</p>
<p>Third</p>
</div>
<div class="spacer"></div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="wrapper">
<div class="card-block">
<div class="content">
<p>First</p>
<p>Second</p>
</div>
<div class="spacer"></div>
<button type="button" class="expand">Expand</button>
</div>
</div>
</div>
</div>
</div>
According to my understanding, height: 100% confuses the element, as its parent's height is the expanded one (after the transition), so it want to fill it. Using flex: 1; and display: flex, the element will fill the remaining height, and will be a flexbox too, meaning its child won't change the .card height too.

flexbox height adjust to content

I use a "full design" flexbox.
I have a weird issue : I have a container that takes all the remaining space and I want in this container that the child, which is also flexbox, to have their height adjust to their content.
Here is the issue:
body, html {
width:100%;
height:100%;
display:flex;
}
.container {
display:flex;
flex:1;
flex-wrap:wrap;
}
.icon {
width:10vh;
margin:10px;
display:flex;
flex-direction:column;
}
.img {
width:10vh;
height:10vh;
display:flex;
align-items:center;
justify-content:center;
background-color:red;
}
.text {
text-align:center;
}
<div class="container">
<div class="icon">
<div class="img">
</div>
<div class="text">
action 1
</div>
</div>
<div class="icon">
<div class="img">
</div>
<div class="text">
Action 2
</div>
</div>
<div class="icon">
<div class="img">
</div>
<div class="text">
Action 3
</div>
</div>
<div class="icon">
<div class="img">
</div>
<div class="text">
Action 4
</div>
</div>
<div class="icon">
<div class="img">
</div>
<div class="text">
Action 5
</div>
</div>
</div>
As you can see, the icon takes the full height of the container : in fact, I don't want to specify a height because I don't know the text length and really want that, if the content is huge, the icon takes the height of its content ( don't want to cut the text). Moreover, if the page is resized, I really want the icon to be aligned (like on smartphone).
Also, I don't understand why the icon takes the height of its parent and not its content because I didn't specify "flex:1" on it. I assume that the default behaviour it's to fit the content size, but this seems not to be working.
image of the issue
.icon's are flex-column which makes .img's stretch by default unless .icon's have align-items. The reason why I didn't apply align-items to .icon's is because other nested flex-containers/flex-items started collapsing. Instead of adjusting down through the hierarchy, I went up and adjusted .container instead.
The relevant CSS:
.container {
display: flex;
flex: 1; /* If you remove this .container will shrink to wrap around .icon's */
flex-wrap: wrap;
justify-content: center; /* This centers .icon's along a horizontal axis. */
align-items: baseline; /* This aligns .icon's along a common baseline vertically. */
outline: 3px dashed blue; /* To show the size of .container */
}
.icon {
width: 10vh;
margin: 10px;
display: flex;
flex-direction: column;
outline: 1px dashed red; /* To show the size of .icon */
}
body,
html {
width: 100%;
height: 100%;
display: flex;
}
.container {
display: flex;
flex: 1;
flex-wrap: wrap;
justify-content: space-between;
align-items: baseline;
align-content: flex-start;
outline: 3px dashed blue;
}
.icon {
width: 10vh;
margin: 10px;
display: flex;
flex-direction: column;
outline: 1px dashed red;
}
.img {
width: 10vh;
height: 10vh;
display: flex;
align-items: center;
justify-content: center;
background-color: red;
}
.text {
text-align: center;
}
<div class="container">
<div class="icon">
<div class="img">
</div>
<div class="text">
Action 1
</div>
</div>
<div class="icon">
<div class="img">
</div>
<div class="text">
Action 2
</div>
</div>
<div class="icon">
<div class="img">
</div>
<div class="text">
Action 3
</div>
</div>
<div class="icon">
<div class="img">
</div>
<div class="text">
Action 4
</div>
</div>
<div class="icon">
<div class="img">
</div>
<div class="text">
Action 5
</div>
</div>
</div>

Resources