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

I’m an old timer too, I remember the problems that happened when inline elements were children of the body and siblings with blocks. Besides that, the first thing that comes to mind for me is image captions. A wrapping div around the image paves the way for captions. Think about that client that comes along after you’ve write the html for 50+ pages and says, " I think I want some Captions under my images".

To bring the html up to speed with html 5 one could consider using the <figure> element. It was also old timers that helped write the html5 specs. There’s a reason the <figure> is a block element that accepts Flow content.

Take a look at the html structure for the figure, at it’s bare minimum you would get this so you wind up with block children of the body…

<!-- Just a figure -->
<figure>
  <img
  src="https://developer.cdn.mozilla.net/media/img/mdn-logo-sm.png"
  alt="An awesome picture">
</figure>
<p></p>
3 Likes

Yes good example :slight_smile:

That’s also reminded me that replaced elements cannot have the pseudo elements :before or :after applied (unless its a broken image and then not replaced) which means that you have no hooks for anything fancy you may wish to do later on. Having a wrapping element also immediately gives you access to these pseudo elements should you wish ‘non-content’ embellishments.:slight_smile:

4 Likes

Great, I didn’t know that some of my doubts about how images are displayed are actually documented in the specs! But in this particular case I think you are partially right and partially wrong. It is correct that an image by default is an inline element and does not accept dimensions but it becomes a replaced element when the element already has intrinsic dimensions (e.g. from the dimension attributes or CSS rules) and the user agent has reason to believe that the image will become available and be rendered in due course - second case in this paragraph. So images waiting to be downloaded, to my mind, should accept dimensions (but broken images shouldn’t as you observed). I think this was an unusual combination of initial height: auto followed by setting dimensions in js that threw Firefox off.

I haven’t thought of this use case but you are certainly right and therefore broken images or those not meant to be downloaded certainly should stay inline and not accept dimensions. From my tests all browsers except IE and Edge follow this behaviour and these experiments of mine with js do not affect it.

This is a good point, we shouldn’t cause slow downs for browser resizing (I’m also a ‘resizer’!). This would need some testing to know how much this script affects the speed. However, just for fun I put the for loop in my script in another for loop with 1000 iterations and I noticed only a slight slow down in browser resizing, for 10 or 100 iterations I didn’t even notice - this would indicate there is quite a lot of headroom. And still, the slow downs don’t affect main browser window resizing since modern browsers handle content in a separate process so only a few fps drop might be noticeable within the browser window.

Anyway, wanting to improve it I’ve come up with an idea that we could cut down on performance penalty if we use js to resize only images which are waiting to be downloaded - those that are fully available can be safely delegated back to pure CSS resizing with height: auto. This would go like this:

<!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 red;
}
</style>
</head>

<body>
<script>
function setImgAscpectRatio() {
	var imgs = document.querySelectorAll('img.img:not([data-loaded])');

	for (var i=0; i<imgs.length; i++) {
		var img = imgs[i];
		
		if (!img.complete) {
			// image not loaded: resize with js
			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';			
		} else {
			// when image is loaded apply pure CSS resizing from now on
			img.style.outlineColor = 'black';
			img.setAttribute("data-loaded", "1");
			img.style.removeProperty("width");
			img.style.maxWidth = "100%";
			img.style.height = "auto";
		}
	}
}

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

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

<div>
  <p>leading text content</p>
  <img src="600x300.php" 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.

I set outline colour to black only to indicate the image has been put back to CSS resizing. This should have very minimal performance penalty once the images have been downloaded. There have a been a few issues I needed to deal with, for example IE doesn’t fire onresize events before all images get downloaded so js resizing does not work then - so I added the window.onload event just to ensure things are displayed properly in the final stage. I also had to add setTimeout(setImgAscpectRatio, 0); in the document ready event to handle certain cases when scrollbars could have been erroneously included in calculation of 100% width of the parent element.

This could be further enhanced with throttle/debounce techniques if necessary (but I think that would be extreme). I feel the more I dig into it the more subtle behaviours and issues come up to cope with and this also makes me think whether it’s all worth the hassle. I have a feeling I’m trying to code something that browsers should be able to do on their own with a one-line CSS rule…

