How to create a variable width squares using css grid layout? - html

Is there a way to have grid cells whose width is based on fr units that dynamically adjust their height so that they stay square?
Also, I was hoping to do this without JS
The below fiddle has some example code. The divs with the class 'sqaure' are the ones which I want to dynamically adjust their height to match their width (which is 1fr so it changes)
https://jsfiddle.net/bpk0sLvL/403/
.holder {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 4px;
row-gap: 4px;
}
.box {
background-color: #ccc;
}
.wide {
grid-column-start: 1;
grid-column-end: 3;
}
<div class="holder">
<div class="box wide">
<p>This can be any height</p>
<p>All these divs are sized based on fr, so I want to have the two square divs stay square as they dynamically resize </p>
</div>
<div class="box sqaure">
This needs to be a sqaure
</div>
<div class="box sqaure">
This needs to be a square as well
</div>
</div>

You need to:
identify the width of .square; and
ensure that the height of .square is equal to that width.
You can identify the width of .square with one line of javascript:
var squareWidth = document.getElementsByClassName('square')[0].offsetWidth;
You can ensure the height of .square is equal to that width with two lines of javascript:
var holder = document.getElementsByClassName('holder')[0];
holder.style.gridTemplateRows = 'auto ' + squareWidth + 'px';
Working Example:
function calculateSquareHeight() {
var holder = document.getElementsByClassName('holder')[0];
var squareWidth = document.getElementsByClassName('square')[0].offsetWidth;
holder.style.gridTemplateRows = 'auto ' + squareWidth + 'px';
}
window.addEventListener('load', calculateSquareHeight, false);
window.addEventListener('resize', calculateSquareHeight, false);
.holder {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 4px;
grid-row-gap: 4px;
}
.box {
background-color: #ccc;
}
.wide {
grid-column-start: 1;
grid-column-end: 3;
}
<div class="holder">
<div class="box wide">
<p>This can be any height</p>
<p>All these divs are sized based on fr, so I want to have the two square divs stay square as they dynamically resize </p>
</div>
<div class="box square">
This needs to be a square
</div>
<div class="box square">
This needs to be a square as well
</div>
</div>

Related

CSS Grid - Stretch dynamic number of colums to fit row width

Is it possible to use CSS grid to auto-fit the columns in a row to always take up the whole width?
I know this would be possible if you knew the number of columns, but is it possible with a dynamic number of columns?
Image for reference of what I'd like to achieve.
column example image
This is what I have so far, but you can see that the lower row item doesn't take up all the row width.
.wrapper {
display: grid;
grid-template-columns: 200px 200px;
column-gap: 20px;
}
.grid {
border: solid #FF8181 1px;
display: grid;
grid-template-rows: 40px repeat(8, minmax(0, 1fr));
width: 200px;
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}
.row-item {
background: #FFC555;
border: 1px solid #835600;
width: 100%;
height: 40px;
}
.item-1, .item-1 {
grid-row: 2 / span 1;
}
.item-2 {
grid-row: 6 / span 1;
font-size: 12px;
}
<div class='wrapper'>
<div class='grid'>
<div class='row-item item-1'></div>
<div class='row-item item-1'></div>
<div class='row-item item-2'>I'm too short</div>
</div>
<div class='grid'>
<div class='row-item item-1'></div>
<div class='row-item item-1'></div>
<div class='row-item item-1'></div>
<div class='row-item item-2'>Should be the whole width</div>
</div>
</div>
you can stretch a element in a grid over the whole width by using:
grid-column: 1 / all;
or
grid-column: 1 / -1;
unfortunately does it affect the other elements in the same grid.
A solution like "span last-column" doesnt exist yet, but is already discussed: https://github.com/w3c/csswg-drafts/issues/2402
Maybe they will implement the function soon. Good luck anyways
What I need doesn't exist yet. This does indeed look like the latest update:
A solution like "span last-column" doesnt exist yet, but is already
discussed: https://github.com/w3c/csswg-drafts/issues/2402
I calculated overlapping grid items and rendered items in subgrids based on this answer:
Group multiple overlapping timeblocks that may not have a direct overlap
With js its different.
Find the grid
Get the Style of the Grid
Filter the Style of the Grid for grid-template-columns
Set the Element grid-column to the number of columns
Your code will look like this:
window.addEventListener("DOMContentLoaded", function(){
// find the elemen by the class and safe it as grid
let grid = document.querySelector(".grid")
// sage the style of the element
const gridComputedStyle = window.getComputedStyle(grid);
// get the grid-template-columns style poperty of the element and format them to a useful value
const gridColumnCount = gridComputedStyle.getPropertyValue("grid-template-columns").split(" ").length
// log for debug
console.log(gridColumnCount)
// set the column style of the element to the span of the variable
document.getElementById("item-2").style.gridColumn = "1 / span " + gridColumnCount;
// set the row style because it got overwritten by the line above
document.getElementById("item-2").style.gridRow = "6 / span " + gridColumnCount;
})
note that i have changed your html and css too:
html:
<div class='grid'>
<div class='row-item item-1'></div>
<div class='row-item item-1'></div>
<div class='row-item item-1'></div>
<div class="row-item" id="item-2">whole width</div>
</div>
Just deleted the wrapper for a better overview
CSS:
.grid {
border: solid #FF8181 1px;
display: grid;
grid-template-rows: repeat(8, minmax(0, 1fr));
width: 300px;
grid-template-columns: repeat(auto, minmax(0, 1fr));
}
.row-item {
background: #FFC555;
border: 1px solid #835600;
width: 100%;
height: 40px;
}
.item-1, .item-1 {
grid-row: 2;
}
.item-2 {
grid-row: 5;
font-size: 12px;
}
I had to change the grid-template-value of grid from auto-fit to auto. Otherwise there would be a bug when counting the columns

