I'm trying to prevent the sidebar height from exceeding the content height.
The "area" will contain an image of any height so its height is not fixed or known in advance.
function toggleTabsContent() {
const display = document.querySelector('.content').style.display;
if (display == "none") {
document.querySelector('.content').style.display = "block";
} else {
document.querySelector('.content').style.display = "none";
}
}
.grid {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr auto;
grid-template-areas:
"tabs area"
"footer footer";
}
.tabs {
grid-area: tabs;
background: #FF9800;
}
.area {
grid-area: area;
height: 200px;
background: #673AB7;
}
.footer {
grid-area: footer;
background: #607D8B;
height: 40px;
}
.content {
height: 250px;
}
<button onclick="toggleTabsContent()">Make tabs taller/shorter</button>
<div class="grid">
<div class="tabs">
<div class="content" style="display: none"></div>
</div>
<div class="area"></div>
<div class="footer"></div>
</div>
JSFiddle
Display: flex; allows for a lot of flexibility - in fact it's purpose is exactly to flex the height or width to the required dimensions per the content inside of it.
Based on your question, this is exactly what you need.
I've separated your footer from the grid, in order to separate statically sized elements, from those you wish to flex.
Next I've added a min-width to your area, to avoid it collapsing completely. However if you want this functionality, you can remove this. i.e. if you want that when there is no image it doesn't appear, you can remove this.
Lastly, the display: flex; and flex-flow: row nowrap; allows the sizing to flex, and for elements within the grid (now named "flex") to be placed next to each other with the same dimensions and they won't wrap to the next line to accommodate the sizing changes.
function toggleTabsContent() {
const display = document.querySelector('.content').style.display;
if (display == "none") {
document.querySelector('.content').style.display = "block";
} else {
document.querySelector('.content').style.display = "none";
}
}
.flex {
display: flex;
flex-flow: row nowrap;
}
.tabs {
background: #FF9800;
height: auto;
width: 100%;
}
.area {
height: auto;
min-height: 200px;
width: 100%;
background: #673AB7;
}
.footer {
background: #607D8B;
height: 40px;
}
.content {
height: 400px;
}
<button onclick="toggleTabsContent()">Make tabs taller/shorter</button>
<div class="flex">
<div class="tabs">
<div class="content" style="display: none"></div>
</div>
<div class="area"></div>
</div>
<div class="footer"></div>
Use height:0;min-height:100%; on the tabs element. This will make sure the height of the element will not contribute on the size of its track then by adding min-height:100% you force it to use the height of the track defined by other elements:
function toggleTabsContent() {
const display = document.querySelector('.content').style.display;
if (display == "none") {
document.querySelector('.content').style.display = "block";
} else {
document.querySelector('.content').style.display = "none";
}
}
.grid {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr auto;
grid-template-areas:
"tabs area"
"footer footer";
}
.tabs {
grid-area: tabs;
background: #FF9800;
height:0;
min-height:100%;
overflow:auto;
}
.area {
grid-area: area;
height: 200px;
background: #673AB7;
}
.footer {
grid-area: footer;
background: #607D8B;
height: 40px;
}
.content {
height: 250px;
}
<button onclick="toggleTabsContent()">Make tabs taller/shorter</button>
<div class="grid">
<div class="tabs">
<div class="content" style="display: none"></div>
</div>
<div class="area"></div>
<div class="footer"></div>
</div>
Related question: How can you set the height of an outer div to always be equal to a particular inner div?
You can set the max-height of tabs to be the height of area
.tabs {
grid-area: tabs;
background: #FF9800;
max-height: 400px;
overflow: scroll;
}
I added lot of H1 elements and it worked the way you wanted it.
JS Fiddle
You should go the flex route, but if the only content in .tabs is an image, you can set it as a background-image and let it automatically fill the area with background-size: cover.
document.getElementById("slider").addEventListener('change', changeAreaHeight);
function changeAreaHeight(event) {
let sliderValue = event.target.value;
document.querySelector('.area').style.height = sliderValue + 'px';
}
.grid {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr auto;
grid-template-areas:
"tabs area"
"footer footer";
}
.tabs {
grid-area: tabs;
background: #FF9800;
background-image: url('https://i.picsum.photos/id/167/100/300.jpg'); /* NEW */
background-size: cover; /* NEW */
}
.area {
grid-area: area;
height: 200px;
background: #673AB7;
}
.footer {
grid-area: footer;
background: #607D8B;
height: 40px;
}
<input type="range" id="slider" min="50" value="200" max="800" />
<label>Change height on .area</label>
<div class="grid">
<div class="tabs">
<div class="content" style="display: none"></div>
</div>
<div class="area"></div>
<div class="footer"></div>
</div>
Related
I want to make a fairy simple looking layout with css grid.
As, shown in above image, i want to place 3 items in a grid in the exact same order as shown in the image, I don't want to set specific height to any of the item, since each of the item have different height depending upon the content in it. I want to give 70% of grid horizontal space to item1 and 30% to item2 & item3, item3 should be placed beneath item2. Again, heights of items should be auto. I am trying to achieve this for many hours but failed to do so.
Markup of the problem:
<div class="container">
<div class="item1">some content in it...</div>
<div class="item2">some Content in it...</div>
<div class="item3">some Content in it...</div>
</div>
Solution as requested:
You could use grid-template-areas to span the first item across multiple rows. And use a spacer at the end of the right column to make the items just as big as needed.
We can use calc to account for the grip-gap.
.grid {
display: grid;
grid-gap: 20px;
grid-template-columns: calc(70% - 10px) calc(30% - 10px);
grid-template-areas: "item-1 item-2" "item-1 item-3" "item-1 spacer";
border: 1px dashed #000;
align-items: start;
}
.item {
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
font-weight: bold;
}
#media screen and (max-width: 600px) {
.grid {
grid-template-areas: "item-2" "item-1" "item-3";
grid-template-columns: 100%;
}
}
.item-1 {
grid-area: item-1;
}
.item-2 {
grid-area: item-2;
}
.item-3 {
grid-area: item-3;
}
.purple {
background-color: #5B00FF;
}
.red {
background-color: #FF0000;
}
.pink {
background-color: #FF00FD
}
.h-500 {
height: 500px;
}
.h-100 {
height: 100px;
}
.h-200 {
height: 200px;
}
<div class="grid">
<div class="item item-1 purple h-500">Item 1</div>
<div class="item item-2 red h-100">Item 2</div>
<div class="item item-3 pink h-200">Item 3</div>
</div>
Alternative solution with different columns:
You could use grid-gap along with grid-template-columns. You have to take the grid-gap into account for the width of template-column That's why there is this calc.
.grid {
display: grid;
grid-gap: 20px;
grid-template-columns: calc(70% - 10px) calc(30% - 10px);
border: 1px dashed #000;
}
.col--right {
display: flex;
flex-flow: column nowrap;
gap: 20px;
}
.item {
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
font-weight: bold;
}
.purple {
background-color: #5B00FF;
}
.red {
background-color: #FF0000;
}
.pink {
background-color: #FF00FD
}
.h-500 {
height: 500px;
}
.h-100 {
height: 100px;
}
.h-200 {
height: 200px;
}
<div class="grid">
<div class="col col--left">
<div class="item purple h-500">Item 1</div>
</div>
<div class="col col--right">
<div class="item red h-100">Item 2</div>
<div class="item pink h-200">Item 3</div>
</div>
</div>
I'm trying to use CSS grid to layout some content in the following constraints.
I have three divs - all should be 50% wide but div two and three should stack on top of each other next to div 1.
I've managed to achieve this using grid-template-areas, but I'm using PHP to dynamically populate this, so there's no guarantee that there will always be three divs, so if it goes over this amount, I simply want the grid to repeat.
I'm using the following code right now:
.container {
display: grid;
grid-template-columns: 50% 50% 50%;
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas:
"Grid-1 Grid-2 ."
"Grid-1 Grid-3 ."
". . .";
}
.Grid-2 { grid-area: Grid-2; }
.Grid-3 { grid-area: Grid-3; }
.Grid-1 { grid-area: Grid-1; }
html, body , .container {
height: 100%;
margin: 0;
}
.container * {
border: 1px solid red;
position: relative;
}
.container *:after {
content:attr(class);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: grid;
align-items: center;
justify-content: center;
}
<div class="container">
<div class="Grid-1"></div>
<div class="Grid-2"></div>
<div class="Grid-3"></div>
</div>
It would also ne nice to not have to give each div that I'm generating a PHP the specific area class. Is this achievable using grid?
Simply like below:
.container {
display: grid;
grid-template-columns: 50% 50%; /* 2 columns */
grid-auto-rows:50vh; /* size of one row*/
}
/* for each 3 divs make the first one span 2 rows */
.container > :nth-child(3n + 1) { grid-row:span 2 }
.container * {
border: 1px solid red;
display: grid;
place-items: center;
}
.container *:after {
content:"some content";
}
body {
margin: 0;
}
<div class="container">
<div ></div>
<div ></div>
<div ></div>
<div ></div>
<div ></div>
<div ></div>
<div ></div>
<div ></div>
</div>
Struggling with the follwing layout, is it possible? The html is not fixed. I've added some code to work with along with a jsbin:
https://jsbin.com/lifinogifu
Layout
<div class="container">
<div class="item">
<div class="item-image"></div>
<div class="item-name"></div>
<div class="item-meta"></div>
<div class="item-action"></div>
</div>
</div>
.item {
display: flex;
width: 100%;
background-color: #eee;
> * {
height: 100px;
border: 1px dotted #ccc;
}
&-image {
width: 100px;
}
&-name {
flex: 1;
}
&-meta,
&-action {
width: 80px;
}
}
Using Grid:
To create the responsive part you will need 2 columns, the first for the yellow, and the second column for all the other blocks. In the second column, you will create a grid with two lines, the first line you will put the green block, and the second line you will put another grid with the red and the blue block. So the code would look something like this:
<style>
#yellow {
background-color: yellow;
}
#green {
background-color: green;
}
#red {
background-color: red;
}
#blue {
background-color: blue;
}
#grid1 {
display: grid;
grid-template-columns: 1fr 4fr;
width: 100%;
height: 400px;
}
#grid2 {
display: grid;
grid-template-columns: 1fr;
}
#grid3 {
display: grid;
grid-template-columns: 1fr 1fr;
}
</style>
<div id="grid1">
<div id="yellow"></div>
<div id="grid2">
<div id="green"></div>
<div id="grid3">
<div id="red"></div>
<div id="blue"></div>
</div>
</div>
</div>
For the desktop part, you will have to create a media query that changes the columns of the grid to achieve the correct design. You actually can achieve this only changing the layout of the grid2 so everything will be side by side:
#grid2 {
grid-template-columns: 1fr 1fr;
}
If I want to use the css-grid and make one of its child fill up the remaining width when the other child is not present on the screen, how would I do something like that?
var button = document.querySelector('.click');
var buttone = document.querySelector('.clicke')
var left = document.querySelector('.left');
button.addEventListener('click', function(e) {
left.style.display = 'none';
})
buttone.addEventListener('click', function(e) {
left.style.display = 'block';
})
html,
body {
margin: 0;
}
.wrap {
display: grid;
width: 100vw;
height: 100vh;
background-color: black;
grid-template-columns: 1fr 1fr;
grid-template-areas: "left right"
}
.left {
grid-area: left;
background-color: green;
}
.right {
grid-area: right;
background-color: red
}
<div class='wrap'>
<div class='left'>
<button class='click'>HIDE ME</button>
</div>
<div class='right'>
<button class='clicke'>SHOW HIM</button>
</div>
</div>
For example, if the green's display becomes none, I would like for the red to fill up the remaining spot in the grid. I can use other displays to achieve this effect easily, but I wanted to know if there was a method to do this while using a grid display with its areas being specifically laid out.
Don't define areas so that the columns can be flexible, set grid-template-columns: 1fr; with grid-auto-flow: column; and grid-auto-columns: 50%
var button = document.querySelector('.click');
var buttone = document.querySelector('.clicke')
var left = document.querySelector('.left');
button.addEventListener('click', function(e) {
left.style.display = 'none';
})
buttone.addEventListener('click', function(e) {
left.style.display = 'block';
})
html,
body {
margin: 0;
}
.wrap {
display: grid;
width: 100vw;
height: 100vh;
background-color: black;
grid-template-columns: 1fr;
grid-auto-columns: 50%;
grid-auto-flow: column;
}
.left {
background-color: green;
}
.right {
background-color: red
}
<div class='wrap'>
<div class='left'>
<button class='click'>HIDE ME</button>
</div>
<div class='right'>
<button class='clicke'>SHOW HIM</button>
</div>
</div>
At least in this case, it's enough to set
gridColumnStart: 1;
to get the desired layout. The element occupies the named area and also the first column.
I am unsure however about the posibilities to make this solution more general
var button = document.querySelector('.click');
var buttone = document.querySelector('.clicke')
var left = document.querySelector('.left');
var right = document.querySelector('.right');
button.addEventListener('click', function(e) {
left.style.display = 'none';
right.style.gridColumnStart = '1';
})
buttone.addEventListener('click', function(e) {
left.style.display = 'block';
right.style.gridColumnStart = '';
})
html,
body {
margin: 0;
}
.wrap {
display: grid;
width: 100vw;
height: 100vh;
background-color: black;
grid-template-columns: 1fr 1fr;
grid-template-areas: "left right"
}
.left {
grid-area: left;
background-color: green;
}
.right {
grid-area: right;
background-color: red
}
<div class='wrap'>
<div class='left'>
<button class='click'>HIDE ME</button>
</div>
<div class='right'>
<button class='clicke'>SHOW HIM</button>
</div>
</div>
Change .wrap display to block when grid is redundant
Although you have stated that:
I can use other displays to achieve this effect easily, but I wanted to know if there was a method to do this while using a grid display with its areas being specifically laid out.
This method keeps the display: grid layout while it's needed, but scraps it in favour of display: block when it's not.
Cheating? This isn't code golf ;-)
const hide = document.querySelector( '.hide' ),
show = document.querySelector( '.show' ),
wrap = document.querySelector( '.wrap' );
hide.addEventListener( 'click', function() {
wrap.classList.add( 'hidden' );
} );
show.addEventListener('click', function() {
wrap.classList.remove( 'hidden' );
} );
html, body {
margin: 0;
}
.wrap {
display: grid;
width: 100vw;
height: 100vh;
background-color: black;
grid-template-columns: 1fr 1fr;
grid-template-areas: "left right";
}
.wrap.hidden {
display: block;
}
.wrap.hidden .left {
display: none;
}
.wrap.hidden .right {
height: 100vh;
}
.left {
grid-area: left;
background-color: green;
}
.right {
grid-area: right;
background-color: red;
}
<div class='wrap'>
<div class='left'>
<button class='hide'>HIDE ME</button>
</div>
<div class='right'>
<button class='show'>SHOW HIM</button>
</div>
</div>
I have a CSS grid that occupies 100% width and 100% height of a window (the body element has display: grid;). The grid has row and column templates and elements which occupy 100% of their allocated space. However, when I add a grid-gap to the grid, it makes the grid too large for the window, forcing scrollbars to appear. How can I stop the grid-gap from adding to the dimensions of the grid - similar to how box-sizing: border-box; stops padding from adding to the dimensions of an element? Instead, I want the gaps to shrink the cells of the grid.
Thanks.
When you use "fr" it works.
Example:
HTML:
<section>
<article class="a">A</article>
<article class="b">B</article>
<article class="c">C</article>
<article class="d">D</article>
</section>
SCSS:
section {
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-flow: column;
grid-gap: 20px;
border: 10px solid blue;
article {
background-color: tomato;
&.d {
grid-column: 2;
grid-row-start: 1;
grid-row-end: 4;
background-color: olive;
}
}
}
It works same as if you used box-sizing: border-box and padding as you can see in this demo. Height is set to 100vh and you can see that if you remove or add grid-gap there is no scrollbar, you just need to remove margin from body.
body {
margin: 0;
}
.grid {
display: grid;
height: 100vh;
grid-gap: 20px;
background: #FF7D7D;
grid-template-columns: 1fr 2fr; /* Use Fractions, don't use % or vw */
}
.grid > div {
background: black;
color: white;
}
div.a, div.d {
color: black;
background: white;
}
<div class="grid">
<div class="a">A</div>
<div class="b">B</div>
<div class="c">C</div>
<div class="d">D</div>
</div>
You could use view-port units:
vw (1% of window's width)
vh (1% of window's height)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
height: 100%;
}
.first { height: 40vh; }
.hori { height: 10vh; }
.second { height: 50vh; }
div > div {
float: left;
}
.left { width: 40vw; }
.vert { width: 10vw }
.right { width: 50vw; }
.first .left,
.second .right {
background: #ccc;
}
.first .right,
.second .left {
background: #000;
}
<div class="first">
<div class="left"></div>
<div class="grid-break vert"></div>
<div class="right"></div>
</div>
<div class="grid-break hori"></div>
<div class="second">
<div class="left"></div>
<div class="grid-break vert"></div>
<div class="right"></div>
</div>