Well, yes, I certainly agree that it’s good practice not to use dangling inline elements but this is a somewhat different topic altogether. Here I was rather concerned with using wrapper elements for the purpose of applying proportional dimensions with the padding trick. To keep html clean I want a universal solution, for example, I will usually have page content like this:

<p>This is some text.</p>
<p><img src="img.jpg" with="500" height="255" alt="my dog"></p>
<p>This is some more text.</p>
<div style="float: right"><img src="img.jpg" with="200" height="100" alt="my snake"></div>

This might come from an editor in an admin panel or some outside source and I just don’t want to be concerned with fixing the html just for the purpose of image scaling on small screen devices. I simply want this html to just work on any device without modifications. That’s why I said I don’t want to use wrapper divs for this purpose.

1 Like

Yes that’s why I pointed to that part in the specs and my take on it is that if the image url is invalid/missing which I guess will be ascertained very quickly (as no image will need to be downloaded) the browser will almost immediately determine that the image is not ‘available’ and therefore not behave like a replaced element.

Reading specs is always difficult and you often have to read between the lines or accept some ambiguity so I may be wrong. However that is certainly how all browsers except IE/Edge seem to treat them.

I usually set my images to display:block in my reset styles by default anyway as that is how I like them to behave most of the time.:slight_smile:

2 Likes

I found it very interesting to experiment with. I guess to get a better understanding it would require digging into actual browser engine code. My conclusions, as wrong or right as they may be are:

Giving an image height: auto results in it getting height: 0px unless it loads in. I had assumed it would fallback to the attribute value. Thinking about it more I guess either could make sense depending on which was more important, “wasting” screen real estate to preserve layout design or “saving” screen real estate at the possible expense of disrupting screen layout.

When an image fails to load, the alt text is displayed as unwrapped text. In some ways it can be styled as though it was <img>alt text</img> (eg. it accepts color style), but frustratingly, for the most part the alt text behaves as though it is not part of the DOM.

This adds to my “CSS wishlist”:

  • a “parent” combinator (though :not looks promising)
  • element level @media queries
1 Like

Well, yes, there is no doubt about that and I fully agree with you but you are talking about broken images. I think I don’t care so much about styling broken images - let’s assume we avoid them or let them become inline for those who turn off images. The more interesting thing here is how to style (set dimensions of) images waiting to be downloaded - I mentioned that part of the specs because it says they should behave like replaced elements and they do. I think the specs are not ambiguous here after reading carefully all the sentences. BTW, it’s good to know this is standardized since I thought browsers choose to behave as they please without any forethought.

That’s what I first assumed, too, but alas you are right - height: auto overwrites the height attribute and initially results in zero height. My wish list would be to have a rule like height: auto-proportional which would apply the height based on the html attributes instead of dimensions encoded in the image file.

Yes something like this with a pseudo could come in handy

