Fully responsive items with CSS grid and auto-fit minmax - html

The use of grid-template-columns: repeat(auto-fit, minmax(600px, 1fr)) makes it easy to build a responsive CSS grid. The container will be filled with as many elements fit into a row, without using a media query.
.container {
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
}
.item {
height: 100px;
background: #ccc;
}
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
The problem is that the items are wider than the screen when the screen is smaller than the min-value specified in minmax(). You can fix this by adding a media query at 400px, but this only works when you know that there's no content around the container. And that's almost impossible when the container could be placed anywhere.
Is there a way or property to tell the items that they should never be wider than 100%?
Something like: Fill the container with as many 400px items as possible, but ensure that non of them gets wider than 100% of the width of the container.
CodePen Demo

You should change grid-template-columns to grid-template-columns: repeat(auto-fit, minmax(min-content, 400px)) because minmax works this way: it tries to apply max value and if fails it applies minimum. But in this you can get blank space in your grid to the right. Demo:
.container {
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fit, minmax(min-content, 400px));
}
.item {
height: 100px;
background: #ccc;
}
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>

Fill the container with as many 400px items as possible, but ensure that non of them gets wider than 100% of the width of the container.
For that you can use the "max-content" property, in your example this would be:
.unresponsive {
grid-template-columns: repeat(auto-fit, minmax(400px, auto));
grid-auto-columns: max-content;
}

Related

How to insert div at the end of a grid?

I have the following code:
.my-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-gap: 20px;
}
.item {
background-color: #ddd;
height: 300px;
}
<div class="my-grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<div class="new-item">hello!</div>
Which looks like this in my testing:
Notice how the hello is on the next row.
Is there any way to instead have it inserted after the grid above? Such that it is the last grid item? Such that it is in line with it, instead of being on the next row.
The obvious solution is to just place the new-item div as a child of my-grid, but I cannot do that in my case, because my-grid is an external component from the internet, so I cannot insert something inside of it.
These two elements also have a common parent as well. Which can be anything.
You can consider display: contents if they share the same container:
/* transfer all the styles to container
in the near future you can do .container:has(.my-grid) {}
*/
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-gap: 20px;
}
.item {
background-color: #ddd;
height: 300px;
}
/* we remove "my-grid" div*/
.my-grid {
display: contents;
}
<div class="container">
<div class="my-grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<div class="new-item">hello!</div>
</div>
You can set absolute position on "new-item" and if you have a div container above "my-grid" do not forget to set "position: relative", on it. Then you can adjust the left and top of "new-item", relative to the main div container. Might course some problem in different resolutions of the screen, adress it with some media queries.

How to create a CSS GRID with 3 distinct head elements

I get a list using the CSS display: grid as shown below
My task has to be edited so that the list will look like this when it is displayed on PC, while on my mobile it will look like the first image.
The first 3 elements will be larger than the rest.
And responsive support
Here is my code:
HTML:
.grid {
width: 638px;
display: grid;
grid-row-gap: 68px;
grid-column-gap: 12px;
grid-template-rows: auto auto;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
justify-content: space-between;
}
.item {
border: 3px solid green;
width: auto;
height: 150px;
}
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
I tried to fix it but it doesn't work like, thanks everyone!
you may rethink it from a 12 columns grid and reset grid-column spanning to the item.
possible example
.grid {
width: 638px;
display: grid;
grid-row-gap: 68px;
grid-column-gap: 12px;
grid-template-rows: auto auto;
grid-template-columns: repeat(auto-fit, minmax(35px, 1fr));/* 35 x 4 = 140 */
justify-content: space-between;
}
.item {
border: 3px solid #4285F4;
border-radius:3px;
width: auto;
height: 150px;
grid-column:auto / span 4; /* about 35px X 4 of width */
margin-right:8px;
}
.item:nth-child(3), .item:nth-child(3)~.item {
margin-right:0;
}
.item:nth-child(3)~.item {
grid-column:auto / span 3;
}
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
spanning columns rules and grid-template-columns can be reset via the mediaquerie your want to use.
You have to use a number that contains 3 and 4 as multiplicator. For example 12. So You divide the grid into 12 columns. Then you use nth-child pseudo selctor to selct the first 3 and give them a span of 4 and every other child a span of 3.
with :nth-child(-n+3) you select the first 3 elements. As you want them to be 3 boxes in one row the need to span 4 to fit the 12 columns.
with :nth-child(n+3) you select all element with exeption of the first 3. Now you need to let them span 3 columns to fit 4 boxes within the 12 column wide row.
.grid {
width: 638px;
display: grid;
grid-row-gap: 68px;
grid-column-gap: 12px;
grid-template-rows: auto;
grid-template-columns: repeat(12, 1fr);
}
.item {
border: 3px solid green;
height: 150px;
}
.item:nth-child(n+3) {
grid-column: span 3;
}
.item:nth-child(-n+3) {
grid-column: span 4;
}
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>

