Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make images stay within the rows of a css grid container?

The code below shows the intended behavior when I resize the window in Chrome 60, and in Firefox 55 (but not in iOS Safari 10.3; that is most likely another question why it misbehaves in Safari).

html, body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background-color: lightgrey;
}

.container {
  box-sizing: border-box;
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(3, calc((60vh - 12px)/3));
  /*grid-template-rows: 1fr 1fr 1fr;*/
  /*grid-template-rows: auto auto auto;*/
  height: 60vh;
  border: 3px solid yellow;
  padding: 3px;
  /*grid-gap: 20px;*/ /* <-- would also mess things up */
}

.tile {
}

img {
  box-sizing: border-box;
  display: block;
  object-fit: contain;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  padding: 3px;
}
 <!-- The image is 200 x 100 px: a green and a blue square next to each other. -->
 <div class="container">
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
 </div>

It is important that the aspect ratio of the images (2:1)

dummy image

is preserved. I would have expected either:

grid-template-rows: 1fr 1fr 1fr;

or:

grid-template-rows: auto auto auto;

makes the images fit within the rows of the grid, but neither of them does. With:

grid-template-rows: repeat(3, calc((60vh - 12px)/3));

I get the desired behavior. How can I avoid working out the math myself? In other words, what should I do so that grid-template-rows: 1fr 1fr 1fr; (or something similar) works?

It is already difficult to work out the height of the container element in CSS on the real page. The goal is to solve it with CSS grid layout; no JavaScript, and no background image hacks.


Update: I originally excluded the background image hack for two reasons.

  1. I thought (due to some misunderstandings) that the background image url must be in the CSS file, but this is not the case: I can use inline styles and have it in the HTML.

  2. It felt hackish. After having seen how complicated and messy it gets with nested flex containers nested inside a grid container just to make it work on Safari, I simply resorted to the background image hack as it is significantly cleaner and works in all browsers tested (Chrome, Firefox, Safari).

In the end, it is not the accepted answer that helped to solve my problem.

like image 842
Ali Avatar asked Sep 19 '17 14:09

Ali


People also ask

How do I make an image fit the grid cell in CSS?

Answer: Use the CSS max-width Property You can simply use the CSS max-width property to auto-resize a large image so that it can fit into a smaller width <div> container while maintaining its aspect ratio.

How do I force an image to stay inside a div?

Answer: Use the CSS max-width Property Additionally, you can also apply the max-height property if you've a fixed height div element, so that the image doesn't overflow from the div's boundary horizontally or vertically.

How do you align items in a grid container?

One of the easiest ways of centering the content of grid items is using Flexbox. Set the display to "grid" for the grid container, and "flex" for grid items. Then, add the align-items and justify-content properties, both with the "center" value, to grid items.


3 Answers

You have the images set to height: 100%. But 100% of what? 100% of the container? 100% of the viewport? 100% of the row? If so, what's the height of the row?

Chrome and Firefox make an educated guess about your intentions. They have implemented algorithms designed to go beyond spec guidance in order to improve user experience. They call these modifications "interventions".

Safari doesn't do this. Safari adheres strictly to spec language, which states that a percentage height on an element must have a defined height on the parent, otherwise it is ignored.

These browser differences are explained in more detail here:

  • CSS Grid Row Height Safari Bug
  • Chrome / Safari not filling 100% height of flex parent

Then you have to consider that grid items, by default, cannot be smaller than their content. If your rows are set to 1fr, but the images are taller than the space allotted, the rows must expand. You can override this behavior with min-height: 0 / min-width: 0 or overflow with any value other than visible.

This behavior is explained in more detail here:

  • Prevent grid items from stretching in CSS Grid Layout
  • Why doesn't flex item shrink past content size?

Still, once you factor in the guidance above, you can probably get your layout to work in Safari with a combination of grid and flex properties:

* {
  box-sizing: border-box;
}

body {
  display: flex;
  flex-direction: column;
  height: 100vh;
  margin: 0;
  background-color: lightgrey;
}

header,
footer {
  flex: 0 0 100px;
  background-color: tomato;
  display: flex;
  align-items: center;
  justify-content: center;
}

.container {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-auto-rows: auto;
  padding: 3px;
}

.tile {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 0;
}

img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  padding: 3px;
}
<header>HEADER</header>
<!-- The image is 200 x 100 px: a green and a blue square next to each other. -->
<div class="container">
  <div class="tile">
    <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
  </div>
  <div class="tile">
    <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
  </div>
  <div class="tile">
    <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
  </div>
  <div class="tile">
    <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
  </div>
  <div class="tile">
    <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
  </div>
  <div class="tile">
    <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
  </div>
</div>
<footer>FOOTER</footer>

jsFiddle

like image 128
Michael Benjamin Avatar answered Nov 16 '22 06:11

Michael Benjamin


You can use grid-template-rows: 1fr 1fr 1fr and more importantly you have to reset the min-width and min-height values of the grid items which defaults to auto (as much as the content).

To provide a more reasonable default minimum size for grid items, this specification defines that the auto value of min-width/min-height also applies an automatic minimum size in the specified axis to grid items whose overflow is visible and which span at least one track whose min track sizing function is auto. (The effect is analogous to the automatic minimum size imposed on flex items.)

Source: W3C

This is similar to the auto flex item rule with flexboxes. See demo below where I reset them to zero:

html, body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background-color: lightgrey;
}

.container {
  box-sizing: border-box;
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  height: 60vh;
  border: 3px solid yellow;
  padding: 3px;
  /*grid-gap: 20px;*/ /* <-- would also mess things up */
}

.tile {
  min-width: 0;
  min-height: 0;
}

img {
  box-sizing: border-box;
  display: block;
  object-fit: contain;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  padding: 3px;
}
<!-- The image is 200 x 100 px: a green and a blue square next to each other. -->
 <div class="container">
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
 </div>
like image 27
kukkuz Avatar answered Nov 16 '22 06:11

kukkuz


I don't know how snugly you want the images to fit, but you could use minmax(). minmax() lets you set a minimum and maximum value for the grid-row size. Setting auto for the min and 33% for the max will let them get as small as the content needs to get, and up to 33% of the height of the grid container, but no bigger. This will keep all your grid items together at maximum height of 99% of the 60vh that the grid container takes up.

This is not exactly the automatic way you were hoping to get... you're still declaring a size, even if it's relative. It does avoid the clunky-looking calc((60vh - 12px) / 3), though there's nothing really wrong with using that method, unless there are other constraints in your post.

However, kukkuz' answer and resetting the min-height is a better solution and is what I was missing.

html, body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background-color: lightgrey;
}
.container {
  box-sizing: border-box;
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(3, minmax(auto, 33%));
  height: 60vh;
  border: 3px solid yellow;
  padding: 3px;
}
.tile {
    display: grid;
}
img {
  box-sizing: border-box;
  display: block;
  object-fit: contain;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  padding: 3px;
}
 <!-- The image is 200 x 100 px: a green and a blue square next to each other. -->
 <div class="container">
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
   <div class="tile">
     <img src="https://i.stack.imgur.com/qbpIG.png" alt="." />
   </div>
 </div>
like image 1
TylerH Avatar answered Nov 16 '22 08:11

TylerH