How to vertically align grid items in auto rows with row span?

I used a CSS Grid Layout to display data in a table-like manner with the last column spanning multiple rows. While this basically works now , I absolutely cannot get the vertical alignment right. See the following snippet:
div {
border: 1px solid black;
padding: 3px;
}
.table {
display: grid;
grid-template-columns: min-content min-content min-content;
white-space: nowrap;
grid-auto-rows: auto;
grid-gap: 3px;
width: min-content;
grid-auto-flow: row dense;
}
.col1 {
grid-column-start: 1;
height: min-content;
}
.col2 {
grid-column-start: 2;
height: min-content;
}
.col3 {
grid-column-start: 3;
grid-row-end: span 2;
}
<div class="table">
<div class="col1">A1</div>
<div class="col2">B1</div>
<div class="col1">A2</div>
<div class="col2">B2</div>
<div class="col3">this<br>is<br>the first<br>very<br>very<br>long<br>cell</div>
<div class="col1">A3</div>
<div class="col2">B3</div>
<div class="col1">A4</div>
<div class="col2">B4</div>
<div class="col3">second cell</div>
</div>
I want the left two columns to always the minimum height and be aligned with the top of each multi-row cell. In this simplified example, that means that A2 and B2 should move up and be shown right under A1 and B1.
I also tried adding a dummy <div> before each col3 and make it take all extra height. But no matter what I tried, the height is always distributed equally among all rows.
Is there a way to make the cells top-aligned with the multi-row cell?

CSS - Ensuring the first two rows of my CSS grid is equal to the remaining height of the screen (excluding header)

I am wondering if the first two rows of my CSS grid can be equal to the height of the screen excluding the header. The content container below the header has been set to overflow: auto, but I would want only my first two rows of the grid to be equal to the height of the content container (without overflowing). My grid has a total of 3 rows, and basically, I want the first two rows to take up the entire height of the container without overflowing, followed by the third row being completely invisible to the screen unless the container is scrolled down. Do let me know if my explanation is confusing, as I am new to CSS grid, and not too sure if my explanation has been sufficient. Thanks all, and I look forward to your replies!
Here's a sample of my HTML layout
<div class='entire'>
<div class='header'>Header</div>
<div class='content'>
<div class='grid'>
item 1
item 2
item 3
</div>
</div>
</div>
CSS
.entire {
height: 100vh;
}
.content {
overflow: auto;
}
.grid {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: ___ ____ ____;
}
The height of .header should be defined in CSS. If not - it should be get by JS. Let's agree with definition in CSS.
Taking into account your last comment about 1st and 2nd row which should be both fit into one screen, 1st should take auto height and 2nd - all the rest height, I don't see any evidence to include the block 3rd into the grid at all. Moved it away.
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
.entire {
height: 100vh;
}
.header {
background: cyan;
height: 20px;
}
.content {
overflow: auto;
}
.grid {
/* subtracting known height of .header */
height: calc(100vh - 20px);
display: grid;
grid-template-columns: 1fr;
/* this will make 1st row auto height and 2nd - all the rest */
grid-template-rows: auto 1fr;
}
.item1 {
background: #eee;
}
.item2 {
background: #ddd;
}
.item3 {
background: #ccc;
height: 100vh;
}
<div class="entire">
<div class="header">Header</div>
<div class="content">
<div class="grid">
<div class="item1">item 1</div>
<div class="item2">item 2</div>
</div>
<div class="item3">item 3</div>
</div>
</div>

