Reserve space and keep aspect ratio of not loaded yet images for responsive design


#1

I'm using a technique for responsive images so that when the container element's width is smaller than the width of the image then the image is scaled down proportionally:

<img src="someimage.png" width="400" height="200" alt="My Image" class="img">

CSS:

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

Codepen sample.

This works well but I've noticed one downside to this method: browsers do not reserve space for the image before the image is downloaded from the server or when the image URL is broken. Normally, browsers would immediately reserve rectangular space for images with width and height attributes, in this case 400x200. But as soon as I add height: auto declaration this behaviour is lost and the <img> element occupies only enough space to accommodate the alt text. You can test this easily by using the code above and supplying invalid image URL to src.

Is there a way to get browsers to reserve space for non-loaded images (whose dimensions are specified in the attributes) when using max-width? Technically, I see no reason why browsers wait for dimensions encoded in the image file while they have already been provided in the html attributes.


#2

Hi there Lemon_Juice,

the only way that I can see - though I am getting more and more short
sighted in my dotage - of achieving your requirement is like this...

<!DOCTYPE HTML>
<html lang="en">
<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">

<title>untitled document</title>

<style media="screen">
body {
    background-color: #f9f9f9;
    font: 100% / 162% verdana, arial, helvetica, sans-serif;
 }
 
.image-container {
    position: relative;
    width: 60%;
    padding-top: 46.5%;
    background: #ffa500;
}

.img {
    position: absolute;
    top: 0;
    width: 100%;
    height: auto
 }
</style>

</head>
<body> 

 <div class="image-container">
  <img class="img" src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Flag_of_Moscow.png/774px-Flag_of_Moscow.png" width="774" width="774" height="600" alt="My Image">
 </div><div>
  text below image
 </div>

</body>
</html>

coothead


#3

Wow, that's quite a convoluted hack with positioning and computing percentages! I don't know if I'm willing to go that far with it but thanks anyway for an idea.

I have an impression something changed in the last few years in this respect as I remember good old advice to always specify width and height attributes for images so that browsers can reserve space for them and thus avoid jumping of elements as subsequent images get downloaded. That was certainly the case but now when I try a simple unstyled image like this:

<img src="http://example.com/inexistent" width="774" height="600" alt="My Image">

the specified dimensions are ignored. I'm sure they were respected in older browsers. I can force them to be respected if I apply display: block but then I cannot use height: auto. It appears it's one of the features of html that was found not to be useful any more...


#4

I would have thought the height and width attribute values would be the fallback dimensions, like you, AFAIK that is how it's always been. Looking at the w3c specs they are "hints" so I guess browsers are free to use them as they wish. (hmm, maybe a "collapse for mobile", errm, "courtesy")

Not on desktop so not tried yet, but if you inspect the alt that's displayed is it something you can target with CSS to maintain the dimensions the image would have occupied?


#5

Hi,
Nothing has changed with the way browsers handle images. The same img scaling techniques used today were also used years ago.

But keep in mind, until responsive design became best practices, sites that were fixed width didn't really scale the images and thus the width and height attributes would hold the space open.

When you introduce min/max dimensions and height:auto in the CSS then that immediately overrides the attributes. Since the image has not loaded yet, then there is no content to hold the <img> container open.

It's really no different than an empty div that has min/max dimensions. It's not going to expand to those dimensions when it has no content

An <img> is a replaced element so it has it's own intrinsic dimensions. The only real reason for using display:block is to discard the default vertical-align:baseline property/value that is on an inline image. It will still scale just the same with it's native display:inline

Here is a bare bones example, it will not stay open until the loaded image gives it the content to expand it. The image is display inline at this point, with max-width and height:auto, and those are the game changers.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Test Page</title>
<style>
img {
  border: 1px solid;
  width:100%;
  max-width:600px;
  height:auto;
}
</style>
</head>
<body>

  <img src="http://via.placeholder.com/600x300" width="600" height="300" alt="My Image">

</body>
</html>

#6

Testing with an invalid url will point to no image at all and there will be no intrinsic ratio for width and height and therefore the width attribute will be honoured but as the height is auto in css then it will be zero because there is no image and therefore just big enough to cover the alt content. How can the browser know what the intrinsic ratio of a non existent image is going to be?

On the other hand if the image is available then a browser 'may' work out the intrinsic ratio from the image attributes even if you set height to auto in css. This is at the browsers discretion anyway as image attributes are indeed optional although it is still best practice to add them.

I see no jump in your codepen example but of course that may not be the same for a slow connection.

