CSS: make image take up space before loading when inside other inline-block element

Viewed 939

I'm working on an image gallery site, which shows a bunch of images of different sizes and aspect ratios. The images should be shown many on a line if they fit.

Each image should be say no wider than 20vw, and no taller than 300px.

Critically, I don't want any "jank" as the page loads - each picture should take up it's space before it has loaded so it all loads smoothly without jank / layout shift, even when internet is slow.

Now, I've managed to achieve this for images that are just sitting straight in the document. But I can't get it to work when they are wrapped inside a <figure> element. (Eventually I want to add <figcations> to them, but this is not relevant for my question). The figure elements could just as well be <div>s or something else.

An example will illustrate what I'm trying to do:

* {
  margin: 0;
}

body {
  background-color: lightgray;
}

figure {
  display: inline-block;
  background-color: lightblue;
}

img {
  max-width: 20vw;
  max-height: 300px;
  height: auto;
  width: auto;
  background-color: black;
  display: inline-block;
}

figure img {
  background-color: gray;
}
<h3>These 2 images take up space correctly while loading:</h3>
<img src="https://deelay.me/1500/https://dummyimage.com/300x600/f0f/000.png" width="300" height="600">
<img src="https://deelay.me/1500/https://dummyimage.com/400x200/0f0/000.png" width="400" height="200">

<h3>These 2 images just jank into existence when they load:</h3>
<figure>
  <img src="https://deelay.me/1500/https://dummyimage.com/500x1000/f00/000.png" width="500" height="1000">
</figure>
<figure>
  <img src="https://deelay.me/1500/https://dummyimage.com/300x150/0f0/000.png" width="400" height="200">
</figure>
<p>

some text that shouldn't move as the page loads
</p>

How can I make the second two images inside <figure>s take up space before they've loaded?

2 Answers

Change inline-block to inline on figure.

* {
  margin: 0;
}

body {
  background-color: lightgray;
}

figure {
  display: inline; /* The only thing I changed */
  background-color: lightblue;
}

img {
  max-width: 20vw;
  max-height: 300px;
  height: auto;
  width: auto;
  background-color: black;
  display: inline-block;
}

figure img {
  background-color: gray;
}
<h3>These 2 images take up space correctly while loading:</h3>
<img src="https://deelay.me/1500/https://dummyimage.com/300x600/f0f/000.png" width="300" height="600">
<img src="https://deelay.me/1500/https://dummyimage.com/400x200/0f0/000.png" width="400" height="200">

<h3>These 2 images just jank into existence when they load:</h3>
<figure>
  <img src="https://deelay.me/1500/https://dummyimage.com/500x1000/f00/000.png" width="500" height="1000">
</figure>
<figure>
  <img src="https://deelay.me/1500/https://dummyimage.com/300x150/0f0/000.png" width="400" height="200">
</figure>
<p>

some text that shouldn't move as the page loads
</p>

However... In Edge and Chrome, neither the images with or without wrapping figures load immediately, in your example or mine. Only in FireFox do they have a height and width before loading. You should wrap them in tags with a specified height and width for a cross browser solution.

New Solution:

I honestly had no idea what made the first example work in FireFox, I just had some intuition that the display mode often affects the volume of elements in strange ways. This exploration did demonstrate to us that browsers do not all render the above example the same way, which is unfortunate. I have an alternate solution for you though. After reading this excerpt from the page you linked me to I found something that seemed to work:

When the width/height of an element — as set using HTML attributes — is overidden using CSS using something like this:

img {
  max-width: 100%;
  height: auto;
}

The aspect ratio is then used to calculate the height and therefore the correct size is applied to the element, meaning that the aforementioned jank will not occur when the image loads.

My solution is to remove the width:auto, it seems to work in both Firefox and Chrome. Honestly I also do not entirely know why this works either... My guess would be that the width:auto was overriding the width as it was defined on the element itself, thus not allowing the auto-magic aspect-ratio to be calculated, or something... If anyone wants to chime in an explain it properly, that would be appreciated.

Here is the demo:

* {
  margin: 0;
}

body {
  background-color: lightgray;
}

figure {
  display: inline-block; /* I put this back to how you had it */
  background-color: lightblue;
}

img {
  max-width: 20vw;
  max-height: 300px;
  height: auto;
  /*width: auto;*//*New solution, don't specify width, only height*/
  background-color: black;
  display: inline-block;
}

figure img {
  background-color: gray;
}
<h3>These 2 images take up space correctly while loading:</h3>
<img src="https://deelay.me/1500/https://dummyimage.com/300x600/f0f/000.png" width="300" height="600">
<img src="https://deelay.me/1500/https://dummyimage.com/400x200/0f0/000.png" width="400" height="200">

<h3>These 2 images just jank into existence when they load:</h3>
<figure class="fig-1">
  <img src="https://deelay.me/1500/https://dummyimage.com/500x1000/f00/000.png" width="500" height="1000">
</figure>
<figure class="fig-2">
  <img src="https://deelay.me/1500/https://dummyimage.com/300x150/0f0/000.png" width="400" height="200">
</figure>
<p>

  some text that shouldn't move as the page loads
</p>

I wrapped your rows with a div and I fixed their width as you write in question. All images has holder (you already do that with the <figure>) and your images need to load these holders. Your holders need max-height: 300px;, so you need to allocate maximum 300px height while you are waiting your images.

.row {
  display:block;
}

.holder {
  display:inline-block;
  width:20vw;
  max-height: 300px;
}

.holder img {
  width:100% !important;
  max-height:300px;
}
<h3>Some header here</h3>
<div class="row">
  <div class="holder">
    <img src="https://deelay.me/1500/https://dummyimage.com/300x600/f0f/000.png" width="300" height="600">
    </div>
  <div class="holder">
    <img src="https://deelay.me/1500/https://dummyimage.com/400x200/0f0/000.png" width="400" height="200">
  </div>
</div>
<h3>These 2 images just jank into existence when they load:</h3>
<div class="row">
  <div class="holder">
    <img src="https://deelay.me/1500/https://dummyimage.com/500x1000/f00/000.png" width="500" height="1000">
  </div>
  <div class="holder">
    <img src="https://deelay.me/1500/https://dummyimage.com/300x150/0f0/000.png" width="400" height="200">
  </div>
</div>

<h3>Txt should be stay here3:</h3>

Related