How do dynamically position and size div elements?

I am trying to have an angular component that can dynamically position and resize div elements depending on how many div elements are to be displayed. This is what I have achieved so far.
EDIT: If you checkout my stackblitz example I am using display: flex
(thus flexbox). The issue I am having is how can I make sure that a
row will at most only have 3 divs and then wrap to the next line and
that the divs length will always be the same?
.
EDIT 2: My goal is that when there is one or two divs the length of
the div is half that of the container. That is why I put a 'ruler' at
the top in the image below. If there are more than 2 divs the length
of the divs should be 1/3 of the length of the container. And a row
should be filled by three divs before wrapping to the next row.
Below is a visual representation of what I am trying to achieve:
All the divs in a particular set are of the same width and height.
I see how I can hack around by using calc() in width but then I will have to pass the number of divs variable to my css file. Also I am aware that using Multiple conditions in ngClass - Angular 4 is an option, but I would like to use that if I did not have any other option.
How can I achieve what I am trying to achieve with css only (if possible)? If it is not possible to use css only I will gladly take any other recommendation.
First make flex container with
.container1 {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
Second use calc() to specify items width
.item {
width: calc(33.33% - 0px);
}
Third specify first and second child behavior. They should grow, but max-width: 50%
.item:first-child, .item:nth-child(2) {
flex-grow: 1;
max-width: 50%;
}
.container1 {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
outline: dashed red 1px;
margin: 10px 0
}
.item {
height: 100px;
background-color: green;
outline: 1px dashed blue;
width: calc(33.33% - 0px);
}
.item:first-child, .item:nth-child(2) {
flex-grow: 1;
max-width: 50%;
}
<div class="container1">
<div class="item"></div>
</div>
<div class="container1">
<div class="item"></div>
<div class="item"></div>
</div>
<div class="container1">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<div class="container1">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<div class="container1">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<div class="container1">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<div class="container1">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<div class="container1">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
Checkout sample working copy Check the flex will slove the above issues i guess.
.parent {
display:flex;
justify-content:space-around;
flex-wrap:wrap;
}
.option-box{
width:40%;
border: 1px blue solid;
margin: 3px
}
#media screen and (max-width: 600px) and (min-width: 301px) {
.option-box {
width:30%;
}
}
#media screen and (max-width: 300px) {
.option-box {
width:100%;
}
}

Responsive CSS Grid with persistent aspect ratio

My goal is to create a responsive grid with an unknown amount of items, that keep their aspect ratio at 16 : 9.
Right now it looks like this:
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, 160px);
grid-template-rows: 1fr;
grid-gap: 20px;
}
.item {
height: 90px;
background: grey;
}
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
The problem is, that the items won't scale with the screen size, resulting in a margin at the right site. But when making the grid adapt to the screen size with e.g.: grid-template-columns: repeat(auto-fit, minmax(160p, 1fr)) and removing the height: 90px;, the aspect ratio doesn't persist.
Maybe there is a better solution without css grid? (Maybe using javascript)
You could take advantage of the fact that padding in percentages is based on width.
This CSS-tricks article explains the idea quite well:
...if you had an element that is 500px wide, and padding-top of 100%,
the padding-top would be 500px.
Isn't that a perfect square, 500px × 500px? Yes, it is! An aspect
ratio!
If we force the height of the element to zero (height: 0;) and don't
have any borders. Then padding will be the only part of the box model
affecting the height, and we'll have our square.
Now imagine instead of 100% top padding, we used 56.25%. That happens
to be a perfect 16:9 ratio! (9 / 16 = 0.5625).
So in order for the columns to maintain aspect ratio:
Set the column widths as you suggested:
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr))
Add a pseudo element to the items to maintain the 16:9 aspect ratio:
.item:before {
content: "";
display: block;
height: 0;
width: 0;
padding-bottom: calc(9/16 * 100%);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
grid-template-rows: 1fr;
grid-gap: 20px;
}
.item {
background: grey;
display: flex;
justify-content: center;
}
.item:before {
content: "";
display: block;
height: 0;
width: 0;
padding-bottom: calc(9/16 * 100%);
}
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
Codepen Demo (Resize to see the effect)
CSS Evolution : aspect-ratio property
We can now use aspect-ratio CSS4 property (Can I Use ?) to manage easily aspect ratio without padding and pseudo-element tricks. Combined with object-fit we obtain very interesting rendering.
Here, photos of various ratios I need to render in 16/9 :
section {
display: grid;
gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* Play with min-value */
}
img {
background-color: gainsboro; /* To visualize empty space */
aspect-ratio: 16/9;
/*
"contain" to see full original image with eventual empty space
"cover" to fill empty space with truncating
"fill" to stretch
*/
object-fit: contain;
width: 100%;
}
<section>
<img src="https://placeimg.com/640/360/architecture">
<img src="https://placeimg.com/640/360/tech">
<img src="https://placeimg.com/360/360/animals">
<img src="https://placeimg.com/640/360/people">
<img src="https://placeimg.com/420/180/architecture">
<img src="https://placeimg.com/640/360/animals">
<img src="https://placeimg.com/640/360/nature">
</section>
Playground : https://codepen.io/JCH77/pen/JjbajYZ
I needed this exact same thing for video layouts, but I couldn't use the other answers because I need to be bounded by width and height. Basically my use case was a container of a certain size, unknown item count, and a fixed aspect ratio of the items. Unfortunately this cannot be done in pure CSS, it needs some JS. I could not find a good bin packing algorithm so I wrote one myself (granted it might mimic existing ones).
Basically what I did is took a max set of rows and found the fit with the best ratio. Then, I found the best item bounds retaining the aspect ratio, and then set that as auto-fit height and width for the CSS grid. The result is quite nice.
Here's a full example showing how to use it with something like CSS custom properties. The first JS function is the main one that does the work of figuring out the best size. Add and remove items, resize browser to watch it reset to best use space (or you can see this CodePen version).
// Get the best item bounds to fit in the container. Param object must have
// width, height, itemCount, aspectRatio, maxRows, and minGap. The itemCount
// must be greater than 0. Result is single object with rowCount, colCount,
// itemWidth, and itemHeight.
function getBestItemBounds(config) {
const actualRatio = config.width / config.height
// Just make up theoretical sizes, we just care about ratio
const theoreticalHeight = 100
const theoreticalWidth = theoreticalHeight * config.aspectRatio
// Go over each row count find the row and col count with the closest
// ratio.
let best
for (let rowCount = 1; rowCount <= config.maxRows; rowCount++) {
// Row count can't be higher than item count
if (rowCount > config.itemCount) continue
const colCount = Math.ceil(config.itemCount / rowCount)
// Get the width/height ratio
const ratio = (theoreticalWidth * colCount) / (theoreticalHeight * rowCount)
if (!best || Math.abs(ratio - actualRatio) < Math.abs(best.ratio - actualRatio)) {
best = { rowCount, colCount, ratio }
}
}
// Build item height and width. If the best ratio is less than the actual ratio,
// it's the height that determines the width, otherwise vice versa.
const result = { rowCount: best.rowCount, colCount: best.colCount }
if (best.ratio < actualRatio) {
result.itemHeight = (config.height - (config.minGap * best.rowCount)) / best.rowCount
result.itemWidth = result.itemHeight * config.aspectRatio
} else {
result.itemWidth = (config.width - (config.minGap * best.colCount)) / best.colCount
result.itemHeight = result.itemWidth / config.aspectRatio
}
return result
}
// Change the item size via CSS property
function resetContainerItems() {
const itemCount = document.querySelectorAll('.item').length
if (!itemCount) return
const container = document.getElementById('container')
const rect = container.getBoundingClientRect()
// Get best item bounds and apply property
const { itemWidth, itemHeight } = getBestItemBounds({
width: rect.width,
height: rect.height,
itemCount,
aspectRatio: 16 / 9,
maxRows: 5,
minGap: 5
})
console.log('Item changes', itemWidth, itemHeight)
container.style.setProperty('--item-width', itemWidth + 'px')
container.style.setProperty('--item-height', itemHeight + 'px')
}
// Element resize support
const resObs = new ResizeObserver(() => resetContainerItems())
resObs.observe(document.getElementById('container'))
// Add item support
let counter = 0
document.getElementById('add').onclick = () => {
const elem = document.createElement('div')
elem.className = 'item'
const button = document.createElement('button')
button.innerText = 'Delete Item #' + (++counter)
button.onclick = () => {
document.getElementById('container').removeChild(elem)
resetContainerItems()
}
elem.appendChild(button)
document.getElementById('container').appendChild(elem)
resetContainerItems()
}
#container {
display: inline-grid;
grid-template-columns: repeat(auto-fit, var(--item-width));
grid-template-rows: repeat(auto-fit, var(--item-height));
place-content: space-evenly;
width: 90vw;
height: 90vh;
background-color: green;
}
.item {
background-color: blue;
display: flex;
align-items: center;
justify-content: center;
}
<!--
Demonstrates how to use CSS grid and add a dynamic number of
items to a container and have the space best used while
preserving a desired aspect ratio.
Add/remove items and resize browser to see what happens.
-->
<button id="add">Add Item</button><br />
<div id="container"></div>
Maybe I am not able to understand the question, but how do you plan to keep item aspect ratio 16:9, while wanting to scale items with screen width?
If you are fine with items having enlarged width in between screen sizes, this can work:
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
grid-template-rows: 1fr;
grid-gap: 20px;
}
.item {
height: 90px;
background: grey;
}
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>