I'm guessing that if you added a style tag in the html after the img and you set the css to auto you would get the image reserved first before css set it to auto. Of course having a style tag in the body is messy although now allowed in html5. (This won't work for invalid urls because there will be no image to fetch and this the css will be honoured very quickly).

The re-flowing of content is more of an issue these days because of the way flex and grid have to rely on circular references to work out the dimensions of the layout so even if you squash the image problem there will still be some layout re-positioning anyway.


#7

Yes, I understand it now, when I use height:auto then the height attribute is overridden so its value is ignored.

I was somewhat wrong in saying that image width and height attributes are not respected in modern browsers - they are but there is a difference between a broken image (invalid URL) and an image waiting to be downloaded. For broken images (which I initially tested) the attribute dimensions are indeed ignored for inline images and the <img> size falls back to alt content size. But if a browser waits for an image to be downloaded then it reserves space for it based on the attributes. I have now tested it with an image that is loaded with a delay and that's how it works. So all is well with the browsers...

This is correct but still it would be good to minimize the re-flowing by at least getting the images to reserve their space immediately.

This problem seems to fall under the more general issue that CSS lacks a proper way of scaling elements proportionally. If I were able to set max-height proportional to the image's width then this would solve the problem gracefully.


#8

How about using PHP file_exists, file_get_contents and getimagesize.


#9

But that is irrelevant here, image dimensions are already known so no need to employ PHP for this task.

Well, it's a shame there is no native HTML/CSS way to handle this problem for responsive design. On some web sites I experience a lot of page re-flows, especially on mobile, and it's irritating when the content you are reading or are trying to tap constantly jumps off and runs away. I've been using this CSS universally and was happy:

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

but now I'd better at least disable this on desktop layouts because then it doesn't do anything apart from turning off space allocation in browsers. For certain use cases I might try applying proportional dimensions using vw units (as described in the article I linked to) but it's not a universal solution and might require some tweaking with unit calculations.


#10

I was thinking about when the "when the image URL is broken".

Have a function displayImg( $imgUrl ) return with a broken image template on failure otherwise return the requested image.


#11

Yes it would be nice to have an aspect ratio property in the css so that you could use it as a multiplier for the height rather than auto and therefore preserve the height. I believe that link you provided above shows a draft spec for something like that. For dynamically added images you would probably need an aspect ratio attribute on the image also if you didn't know the value beforehand so I guess its not that straightforward.

If you have the image widths and dimensions available beforehand you can 'hard code' the image container's height based on the image aspect ratio and thus preserve the space in a responsive layout.

e.g.

It uses the old padding percentage trick (which @coothead also used).

It's a lot more work of course but does solve the problem. I think we will probably have to wait a bit longer until it is solved properly though. Unless some bright spark can improve on this or has a better idea :slight_smile:


#12

I also questioned the relevance of this when I saw it. But I have done similar things myself, creating an image object class with methods to gather various useful properties like aspect ratio, etc. which can be used for things like setting reduced dimensions. And yes the first thing it does is see if the image file actually exists.
Now looking at the solutions offered, using the "padding trick" which is maybe not the most elegant, but Hobson's choice probably.
Perhaps php could help in doing the donkey work and number crunching to render the image in its correctly proportioned container with a padding value relating to aspect. Maybe, dare I suggest it, in-lining the padding value to individual containers. If there are many images of varying sizes and aspects, that may actually be neater than rendering a whole bunch of dynamically built css classes in the head/style and repeating them all in the container's attributes.
Not sure if this suits your needs, but an idea.


#13

This seems to work (somewhat). i.e. giving the img display: inline-block

