I have a number of tiles, all the same, static width and height inside some container. The width of the container is dynamic. The number of tiles is dynamic. I need the tiles to "wrap" such that as many as possible fit on each row but a new row is created when the number of tiles fills up the width of the container. A full row of tiles should be centered. A row of tiles that is not full should be left aligned with a full row (or positioned as if it was full and centered). See the diagram below.
In this diagram, the green represents the container, the black represents the tiles.
I can get close by making the tiles inline-block and using text-align: center on the container. However, that comes out like the following instead:
Here's a code sample. I am able to make necessary changes to the HTML.
.container {
border: 5px solid #0f0;
width: 250px;
text-align: center;
}
.tile {
margin: 3px;
display: inline-block;
border: 5px solid #000;
width: 50px;
height: 40px;
}
<div class="container">
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
</div>
TL;DR
There is a way to do it with the CSS Grid Layout. One has to put the following properties to the parent element, which is the .container element in this scenario:
display: grid;
grid-template-columns: repeat(auto-fill, 60px);
grid-gap: 10px;
justify-content: center;
Some explanation and details
I've tried a lot to make this work, but in the end I was only successful with the CSS Grid Layout which I used for the first time here. I bet there are more readers that aren't very familiar with the above properties so I will add one or two lines of explanation for each.
display: grid;
There's not much to say about this, apart from the limitations in browser support.
grid-template-columns: repeat(auto-fill, 60px);
The grid-template-columns CSS property defines the line names and track sizing functions of the grid columns.
This property is a bit more complicated as there are many different options for its value. Here we use the repeat() function which requires the number of columns as first argument and the width of each column cell as second argument. Fortunately, it's possible to define a variable column number by using auto-fill. The width of 60px is the width of the .tile (50px for the width and 10px for the border).
grid-gap: 10px;
The grid-gap CSS property is a shorthand property for grid-row-gap and grid-column-gap specifying the gutters between grid rows and columns.
To define different gap widths for columns and rows you can use grid-gap: 5px 10px; where the first value is for the row-gap. I randomly selected 10px for this example.
justify-content: center;
Finally let's tell the browser to center the content of our container (the grid) if there is space around. This property is not related to the CSS Grid Layout. It is usually used in flexbox scenarios but works like a charm here as well.
Demo
I added the described four properties to the .container and removed the margin as well as the width from the .tile. Solution tested in Firefox 52 and Chrome 57.
.container {
border: 5px solid #0f0;
width: 250px;
display: grid;
grid-template-columns: repeat(auto-fill, 60px);
grid-gap: 10px;
justify-content: center;
}
.tile {
display: inline-block;
border: 5px solid #000;
height: 40px;
}
<div class="container">
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
</div>
Nice to know
In the FF developer console (F12) there is a built-in grid highlighter that allows easy debugging of the defined grid:
You could use flexbox, but if the last row doesn't have enough elements you'll have a similar issue as with inline-block.
You can use :before and :after to fill out the last row, but if more than 2 elements are missing from the last row it won't help entirely.
div {
display: flex;
align-items: stretch;
justify-content: center;
flex-wrap: wrap;
}
div > * {
margin: 1rem;
}
div::before,
div::after {
background: red; /* For demo only */
display: block;
content: "";
width: 200px;
height: 200px;
margin: 1rem;
order: 99999999;
}
<div>
<img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200"><img src="http://placehold.it/200x200">
</div>
Edit: Looking at my own example I realise this doesn't solve your problem. I'll delete this post unless it helps you somehow anyway.
Edit2: You may actually have to resolve to adding some empty divs using JS to fill out the void. That should work though I'd definitely consider it an ugly hack.
Can you please try the below,
.container {
border: 5px solid #0f0;
width: 250px;
}
.tile {
margin: 5px 10px;
display: inline-block;
border: 5px solid #000;
width: 50px;
height: 40px;
text-align: center;
}
<div class="container">
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
</div>
All I did is removed the text-align from .container class and moved it to .tile class. Added a margin to space the elements. You can adjust the 5px 10px based on your needs. Am able to match the output to yours. Please let me know if it works.
Related
I have a simple flex element with some children, something like this:
.container{
display: flex;
flex-wrap: wrap;
}
.container div.flex-box{
width: 200px;
margin: 20px;
padding: 20px;
font-size: 40px;
border: 1px solid;
}
<div class='container'>
<div class='flex-box'>one</div>
<div class='flex-box'>two</div>
<div class='flex-box'>three</div>
<div class='flex-box'>four</div>
<div class='flex-box'>five</div>
<div class='flex-box'>six</div>
</div>
I am using flex wrap so, when screen goes smaller they stack.
Now, I want to reload fourth and fifth element using an ajax call to reload both elements, for this I need to put both children inside a container, but when I do, it becomes a new flex child
.container{
display: flex;
flex-wrap: wrap;
}
.container div.flex-box{
width: 200px;
margin: 20px;
padding: 20px;
font-size: 40px;
border: 1px solid;
}
<div class='container'>
<div class='flex-box'>one</div>
<div class='flex-box'>two</div>
<div class='flex-box'>three</div>
<div class='subcontainer'>
<div class='flex-box'>four</div>
<div class='flex-box'>five</div>
</div>
<div class='flex-box'>six</div>
</div>
I am looking for a way to ingore this "subcontainer", and keep the children working as before.
Is this possible?
Use display:contents (https://css-tricks.com/get-ready-for-display-contents/) but pay attention to the support (https://caniuse.com/#feat=css-display-contents)
.container {
display: flex;
flex-wrap: wrap;
}
.container div.flex-box {
width: 200px;
margin: 20px;
padding: 20px;
font-size: 40px;
border: 1px solid;
}
.subcontainer {
display: contents
}
<div class='container'>
<div class='flex-box'>one</div>
<div class='flex-box'>two</div>
<div class='flex-box'>three</div>
<div class='subcontainer'>
<div class='flex-box'>four</div>
<div class='flex-box'>five</div>
</div>
<div class='flex-box'>six</div>
</div>
If the element is a child of a flex container, then it becomes a flex item. That's the general rule.
However, most browsers will ignore that rule when the child is absolutely positioned.
I'm not sure that's a useful solution in this case, but that's what you would have to do: absolutely position .subcontainer and make it flex container, so that the children become flex items.
I have a row of items of arbitrary width. They are centered within the container (note white space on the left and right sides of the red container):
Sometimes the container gets smaller than the width of all items:
When this happens, I want the items in the end to wrap to the next row like this:
It is very imporant for me that each row's content must be left-aligned, but the grid as a whole must be centered:
Initially, I tried implementing it with FlexBox. After a lot of frustration and hair pulling, I've learned that this is impossible witn FlexBox: https://stackoverflow.com/a/32811002/901944
Another answer on the same page suggests using CSS grid instead of flexbox.
CSS grid produces a slightly different result, but that also suits me:
Here's the code that makes it work:
.red-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(210px, max-content));
justify-content: center;
}
This code contains a lot of keywords that I don't understand: grid-template-columns, repeat, auto-fit, minmax and max-content. I tried reading up on them and failed. None of guides and API docs explicitly explain how this particualr combination works. MDN docs are way too short and cryptic.
What I specifically struggle with is this 210px magic number. Why is it necessary? (Erm, I know it's necessary because how the spec is designed, but this does not help me understand.)
The sizes of items in my grid are arbitrary, so I can't use a fixed value. Also, setting this fixed value makes the result slightly off: small items grow and large items overflow the container.
What I essentially want is:
grid-template-columns: repeat(auto-fit, minmax(min-content, max-content));
but that rule is recognized by browsers as faulty.
I've stumbled upon this answer that explains that using both min-content and max-content together is forbidden by the spec in this context. The answer's suggested solution is... to use Flexbox!
The loop has closed. I'm back to where I started, expect that I'm now lacking hair on my head for another round.
How to do I center my grid while left-aligning each row's content, with items having arbitrary widths?
Here's a boilerplate to fiddle with for your convenience: https://jsbin.com/vuguhoj/edit?html,css,output
The container can be resized by dragging it by the bottom-right corner.
PS No display: inline and float: left please.
.page {
border: 1px solid black;
overflow: hidden;
resize: horizontal;
max-width: 500px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(max-content, 50px));
justify-content: center;
}
.item {
border: 1px solid black;
margin: 1px;
}
<div class="page">
<div class="grid">
<div class="item">
Foofoofoo
</div>
<div class="item">
Bar
</div>
<div class="item">
BazBaz
</div>
<div class="item">
QuuxQuuxQuux
</div>
</div>
</div>
CSS grid approach-
root answer - joe82
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(179px, max-content));
grid-gap: 10px;
justify-content: center;
background: #999;
overflow: hidden;
padding: 10px;
}
.background {
width: 179px;
height: 64px;
background: #99d9ea;
}
.background .child {
border: 2px solid #000;
}
.background:nth-child(1) .child {
width: 110px;
height: 50px;
}
.background:nth-child(2) .child {
width: 120px;
height: 60px;
}
.background:nth-child(3) .child {
width: 50px;
height: 55px;
}
.background:nth-child(4) .child {
width: 175px;
height: 40px;
}
<div class="container">
<div class="background"><div class="child"></div></div>
<div class="background"><div class="child"></div></div>
<div class="background"><div class="child"></div></div>
<div class="background"><div class="child"></div></div>
</div>
Newbie here!
I admit that the above one isn't the output you desired, but it is my best attempt towards it, with the use of CSS grids. Here, you can understand that if we want to make it responsive then a minimum width is required after which, the column(content) will get carried to the next line, and that width is defined here(grid-template-columns: repeat(auto-fit, minmax(204px, max-content));) 204px. But because of it each column will take that much width at least, that's why I represented the actual dimension of a column with blue background and the actual content within the border.I just post it for your acknowledgment and approach so that you can get closer to the actual answer.
By the way, Flex Approach-
root idea - random COSMOS
.container {
min-width: 130px;
max-width: 340px;
overflow: auto;
background: #999;
padding: 10px 40px;
resize: horizontal;
border: 1px solid #000;
}
.main-content {
display: flex;
flex-wrap: wrap;
background: #fff;
max-width: 340px;
overflow: hidden;
}
.child {
margin: 5px;
padding: 5px;
background: #99d9ea;
border: 1px solid black;
}
<div class="container">
<div class="main-content">
<div class="child">Foofoofoo</div>
<div class="child">Bar</div>
<div class="child">BazBaz</div>
<div class="child">QuuxQuuxQuux</div>
</div>
</div>
Resize the window or the above div to see the results
The above tells that the div is centered and the content is at left but not resizing according to content.
My personal opinion -
You should use #media for making it responsize, just as the way you want it to be, It is just like coding a lot for a simple output but it can give you the best and satisfying results out of your hard work and time!
Kindly inform me if you want me to make it responsize for you, I mean just like a demo-
Regard,
Om Chaudhary
If we go for center we don't get the left-align thing:
.page {
border: 1px solid black;
max-width: 500px;
resize: horizontal;
overflow: hidden;
display: flex;
justify-content: center;
padding: 0 50px;
}
.grid {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.item {
border: 1px solid black;
margin: 1px;
}
<div class="page">
<div class="grid">
<div class="item">Foofoofoo</div>
<div class="item">Bar</div>
<div class="item">BazBaz</div>
<div class="item">QuuxQuuxQuux</div>
</div>
</div>
Now with left-align (as in #Michael Benjamin answer) it doesn't do the center stuff:
.page {
border: 1px solid black;
max-width: 500px;
resize: horizontal;
overflow: hidden;
display: flex;
justify-content: center;
padding: 0 50px;
}
.grid {
display: flex;
flex-wrap: wrap;
}
.item {
border: 1px solid black;
margin: 1px;
}
<div class="page">
<div class="grid">
<div class="item">Foofoofoo</div>
<div class="item">Bar</div>
<div class="item">BazBaz</div>
<div class="item">QuuxQuuxQuux</div>
</div>
</div>
Why?
Because in the second code and in #Michael Benjamin code the .grid is technically centered. Visual:
See the whole div is centered. The problem is that the .grid div doesn't change its width according to the content inside it. But, according to the width of its parent div (.page).
I know it doesnt solve your problem, but I am just trying to make sure that now you understand the main problem. So, maybe you can find the solution in another way.
For this case, I see the only solution is to use JavaScript.
In this code we get each .item width. Then we set .flex width = total .item width. If the total .item width is smaller then the .page width - we set .flex width = ((total .item width) - (last .item width)). I hope the JS is pretty readable. If you need more explanation - can give it in comments.
Note that this is not wary flexible and universal solution, But it works good in this particular case, because it was written for it.
This snippet you can test only on browser window size change. Better to check in using Snippet Full page and Chrome console with Devise toolbar mode. Or here https://jsfiddle.net/focusstyle/sh6dnLvt/1/
window.addEventListener("resize", function() {
flexWidth();
});
function flexWidth() {
let page = document.querySelector('.page');
let flex = document.querySelector('.flex');
let totalWidth = biggesWidth = itemWidth = lastWidth = 0;
let flexItem = document.querySelectorAll('.item');
for (let i = 0; i < flexItem.length; i += 1) {
itemWidth = flexItem[i].offsetWidth;
if (biggesWidth<itemWidth) {
biggesWidth = itemWidth;
}
biggesWidth = flexItem[i].offsetWidth;
totalWidth += itemWidth;
lastWidth = itemWidth;
}
if (totalWidth > page.clientWidth) {
totalWidth = totalWidth - lastWidth;
}
totalWidth += 1;
flex.style.cssText = "min-width: "+biggesWidth+"px; max-width: "+totalWidth+"px;";
}
.page {
border: 1px solid black;
overflow: hidden;
text-align: center;
}
.flex {
display: inline-flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.item {
border: 1px solid black;
}
<div class="page">
<div class="flex">
<div class="item">
Foofoofoo
</div>
<div class="item">
Bar
</div>
<div class="item">
BazBaz
</div>
<div class="item">
QuuxQuuxQuux
</div>
</div>
</div>
Let's understand the 210px first. When you write the below code:
grid-template-columns: repeat(auto-fit, minmax(210px, max-content));
The browser knows when to wrap the items around. It ensures that your grid items will always be wider than 210px or at least equal to 210px.
If the browser has 420px width available, it will put 2 items in a row.
If the browser has 630px width available, it will put 3 items in a row, and so on...
You can learn about CSS grids here
If you still don't want to have a min-content of 210px, you can always wite media queries in CSS.
Another thing that may suit your requirements is giving a min-width and max-width to your grid-items.
Hopefully, it saves some of your hair.
You have two containers available – .page and .grid.
This enables you to distribute the two tasks – centering and left-alignment.
Use the top-level container for centering.
Use the nested container for wrapping and left alignment.
Here's a code concept:
.page {
display: grid;
grid-template-columns: 50px 1fr 50px;
border: 1px solid black;
max-width: 500px;
}
.grid {
grid-column: 2;
justify-self: center;
display: flex;
flex-wrap: wrap;
}
.item {
border: 1px solid black;
margin: 1px;
}
<div class="page">
<div class="grid">
<div class="item">Foofoofoo</div>
<div class="item">Bar</div>
<div class="item">BazBaz</div>
<div class="item">QuuxQuuxQuux</div>
</div>
</div>
jsFiddle demo
I don't know if I'm missing something but I think this is possible to achieve this with flexboxes.
You have to get this structure :
<div class="container">
<div class="row">
<div class="item item-1"></div>
<div class="item item-2"></div>
<div class="item item-3"></div>
<div class="item item-4"></div>
</div>
</div>
where the row has a max size and margin: 0 auto (to be centered)
Here is my example on Codepen
I'm trying to display a list of flex items with fixed width in the center of flexbox with wrap by using margin: auto. When wrap happens, the wrapped item also centers in its own container:
Is there a way to keep the wrapped item on the left while everything else is centred?
.parent {
display: flex;
flex-wrap: wrap;
height: 100px;
}
.children {
flex: 1;
border: 1px solid black;
max-width: 300px;
min-width: 300px;
margin: auto;
height: 100px;
margin-top: 1rem;
}
<div class="parent">
<div class="children"></div>
<div class="children"></div>
<div class="children"></div>
<div class="children"></div>
<div class="children"></div>
</div>
You're saying you want the items centered, but when there is only one item that wraps, you want it left-aligned.
The problem is that there is really no left-alignment in the flex container. Everything is centered, based on available space in the row. So the single item in the last row has no concept of what's going on above, and nothing to align with.
Here's what happens if you left-align the last item (on wider screens):
.parent {
display: flex;
flex-wrap: wrap;
}
.children {
border: 1px solid black;
max-width: 300px;
min-width: 300px;
margin: auto;
height: 100px;
margin-top: 1rem;
}
.children:last-child {
margin-left: 0;
}
<div class="parent">
<div class="children"></div>
<div class="children"></div>
<div class="children"></div>
<div class="children"></div>
<div class="children"></div>
</div>
What you need is a nested grid structure.
A top-level grid to establish the centering.
A nested grid for the wrapping.
In the demo below, you'll find a three-column grid. The left and right columns are empty (spacer) items, created with CSS pseudo-elements, and set to equal widths. This centers the middle item.
The middle item is also a grid container. Using the repeat() and auto-fill functions, the items can wrap, with individual items aligning left.
body {
display: grid;
grid-template-columns: auto 1fr auto;
}
body::before,
body::after {
content: ''; /* in grid (and flex) layouts, pseudo-elements on the container
are treated as items */
}
.parent {
display: grid;
grid-template-columns: repeat(auto-fill, 300px);
grid-auto-rows: 100px;
grid-gap: 10px;
justify-content: center; /* centers the columns (not the items, like in flex) */
}
.children {
border: 1px solid black;
}
<div class="parent">
<div class="children"></div>
<div class="children"></div>
<div class="children"></div>
<div class="children"></div>
<div class="children"></div>
</div>
jsFiddle demo
I have a flexbox that is basically a "list of sub divs". The box' main feature is the automated wrapping. However I notice that, due to children's wrapping value, there is also an offset to the left of the box.
I wish to remove this. I've tried doing this by giving the container a negative padding: however that seems to be disallowed.
Then I tried to find a selector for the "first child on each line", but that also failed. Is there a way to do this in css?
Alternatively, an a grid display: grid be made to have variable width columns, and automatically calculate the number of columns based on the content (thus: wrapping).
Example below:
.cont {
display: flex;
flex-wrap: wrap;
width: 100%;
position: relative;
border: solid 1px red;
padding-left: -16px;
justify-content: 'center';
}
.cell {
min-width: 20%;
border: solid 1px black;
margin: 8px 16px 8px 0;
}
.wide2 {
min-width: calc(50% - 2px);
}
.wide {
width: calc(50% - 16px);
}
<div class="cont">
<div class="cell wide">
wide
</div>
<div class="cell">
test
</div>
<div class="cell">
test
</div>
<div class="cell">
test second line, should also have zero margin to left
</div>
</div>
<div class="cont">
<div class="cell wide">
wide
</div>
<div class="cell wide2">
this should line up,<br> iff margin right is no longer there
</div>
</div>
I believe this is currently not possible, and probably won't ever be.
You see, if you can have that selector, you'll be able to implement this:
.flexbox {
display: flex;
flex-wrap: wrap;
}
.flexbox .flex-item {
margin-left: 200px;
}
.flexbox .flex-item:first-of-flex-line {
margin-left: 0;
}
Notice that when margin-left: 0 of :first-of-flex-line cancels the margin-left: 20px rule, it can possibly make some items of the next line move up, almost like infinite loop. This would require iterative layouting, which seems unlikely to be implemented in browser as it is expensive rendering.
I am currently using CSS grid, and I'm using the grid-row-gap and column-row-gap many places. My problem is that when doing media queries, and some elements/divs are not to be shown at smaller resolution I usually just set them to display: none. However, if I'm not mistaken, even though the element can be seen, the row or column gap feature still applies on the "hidden" element.
Is there any way to fix this, or am I doing something wrong here?
EDIT:
Okay, it seems like display: none don't affect the grid-row-gap - which makes sense. But I found a small workaround by removing the grid-gap and adding a padding instead when the resolution changes. And that seems to do the trick.
Thanks for all the answers.
Is there any way to fix this [...]?
One simple fix when applying display: none; to child elements in a CSS grid, is to remember to alter the parent grid accordingly to take account of the fact that those child elements are no longer displayed.
Working Example:
body > div {
display: inline-grid;
grid-template-rows: auto auto auto;
grid-template-columns: auto auto auto;
grid-row-gap: 6px;
grid-column-gap: 12px;
width: 120px;
height: 120px;
margin-right: 24px;
border: 1px solid rgb(255, 0, 0);
}
div div {
border: 1px solid rgb(255, 0, 0);
}
body > div:hover div:nth-of-type(n+7) {
display: none;
}
body > div:nth-of-type(2):hover {
grid-template-rows: auto auto;
height: 80px;
}
<div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
In both the CSS Grids above the final three child elements are removed from display on :hover. But in the second grid (only) the height of the parent and the number of grid-template-rows are also reduced, thus avoiding displaying a gap which the non-displayed child elements used to occupy.
You can do this if you use grid-auto-columns (or grid-auto-rows) because the column definitions are dynamic, therefore no column exists if the item is hidden.
In this example I'm using grid-auto-flow: column which is sort of similar to flex-direction: row in this example. It's easier to visualize what's going on if they are columns vs. rows.
Note: grid-auto-flow is supported in IE, but *-auto-columns is.
Important: Don't confuse this with grid-template-columns: auto !
.outer-container
{
background: yellow;
outline: 1px solid black;
}
.container
{
display: grid;
grid-auto-columns: 1fr;
grid-auto-flow: column;
grid-gap: 10px;
}
.item
{
background: steelblue;
padding: .5em 1em;
color: white;
}
.item.hidden
{
display: none;
}
<div class="outer-container">
<div class="container">
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item hidden">Item 3 (hidden)</div>
</div>
</div>