13

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.

Ali
  • 56,466
  • 29
  • 168
  • 265
  • The answer to [CSS Grid Row Height Safari Bug](https://stackoverflow.com/q/44770074/341970) seem to indicate why it behaves differently in Safari: *"The problem is that Safari is not recognizing the `height: 100%` on the `img` elements."* – Ali Sep 19 '17 at 15:04
  • What height do you want these rows to be, and how do you expect the grid to know it? You have only set the grid container to height `60vh`, but you haven't set a height for the grid rows. – TylerH Sep 19 '17 at 15:29
  • @TylerH Good questions. I would want the grid to stretch to the bottom of the page (fill out the empty space on the page), although I do not know how to do that. :-( Currently I subtract the the height of the header and footer from `100vh`, use `calc()`, and set this as the height of the container. Sorry, I am an absolute beginner in this area. As for your other question: The rows should be of equal height. – Ali Sep 19 '17 at 15:48
  • Ah, so you have a header and footer that together are `40vh + 12px`, more or less? – TylerH Sep 19 '17 at 15:51
  • @TylerH The formula is more complicated than that, unfortunately. :-( It would make my life *much* easier if I could put both the header and the footer inside the grid container, and then set the height of the grid to `100vh`. Or is there a way to make a `div` container stretch to the bottom of the page (or till the footer)? It looks like I have to tell the height of some element... – Ali Sep 19 '17 at 15:57

3 Answers3

6

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>
kukkuz
  • 41,512
  • 6
  • 59
  • 95
5

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:

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:

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

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • I looked briefly for something like https://stackoverflow.com/questions/43311943/prevent-grid-items-from-stretching-in-css-grid-layout - I feel like this may be a duplicate of that. – TylerH Sep 19 '17 at 18:17
  • @TylerH, this OP's code appears to have several issues that need attention. The link you mention covers the minimum sizing issue. If that was the only problem, I would agree, it's a dupe. However, there are also the issues of percentage heights and browser rendering variations. – Michael Benjamin Sep 19 '17 at 18:22
  • @Michael_B Hmmm. It is totally messed up on the iPhone in Safari 10.3. :-( – Ali Sep 19 '17 at 19:51
  • Like I wrote in my answer, you'll have to work it out. As I don't have all the specifics of your layout, I just provided guidance. I would suggest you review the links in my answer. In particular, this one: https://stackoverflow.com/q/44770074/3597276 – Michael Benjamin Sep 19 '17 at 19:55
  • 1
    @Michael_B Fair enough. I will try to solve it on my own, and if I fail, I will post another question with more elaborate code, showing more of the actual layout. – Ali Sep 19 '17 at 21:37
1

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>
TylerH
  • 20,799
  • 66
  • 75
  • 101
  • This does not work in Chrome 60, unfortunately. :-( The images overflow. – Ali Sep 19 '17 at 16:01
  • @Ali Alas, I have grown complacent using Firefox and forgot that Chrome likes to break a lot of things. Thanks for letting me know; I'll take a look and see what I can do to fix Chrome's problem. – TylerH Sep 19 '17 at 16:07
  • @Ali Looks like Chrome doesn't like extending grid-children properties to grand children of `display: grid` elements. I've updated the Snippet and it should work in Chrome now. I don't have Safari to test on. – TylerH Sep 19 '17 at 16:10
  • Thanks, I appreciate your efforts but I am still not comfortable with the proposed solution. Could expand a little bit on your answer, please? Why and how your answer resolves the issue? What was wrong with my attempts? Such an explanation will be useful for future Google visitors too. (Assuming of course that they want to learn and not just blindly copy-and-paste code without understanding it first...) – Ali Sep 19 '17 at 17:26
  • @Ali I added a bit more info on minmax, but kukkuz' answer is the cleaner solution. I haven't read Michael_B's answer just yet, but it's probably cleaner, too. – TylerH Sep 19 '17 at 18:15
  • 1
    OK, I looked at your updated answer. The issue is that on the real webpage `.tile` is a flex container... :-( Anyways, the bug in Chrome is not your fault. I upvoted your answer to say thank you for your efforts! – Ali Sep 19 '17 at 21:47