img:notloaded { 
  height: auto-proportional; 

or height: 0 or height: height-attr etc.

1 Like

Yes we must have been talking at cross purposes a little.:slight_smile:

I still think that unless the browser has ascertained there is indeed an image to load then the img will remain as an inline element (not a replaced element) until such time the browser says “Ok we found one” and then it will honour the width and height attributes while it downloads the resource for the replaced element.

That seems to be working better :slight_smile:

But from my tests it’s the other way round: an image is a replaced element immediately (and its attribute dimensions are honoured immediately) and when the browser comes to the conclusion “I have tried but I can’t load the resource” then it is changed to an inline element. Browsers are optimistic that the image will be downloaded (“in due course”) and only switch to inline display on failure (except in IE, which keeps it replaced). That is all great unless we try to apply automatic size for responsive design.

Yes, but actually I wanted to make it even better by switching to CSS-only resizing earlier: not when the image is completely loaded but already when it has started being downloaded (this can be detected by using naturalWidth or naturalHeight property instead of complete). But a peculiar bug on mobile Firefox prevented me from doing that.

It appears that image rendering goes through several different stages and in each stage browsers may choose a different rendering for it:

  1. Image is waiting to be downloaded - no image available yet.
  2. Image is in the process of being downloaded and its dimensions are already known from the file header (image is usually displayed progressively).
  3. Image has finished downloading.

In all these stages the image is a replaced element and its width and height attributes are honoured (unless overridden with css or its url is invalid). Theoretically, I would expect the image (or the space for it) to be rendered the same in all these stages but there are subtle differences. One of them, that I’ve already mentioned, is that Firefox doesn’t apply bottom vertical space at stage 1.

Another one is that mobile Firefox may not always figure out dimensions from image file at stage 2. At stage 2 the browser has already fetched image header which contains dimensions so it should be capable of computing img size when height: auto is set. But mobile Firefox for some reason doesn’t always do that and the result is height: 0 - so I have to wait until stage 3 before I can safely apply height: auto in all browsers.

There may be other subtle differences between stage 2 and 3 like different image scaling algorithms - some browsers may use a faster algorithm at progressive stage 2 and switch to a more accurate one at stage 3. I can’t remember which browsers do that but I’ve noticed it on a few occasions.

The specs are not so accurate here and do not split the process into three stages, they seem to describe only two states: image not loaded (yet) and image loaded. Another stage is image broken but that is a different situation altogether.

1 Like

[off-topic]
I was curious to know how the AmpProject handled non-existent images:


<!doctype html>
<html amp lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">

<title>Hello, AMPs</title>

<link rel="canonical" href="http://example.ampproject.org/article-metadata.html">

<script async src="https://cdn.ampproject.org/v0.js"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "NewsArticle",
  "headline": "Open-source framework for publishing content",
  "datePublished": "2015-10-07T12:02:41Z",
  "image": [
    "logo.jpg"
  ]
}
</script>

<style amp-boilerplate>
body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}
</style>

<noscript><style amp-boilerplate>
 body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}
</style></noscript>

<style amp-custom>
body {
  margin: 10px;
}
li {
  display:inline-block;
  margin: 0 2em;
}
dl {
  margin: 0 auto;
  outline:solid 1px #eee;
  display:inline-block;
}
amp-img {
  outline: 1px solid green;
}
.tac {
  text-align: center;
}
.tal {
  text-align: left;
}
</style>

</head>
<body>
  <h1>
  	<a href="https://AmpProject.org">
  		AmpProject.org
  	</a>
  </h1>

  <ul>
	<li>
		<a href="http://tests.fotokraina.com/resp-img/test1.php">
			Test 1
		</a>
	</li><li>
		<a href="http://tests.fotokraina.com/resp-img/test2.php">
			Test 2
		</a>
	</li><li>
		<a href="http://tests.fotokraina.com/resp-img/test3.php">
			Test 3
		</a>
	</li> 
  </ul>
    	
	<div class="tac">
		<dl class="tal">
		  <!-- 
		  	img src="delayed-img.php?600x300" width="600" height="300" class="img" alt="My Image"
		  -->
		  <dt>leading text content</dt>
		  <dd>
			  <amp-img 
			  	src="http://via.placeholder.com/600x300" 
			  	alt="Welcome" 
			  	width="600" height="300"
			  ></amp-img>
			</dd>  
		  <dd> &nbsp; </dd>

		  <!--
		  	img src="delayed-img.php?700x200" width="700" height="200" class="img" alt="My Image 2"
		  -->
		  <dt>trailing text content 2</dt>
		  <dd>
		  	<amp-img 
		  		src="http://via.placeholder.com/700x200" 
		  		alt="Welcome" 
		  		width="700" height="200"
		  	></amp-img>
		  </dd>
		  <dd> &nbsp; </dd>

		  <dt>Non-existent image</dt>
		  <dd>
			  <amp-img 
			  	src="http://Non-existent-via.placeholder.com/600x300" 
			  	alt="Welcome" 
			  	width="600" height="300" 
			  ></amp-img>
			</dd>  
		</dl>
	</div>	

</body>
</html>

[/off-topic]

and the result is …?

For the supplied script a dimensioned placeholder was rendered.

Just noticed a new feature where the previous obligatory image dimensions are not required as long as the layout is set correctly. The image will resize to the parent container! This will be ideal for thumbnails where I am currently using my server to find and pass the thumbnail dimensions. AmpProject’s faster server can do the processing which will hopefully decrease rendering time.

Later today I hope to test the new features and also upload the supplied script.

1 Like

New topic created:

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.