Expanding to fill remaining space in a CSS Grid layout

I want to use CSS grid and the following is a mock-up of the aim:
I'm building an interface that should expand rightward to fill the browser screen; my current code causes column 2 of the outer grid to be as wide as the browser in addition to column 1; or maybe one of it's children is causing this and it's just expanding to accommodate. Either way, it's spilling off the page horizontally
So the code:
#main {
width: 100%;
display: grid;
grid-template-columns: 250px 100%;
grid-template-rows: 100px 100%;
}
#col-2-outer {
display: grid;
grid-template-columns: 250px auto;
grid-template-rows: 100%;
}
#row-1-inner {
grid-column: span 2;
}
#col-2-inner table {
width: 100%;
}
<div id="main">
<div id="col-1-outer"></div>
<div id="col-2-outer">
<div id="row-1-inner"></div>
<div id="row-2-inner">
<div id="col-1-inner"></div>
<div id="col-2-inner">
<table></table>
</div>
</div>
</div>
</div>
FYI, for the time being I've forgone template areas until I get a handle on the basics (unless this somehow solves my problem but I gather this is strictly a code organization feature?).
I'd suggest to change your markup with a 3x2 grid like below:
Remove the hierarchical structure like you have in your code and add one element for each section in the grid.
Note that in the rule grid-template-columns: 250px 150px auto, 250px is the width of your col-1-outer and 150px is the width of the col-1-inner.
Span the first column over the two rows by using grid-row: span 2
Span the first row in the second column by using grid-column: span 2.
Extend the table over the last grid item by using 100% width and height.
See demo below:
* {
border: 1px solid; /* For illustration */
}
#main {
width: 100%;
display: grid;
grid-template-columns: 250px 150px auto;
grid-template-rows: 100px auto;
}
#col-1-outer {
grid-row: span 2;
}
#row-1-inner {
grid-column: span 2;
}
#col-2-inner table {
width: 100%;
height: 100%;
}
<div id="main">
<div id="col-1-outer">col-1-outer</div>
<div id="row-1-inner">col2-row-1-inner</div>
<div id="col-1-inner">col2-row2-inner</div>
<div id="col-2-inner">
<table><tr><td>table</td></tr></table>
</div>
</div>
The 100% for the 2nd column in your grid-template-columns is based on the width of the container - rather than occupying the space outstanding within the container, it will push out to the right because the 2nd column is trying to match the width of the container.
Try changing this to auto and this should rectify the issue, as it will only take up the space up to the end of the container and no further.
Source: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns

How to prevent grid-row span from changing column placement?