<!DOCTYPE HTML>
<html lang="en">
<head>
<title>No Image</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style type="text/css">
.nonexistent {
 display: inline-block;
 outline: 1px solid #070;
 color: #f00;
 max-width: 100%;
 min-height: auto;
}
p { outline: 1px solid #007; }
</style>
</head>
<body>
<h1>No Image</h1>
<div>
 <p>leading text content</p>
 <img class="nonexistent" src="nonexistent.jpg" height="200" width="600" alt="nonexistent">
 <p>trailing text content</p>
</div>
</body>
</html>

#14

Trying it out on the original codepen by @Lemon_Juice it seems to work with display: block too. It's the min-height instead of just height that appears to do the trick.


#15

Am I missing something?

I don't see it scaling to preserve the aspect ratio. It stays at 200px height no matter what the width is.

I just did a live edit in the codepen too and only the img width changes.


#16

Yes, my test was not too thorough. :grimacing:
Thought that was just too easy.


#17

Unfortunately, the easy trick is too easy to work...

I see this is a way of automating the implementation of a hack but for now I want to explore ideas without server side languages. Besides, I'm not too fond of the padding trick because it requires using wrapper elements. Actually, I'd like to find a method that will work for user-editable content like a html editor in an admin panel where the site owner can insert html text with normal images that have width and height attributes and things will just work - using wrapper divs adds complexity to this process.

Anyway, I've tried using the vw method and all looked well except that vw refers to the width of the viewport including the scrollbars so if scrollbars appear the proportions are distorted (btw, who the hell thought vw would be any good if they include scrollbars, which are outside of the page content??). So I resorted to javascript and css variables for help:

<!DOCTYPE HTML>
<html>
<head>
<title>Image</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style type="text/css">
body {
  margin: 10px;
}
html {
	--img-horizontal-spacing: 20px; /* fallback value when js is off */
	--img-max-width: 100%;
}
.img {
	outline: 1px solid green;
	max-width: var(--img-max-width);
}
</style>
</head>

<body>
<script>
function setHSpaceVar() {
	var scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
	document.documentElement.style.setProperty('--img-horizontal-spacing', (20 + scrollbarWidth) + 'px');
}

setHSpaceVar();
window.addEventListener('resize', setHSpaceVar);
</script>

<div>
  <p>leading text content</p>
  <img src="delayed-img.php?600x300" width="600" height="300" class="img" style="max-height: calc((100vw / 2) - (var(--img-horizontal-spacing) / 2))"  alt="My Image">
  <p>trailing text content</p>
  <img src="delayed-img.php?700x200" width="700" height="200" class="img" style="max-height: calc((100vw / 3.5) - (var(--img-horizontal-spacing) / 3.5))" alt="My Image 2">
  <p>trailing text content 2</p>
</div>

</body>
</html>

Here is the live example, the images are intentionally loaded with a delay to be able to see if the space is reserved for them before they appear. Not an ideal solution since I still need to use the ugly style attributes where I set the ratios.

Another attempt with no additional html but with javascript that handles setting image proportions and gracefully falls back to the height: auto behaviour when js is off:

<!DOCTYPE HTML>
<html>
<head>
<title>Image</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style type="text/css">
body {
  margin: 10px;
}
.img {
	display: inline-block;
	outline: 1px solid green;
	max-width: 100%;
	height: auto;
}
</style>
</head>

<body>
<script>
function setImgAscpectRatio() {
	var img;
	var imgs = document.querySelectorAll('img.img');
	
	for (var i=0; i<imgs.length; i++) {
		img = imgs[i];
		
		var ratio = img.getAttribute('width') / img.getAttribute('height');
		var maxWidth = img.parentNode.offsetWidth;
		var targetWidth = Math.min(img.getAttribute('width'), maxWidth);
		var height = targetWidth / ratio;
		img.style.height = height + 'px';
		img.style.width = targetWidth + 'px';
	}
}

// set image sizes when page html is partially loaded:
var arInterval = setInterval(setImgAscpectRatio, 1000);

document.addEventListener('DOMContentLoaded', function() {
	clearInterval(arInterval);
	setImgAscpectRatio();
	window.addEventListener('resize', setImgAscpectRatio);
});
</script>

<div>
  <p>leading text content</p>
  <img src="delayed-img.php?600x300" width="600" height="300" class="img" alt="My Image">
  <p>trailing text content</p>
  <img src="delayed-img.php?700x200" width="700" height="200" class="img" alt="My Image 2">
  <p>trailing text content 2</p>
</div>

</body>
</html>

Live demo is here.

This is the least obtrusive method to me because there is no need to change html markup with special attributes or wrapper elements. I wish I could do it with pure CSS with a one-liner but well, we don't live in a perfect world. From usability point of view it appears to be good enough for me.


#18

I like the second version too. After much trial and error I also came to the conclusion that CSS alone could not do it and went to JavaScript. Mine was a bit different, I was trying getComputedStyle and not offsetWidth and would have needed an added wrapper element. So yours is a lot better IMHO. :thumbsup: .


#19

After a bit of testing I found it was not yet perfect in Firefox. The problems I found in Firefox:

  1. When using height: auto I had to also set display: inline-block on the image otherwise javascript was incapable of setting dimensions of images that were not yet downloaded and they shrank to alt text size. I have no idea why display: inline-block is necessary since it doesn't really affect image rendering as opposed to the default display: inline. This was not problematic in this case but sometimes I might want to apply different display rules on images so this method would not allow me to do that.

  2. Although Firefox reserved space for images it didn't reserve space below images that is usual for inline images with the default vertical-align: baseline rule. That space appeared only after the image was downloaded and displayed, which resulted in a slight shift of content under the image. Also, I have no idea why Firefox has to wait for the actual image in order to apply that space but that's what it does in this case. Interestingly, getting rid of height: auto solves the problem, which is also weird since javascript overwrote height immediately so theoretically it shouldn't be a problem...

So finally, I got rid of all CSS styles for images, which gets rid of these problems and is more universal as it allows for more image styling options. The downside is that without javascript the images will not scale at all and will show at their default (large) size. I don't think this is a real problem because from what I can see mobile browsers don't even have the option to turn off javascript - maybe some do but it must be extremely rare for someone to turn off js on a mobile phone. There it goes:

<!DOCTYPE HTML>
<html>
<head>
<title>Image</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style type="text/css">
body {
  margin: 10px;
}
.img {
	outline: 1px solid green;
}
</style>
</head>

<body>
<script>
function setImgAscpectRatio() {
	var img;
	var imgs = document.querySelectorAll('img.img');
	
	for (var i=0; i<imgs.length; i++) {
		img = imgs[i];
		
		var ratio = img.getAttribute('width') / img.getAttribute('height');
		var maxWidth = img.parentNode.offsetWidth;
		var targetWidth = Math.min(img.getAttribute('width'), maxWidth);
		var height = targetWidth / ratio;
		img.style.height = height + 'px';
		img.style.width = targetWidth + 'px';
	}
}

// set image sizes when page html is partially loaded:
var arInterval = setInterval(setImgAscpectRatio, 1000);

document.addEventListener('DOMContentLoaded', function() {
	clearInterval(arInterval);
	setImgAscpectRatio();
	window.addEventListener('resize', setImgAscpectRatio);
});
</script>

<div>
  <p>leading text content</p>
  <img src="delayed-img.php?600x300" width="600" height="300" class="img" alt="My Image">
  <p>trailing text content</p>
  <img src="delayed-img.php?700x200" width="700" height="200" class="img" alt="My Image 2">
  <p>trailing text content 2</p>
</div>

</body>
</html>

Live demo: here.


#20

As far as I can ascertain from the specs an image is by default an inline element and inline elements do not accept dimensions. However once the image has been downloaded it becomes a 'replaced' element and then its paint areas is defined by its intrinsic dimensions or by any attributes specified or by CSS (basically once it has replaced content it behaves much like inline-block).

Therefore for an image not downloaded yet (or not expected to be available) or for an image with a broken url it should default to an inline element and not accept dimensions. Both Chrome and Firefox show this behaviour for missing images while IE/Edge will still honour dimensions which If I am correct is contrary to the specs.

Imagine a user browsing who has specifically turned images off as they are on a slow connection only to find the page full of great big gaps where the images should have been. This makes the reading of the page very awkward. You may help some users but then you break the experience for others. Unfortunately these conundrums are the life of a web developer - you can't always please everyone it seems. :slight_smile:

I'm not keen on this as it makes my browser 'janky' to use when resizing and will be quite an overhead for a page with a lot of images to manage. You can throttle/debounce of course but then you get the image only changing at the end of the resize and sometimes being wider than the window while this is happening which I find just as irritating.

I think you need to be careful that in solving the initial layout problem you don't degrade the ongoing and continual performance of the users browser. I'm not saying what you are doing is wrong and I see it as a good exercise to make things better but I often see that in attempts to solve one problem we actually make it worse further down the line in some un-associated way.

For me the initial page load is part and parcel of the web experience and I'm quite happy for things to move and settle down (usually imperceptibly on a fast connection). I am not happy though to have my browsers stutter every time I try to resize it to make room for other apps on the desktop. My case may be unique but I am a constant 'resizer' as I have many applications open and I like to organise them myself.

I am old school and in the olden days an inline element was not valid as a direct child of the body and for that reason I tend to wrap all my images in a div (or p element). I realise times have changed but old habits die hard and I have found that having your image in a wrapper is very useful in the long term as it offers a nice hook to change and adjust things further down the line or create layout tweaks and changes. However this is a personal preference only and not having a wrapper for an image is perfectly valid although I would still argue the semantics of it. However semantics seem to have slipped a little in html5 - although that has made things easier I guess for most:).

All in all though this a very interesting thread and in the end it probably boils down to a personal preference and I guess you do what you think best :slight_smile: