Fix/Improve Responsive CSS Grid - html

I'm building an app that needs a grid system, here's how far I've come:
I have a problem:
Each of the blocks should fill up the entire space of the grid
As you can see, the black block is not occupying the rest of the space. I want the boxes to occupy the rest white space(but all their widths should be the same).
I've read Expand a div to fill the remaining width and Make last element take remaining width with wrapping (and with IE9 support), but they don't answer the case of the dynamically wrapping grid that is here the important part since they are made for float and display: block.
Now let's look at the code:
HTML:
<div className='container'>
<div className="item"></div>
<div className="item"></div>
<div className="item"></div>
<div className="item"></div>
<div className="item"></div>
<div className="item"></div>
<div className="item"></div>
</div>
CSS:
container {
overflow: hidden;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.item {
height: 300px;
min-width: 300px;
background-color: red;
border: 2px solid yellow;
}
.container is the grid parent and .items are the children. Keep in mind that the solution should work with any number of children(.item). Thank you in advance!

I think with plain css this is not possible using grid. You could use flexbox with flex-grow instead of grid but when two items wrap in the next line they both are expanded.
Working example (a bit smaller then your example):
I don't know what that className attribute in your divs is for, i presume you meant just "class" and used that, respectively an id in the container for simplicity.
#container {
overflow: hidden;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.item {
height: 120px;
min-width: 120px;
background-color: red;
border: 2px solid yellow;
flex-grow: 1;
}
#last {
flex-grow: 2;
}
<div id='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" id="last"></div>
</div>
If it is important to use grid it can be done with a small javascript function. Find out how many columns the grid has and in which column the item starts and set its gridColumnStart and gridColumnEnd so that it uses all the remaining grid areas. For beeing responsive you have to add an event listener that reacts on resize and calls the expand function.
Working example:
const container = document.querySelector('#container');
const last_item = document.querySelector('#last');
function expandLastItem() {
const container_style = window.getComputedStyle(container);
const item_style = window.getComputedStyle(last_item);
const offset_left = last_item.offsetLeft;
const grid_width = parseInt(container_style.width);
const item_width = parseInt(item_style.width);
const row_length = Math.floor(grid_width / item_width);
const grid_column = Math.floor(offset_left / item_width) + 1;
last_item.style.gridColumnStart = grid_column;
last_item.style.gridColumnEnd = row_length + 1;
}
window.addEventListener('resize', function() {
last_item.style.gridColumnStart = '';
last_item.style.gridColumnEnd = '';
expandLastItem();
});
expandLastItem();
#container {
overflow: hidden;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.item {
height: 120px;
min-width: 120px;
background-color: red;
border: 2px solid yellow;
}
<div id='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" id="last"></div>
</div>
If you are using jQuery the code gets a bit simpler.
Working example:
function expandLastItem() {
const offset_left = $('#last').offset().left;
const grid_width = parseInt($('#container').width());
const item_width = parseInt($('#last').width());
const row_length = Math.floor(grid_width / item_width);
const grid_column = Math.floor(offset_left / item_width) + 1;
$('#last')[0].style.gridColumnStart = grid_column;
$('#last')[0].style.gridColumnEnd = row_length + 1;
}
$(window).on('resize', function() {
$('#last')[0].style.gridColumnStart = '';
$('#last')[0].style.gridColumnEnd = '';
expandLastItem();
});
expandLastItem();
#container {
overflow: hidden;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.item {
height: 120px;
min-width: 120px;
background-color: red;
border: 2px solid yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='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" id="last"></div>
</div>

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>

Flexbox items with min width and adaptive to content

I want to make flexbox with responsive to content items.
Here is example code:
HTML:
.container {
width: 200px;
height: 400px;
display: flex;
flex-flow: row wrap;
}
.item {
flex-basis: 40px;
background-color: orange;
margin: 0 10px 10px 0;
}
<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">this is text content and again and again this is text content and again and again</div>
</div>
And this is what I want to achieve:
How can I achieve that behavior? Thanks!)
If you want your page to be responsive to different resolution then u shouldn't adopt the habit of defining height and width in terms of px.
instead try to put width and height interms of % , width defined in % tend to adjust with resolution but width defined in terms of px tend to remain static and insensitive to changing resolution.
try this instead,
.container {
width: 800px;
height: 400px;
display: flex;
flex-flow: row wrap;
}
.item {
flex-basis: 25%;
background-color: orange;
margin: 0 10px 10px 0;
height: 50%;
}
.items {
flex-basis: 51%;
background-color: orange;
margin: 0 10px 10px 0;
height: 50%;
}
<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="items">this is text content and again and again this is text content and again and again</div>
<div class="item"></div>
</div>
This Works fine. All the best
Any reason you have a strong attachment to flexbox? This would be super easy using the bootstrap grid. (I see you have the container class, so you likely already have bootstrap installed)
<div class="container">
<div class="item col-md-3"></div>
<div class="item col-md-3 col-md-offset-1"></div>
<div class="item col-md-3 col-md-offset-1"></div>
<div class="item col-md-3"></div>
<div class="item col-md-3 col-md-offset-1"></div>
<div class="item col-md-3 col-md-offset-1"></div>
<div class="item col-md-6">this is text content and again and again this is text content and again and again</div>
<div class="item col-md-3 col-md-offset-1"></div>
</div>
If you're not into using bootstrap, CSS3 now has a native grid layout which should also suit your purpose.
In your situation, I think this would be the way to go. Flexbox is a great layout, but it's meant more for dynamic displays where you don't need precise control over the size/dimension of the items.
You can add a class, let's say extra.
And add assign property of max-width:max-content; to it.
.container {
display: flex;
width: 300px;
height: 300px;
flex-wrap: wrap;
border: 2px solid red;
}
.item {
flex: 1;
min-width: calc(100px - 10px);
max-width: calc(100px - 10px);
height: calc(100px - 10px);
margin: 5px;
background: green;
}
.extra {
max-width: max-content !important;
max-width: -moz-fit-content !important;
color: white;
}
<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 extra">this is extra text and expands</div>
<div class="item"></div>
</div>

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>

How to span two columns on a 3x3 grid with flexbox with paddings?

.page {
width: 90% margin: auto;
}
.row-container {
width: 100%;
display: flex;
}
.item-container {
flex: 1;
padding: 16px;
}
.item-container-2x {
flex: 2;
}
.item {
background-color: #e7e8e9;
height: 50px;
border: 1px solid #dddddd;
}
<div class="page">
<div class="row-container">
<div class="item-container">
<div class="item"></div>
</div>
<div class="item-container">
<div class="item"></div>
</div>
<div class="item-container">
<div class="item"></div>
</div>
</div>
<div class="row-container">
<div class="item-container item-container-2x">
<div class="item"></div>
</div>
<div class="item-container">
<div class="item"></div>
</div>
</div>
</div>
The issue here is with the paddings. It causes the grid on the second row to be misaligned.
I'm using flex: 1; for the equal width grid items, and flex: 2; next to one with flex: 1; for the second row. My understanding is that the flex numbers add up. I'm trying to add them up to 3, but in the second row, having only one margin between the two grid items is impacting to spacing. I'm not sure if this approach is better than defining variable widths for the gird items, but using flex seems simple to me.
I think there's also a problem that I'm using fixed paddings of 16px with the variable width withflex: 1/2. I did try percentage margin and still had the same problem. And I'm having a hard time getting my head around needing to use a combination of padding and margin, and maybe even negative margin.
Thanks for any help.
When using padding on a flex item, Flexbox has a somewhat more complicated way of calculating the space left, which makes it a little more tricky to make that work.
In this case, and to keep using flex-grow for sizing, using margin on the flex item's child (item) would be simpler. It will give the same output as padding does on a parent, with a non-flex child.
Stack snippet
.page {
width: 90%;
margin: auto;
}
.row-container {
width: 100%;
display: flex;
}
.item-container {
flex: 1;
}
.item-container-2x {
flex: 2;
}
.item {
background-color: #e7e8e9;
height: 50px;
border: 1px solid #dddddd;
margin: 16px; /* moved from ".item-container" and changed to "margin" */
}
<div class="page">
<div class="row-container">
<div class="item-container">
<div class="item"></div>
</div>
<div class="item-container">
<div class="item"></div>
</div>
<div class="item-container">
<div class="item"></div>
</div>
</div>
<div class="row-container">
<div class="item-container item-container-2x">
<div class="item"></div>
</div>
<div class="item-container">
<div class="item"></div>
</div>
</div>
</div>
If you need, or have, to use padding on the flex items, you need to compensate for the removed item's padding (16px on each side), and add it as an initial flex-basis on the spanned item.
Stack snippet
.page {
width: 90%;
margin: auto;
}
.row-container {
width: 100%;
display: flex;
}
.item-container {
flex: 1;
padding: 16px;
}
.item-container-2x {
flex: 2 32px; /* changed */
}
.item {
background-color: #e7e8e9;
height: 50px;
border: 1px solid #dddddd;
}
<div class="page">
<div class="row-container">
<div class="item-container">
<div class="item"></div>
</div>
<div class="item-container">
<div class="item"></div>
</div>
<div class="item-container">
<div class="item"></div>
</div>
</div>
<div class="row-container">
<div class="item-container item-container-2x">
<div class="item"></div>
</div>
<div class="item-container">
<div class="item"></div>
</div>
</div>
</div>
In another answer of mine, I made a flex box table version, which might help you further. Check out the "Stack snippet - Flexbox" sample at this link:
Layout a flex box similar to a table?
As a side note, and since you mentioned trying with percent value, that is gonna give you some more issues, which you can read more about here:
Why doesn't percentage padding / margin work on flex items in Firefox?
And here is some more reading about sizing items with flex-grow:
flex-grow not sizing flex items as expected