Responsive Layout with same growing gaps in each row

I have a container with a random number of elements which should always align on the left side. The container width can increase or decrease.
If the container size is increasing then the first element from the row+1 should go one row back and should appear on the right side. But it should only go one row back if it can fit there with a padding on the left and right side.
While the element does not have space one row abouve, then the space between the elements should grow until the element from row+1 can fit in this row.
The same functionality should work also in the reverse way.
Here is my code:
.container {
min-height: 400px;
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-around;
align-items: center;
align-content: flex-start;
}
.container:after {
display: block;
content: " invisible node ";
flex(999 999 auto);
}
.item {
flex: 0 0 auto;
margin: 5px;
width: 50px;
height: 50px;
background-color: green;
}
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
https://jsfiddle.net/p6k537ep/
My Problem ist that in the second row the space between the elements is not the same as in the first row (It depends how many elements are there).
Edit:
But it is working that the elements from the second row can only move one row abouve if they have anough space. The Gap is growing in the correct way in the first row but not in the second one.
AFAIK, this is not solvable in Flexbox without adding invisible non-semantic helper elements to hold the horizontal rhythm of the last row (with the same width and horizontal margins as .items, but zero height).
However, this is easily solvable in CSS Grid, which seems perfect for this:
.container {
min-height: 400px;
display: grid;
grid-template-columns: repeat(auto-fill, 60px);
justify-content: space-around;
align-items: center;
align-content: start;
}
.item {
flex: 0 0 auto;
margin: 5px;
width: 50px;
height: 50px;
background-color: green;
}
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
and kind of solvable with inline-blocks, using the similar pseudo-element hack and some "magic" of the inline formatting:
.container {
min-height: 400px;
text-align: justify;
font-size: 5px;
line-height: 0;
}
.container:after {
display: inline; /* it's important, it should continue the same line! */
/* each character below acts as an invisible placeholder for the item */
/* with em dashes, they would be 1em (5px here) wide each */
content: '— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — ';
letter-spacing: 55px; /* expand each placeholder to the item width */
word-spacing: -56px; /* collapse the whitespaces */
margin-left: 5px;
}
.item {
display: inline-block;
margin: 5px;
width: 50px;
height: 50px;
background-color: green;
vertical-align: top;
}
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
But, honestly, I'd not recommend using the second option in production. Left-aligning the items instead would probably be a better fallback for browsers that don't support Grid.
You could use the margin property on the :after pseudoelement to keep the blocks together.
fiddle
.container {
min-height: 400px;
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-between;
align-items: center;
align-content: flex-start;
}
.container:after {
display: block;
content: " ";
margin-left: auto; /* added */
}
.item {
flex: 0 0 auto;
margin: 5px;
width: 50px;
height: 50px;
background-color: green;
}
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>