I have a 3 X 3 CSS Grid.
I have a row in which I have three items A, B & C.
I want item C to have a rowspan of 2.
To do so, I am using grid-row: 1 / span 2;. It is taking two rows, but it's being placed in the first column instead of simply lying in the 3rd column. I don't know why this is happening.
I want item C to stay at the place where it is in the HTML.
One work around to this problem is to explicitly setting grid-column: 3 / span 1 which I don't want to do. I want items to be placed the way they are in HTML.
Is there any way to suppress this behavior?
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
}
h1 {
border: 2px solid;
padding: 0;
margin: 0;
font-size: 20px;
}
.a {
grid-row: 1 / span 2;
background: orange;
}
<div class="grid-container">
<div>
<h1>A</h1>
</div>
<div>
<h1>B</h1>
</div>
<div class="a">
<h1>C</h1>
</div>
</div>
Another way of solving it (That points to the reason why is stating a row for the other items):
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
}
h1 {
border: 2px solid;
padding: 0;
margin: 0;
font-size: 20px;
}
.a {
grid-row: 1 / span 2;
background: orange;
}
.b {
grid-row: 1;
}
<div class="grid-container">
<div class="b">
<h1>A</h1>
</div>
<div class="b">
<h1>B</h1>
</div>
<div class="a">
<h1>C</h1>
</div>
</div>
And the reason of this behaviour is that the more restrictive elements get positioned first. This way, the possibilities of the grid algorithm to achieve a solution are bigger.
That is, an element that has a requirement will be positioned first, elements that don't have a requirement last.
Steps 2 (for a item) and 4 (for the remaining items) in this part of the spec
If only one gets stock to a row number it will come first and stick there ahead in the flow. To avoid this, other grid items needs to be set to a defaut row as well.
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
}
div {
grid-row: 1;/* here is the basic fix but will set each item on first row */
}
h1 {
border: 2px solid;
padding: 0;
margin: 0;
font-size: 20px;
}
.a {
grid-row: 1 / span 2;
background: orange;
}
<div class="grid-container">
<div>
<h1>A</h1>
</div>
<div>
<h1>B</h1>
</div>
<div class="a">
<h1>C</h1>
</div>
</div>
Else you need also to tell in which grid-column it should stand
.a {
grid-row: 1 / span 2;
grid-column:3;
background: orange;
}
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
}
h1 {
border: 2px solid;
padding: 0;
margin: 0;
font-size: 20px;
}
.a {
grid-row: 1 / span 2;
grid-column:3;
background: orange;
}
<div class="grid-container">
<div>
<h1>A</h1>
</div>
<div>
<h1>B</h1>
</div>
<div class="a">
<h1>C</h1>
</div>
</div>
or let auto placement do its job while only setting how many rows to span, wich is here, in my own opinion, the most flexible way with a minimum of css rules/selector to set, too much grid kills grid :) , make it simple :
.a {
grid-row: span 2;
background: orange;
}
snippet with a few example letting the .aclass do its job without setting the column nor the row number where to stand, it will just be spanning where it stans in the flow
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
}
h1 {
border: 2px solid;
padding: 0;
margin: 0;
font-size: 20px;
}
.a {
grid-row: span 2;
background: orange;
}
<div class="grid-container">
<div>
<h1>A</h1>
</div>
<div>
<h1>B</h1>
</div>
<div class="a">
<h1>C</h1>
</div>
</div>
<div class="grid-container">
<div>
<h1>A</h1>
</div>
<div class="a">
<h1>B</h1>
</div>
<div>
<h1>C</h1>
</div>
</div>
<div class="grid-container">
<div>
<h1>A</h1>
</div>
<div>
<h1>B</h1>
</div>
<div>
<h1>C</h1>
</div>
<div>
<h1>D</h1>
</div>
<div>
<h1>E</h1>
</div>
<div class="a">
<h1>F</h1>
</div>
<div>
<h1>G</h1>
</div>
<div>
<h1>H</h1>
</div>
</div>
<hr/>
<div class="grid-container">
<div>
<h1>A</h1>
</div>
<div>
<h1>B</h1>
</div>
<div class="a">
<h1>C</h1>
</div>
<div>
<h1>D</h1>
</div>
<div>
<h1>E</h1>
</div>
<div>
<h1>F</h1>
</div>
<div>
<h1>G</h1>
</div>
<div>
<h1>H</h1>
</div>
</div>
Clearly, there's something in the spec that causes this behavior. I'm not yet sure what it is. (Update: see #Vals' answer for an explanation.)
However, here's a valid and simple solution:
Instead of:
.a {
grid-row: 1 / span 2;
}
Use:
.a {
grid-row-end: span 2;
}
From the spec:
9.3. Line-based Placement: the grid-row-start,
grid-column-start, grid-row-end, and grid-column-end
properties
The grid-row-start, grid-column-start, grid-row-end, and
grid-column-end properties determine a grid item’s size and location
within the grid by contributing a line, a span, or nothing (automatic)
to its grid placement, thereby specifying the inline-start,
block-start, inline-end, and block-end edges of its grid area.
...
For example, grid-column-end: span 2 indicates the second grid line
in the endward direction from the grid-column-start line.
Also, consider this single rule that gives you full control and makes it all work:
.a {
grid-area: 1 / 3 / 3 / 4;
}
jsFiddle
The grid-area shorthand property parses values in this order:
grid-row-start
grid-column-start
grid-row-end
grid-column-end
Note the counter-clockwise direction, which is the opposite of margin and padding.