CSS Image Layouting Before Image Loaded

CSS Image Layouting before image loaded

As Pete already said, you can not do any (automatic) calculations before the image is downloaded, so the browser knows its width and height.

But since you are able to determine the aspect ratio of the image beforehand, you could try a “workaround” by adding an extra placeholder element around the image – and make use of the fact that padding values given in percentage always are calculated based on the width of an element, even for padding-top/-bottom.

That could look something like this:

<div style="position:relative; width:100%; height:0; padding-top:50%;">
<img style="position:absolute; top:0; left:0; width:100%;" src="…">
</div>

This is a div element with no height, but a padding-top – that will give it an actual “height” of 50% of the computed 100% width. (That would be for an image with an aspect ratio of 2:1 – for 3:1 the padding-top value would have to be 33.333% accordingly – and so forth, basically height/width*100.)

That should span up our placeholder even before the image is loaded.

The image itself is positioned absolutely inside this relatively positioned placeholder – that makes sure it gets displayed at the same position in the document.

The only thing that might be problematic is rounding that has to occur for values with decimal points – 33.333% of 1000 pixels would be 333.33 pixels, and the browser has to round that down to 333 pixels. Should the resized image have an actual height of 334 pixels however, it would just flow out of the area the placeholder div is spanning up by that one pixel. May depend on the actual layout (and your fetish for pixel-perfect accuracy) whether that’s acceptable or not.

How to keep an image size intrinsic, before it is loaded, so layout is stable?

Updating for 2021, we now have a widely supported standards-compliant way to do this is with the CSS aspect-ratio property.

You can simply use the height and width like so:

.myimage {
width: 50%;
height: auto;
aspect-ratio: 1280 / 720;
}

This will first calculate the width as 50% of the parent element, then, with the height set to auto (it's best to explicitly set it to auto in case something else changes the height for maintainability), it will use the value in aspect-ratio to calculate the height it should reserve.

This does affect the image element even after the image has been loaded though. Meaning if the aspect-ratio property doesn't match the image's actual aspect ratio, the image will be stretched to fit. You can prevent this by prepending the "auto" value.

aspect-ratio: auto 1280 / 720;

With that, it will use the defined aspect ratio before the image is loaded and the actual aspect ratio is known, then fall back to auto once the browser knows the intrinsic aspect ratio.

This is not supported by non-standard browsers like Internet Explorer and Safari, so if you want to make this work in those purely with CSS, you'll need to continue using the first workaround in kubi's answer (as a side note, the 4th option is not and has never been on track to become part of the standard).

However, this question would likely not even get asked if it were today (at least not by the same person) because there is now an HTML solution which is the old, pre-responsive web design HTML solution. Most modern browsers now use the image element's height and width attributes to calculate the aspect ratio, while CSS height and width override those values unless they are set to auto.

Therefore...

<img src='myimage.jpg' class='myimage' width='1280' height='720' />
.myimage {
width: 50%;
height: auto;
}

This will now reserve the correct amount of vertical height, even in Safari. As of right now, there are still some browser-specific implementation notes to consider.

In Chromium and Firefox, it is currently implemented using the CSS aspect-ratio property with "auto" prepending the calculated aspect ratio.

aspect-ratio: auto attr(width) / attr(height);

Since Safari doesn't support the aspect-ratio property yet as of my writing this, its implementation has a bit more nuance. As a result, it will not reserve space if there is not a valid image value in the src attribute, meaning if you are using old-fashioned JavaScript-based image lazy loading, you will need to include a fallback image.

In any case, you should include height and width attributes on your image tags as much as possible. They actually do something now.

Image with declared width and height renders square before load

You can achieve the desired effect with the following solution.

HTML:

<img src="blank.gif" class="lazy" data-src="foo.png" width="1500" height="1800" alt="bar">  
▲ ▲
║ ╚═══ The class will be used for the lazy loader below

╚═══ Use faulty gif here to hide it from showing before loaded

Hint: If you want the placeholder rectangle to be visible and in one color, consider using a 1x1 px image for blank.gif. It will load
very fast and will stretch nicely to your proportions, filling it with
the color of your choosing.

JavaScript:

/* lazyload.js (c) Lorenzo Giuliani
* MIT License (http://www.opensource.org/licenses/mit-license.html)
*
* expects a list of:
* `<img src="blank.gif" data-src="my_image.png" width="600" height="400" class="lazy">`
*/

!function(window){
var $q = function(q, res){
if (document.querySelectorAll) {
res = document.querySelectorAll(q);
} else {
var d=document
, a=d.styleSheets[0] || d.createStyleSheet();
a.addRule(q,'f:b');
for(var l=d.all,b=0,c=[],f=l.length;b<f;b++)
l[b].currentStyle.f && c.push(l[b]);

a.removeRule(0);
res = c;
}
return res;
}
, addEventListener = function(evt, fn){
window.addEventListener
? this.addEventListener(evt, fn, false)
: (window.attachEvent)
? this.attachEvent('on' + evt, fn)
: this['on' + evt] = fn;
}
, _has = function(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
;

function loadImage (el, fn) {
var img = new Image()
, src = el.getAttribute('data-src');
img.onload = function() {
if (!! el.parent)
el.parent.replaceChild(img, el)
else
el.src = src;

fn? fn() : null;
}
img.src = src;
}

function elementInViewport(el) {
var rect = el.getBoundingClientRect()

return (
rect.top >= 0
&& rect.left >= 0
&& rect.top <= (window.innerHeight || document.documentElement.clientHeight)
)
}

var images = new Array()
, query = $q('img.lazy')
, processScroll = function(){
for (var i = 0; i < images.length; i++) {
if (elementInViewport(images[i])) {
loadImage(images[i], function () {
images.splice(i, i);
});
}
};
}
;
// Array.prototype.slice.call is not callable under our lovely IE8
for (var i = 0; i < query.length; i++) {
images.push(query[i]);
};

processScroll();
addEventListener('scroll',processScroll);

}(this);

Sources:
The Lazyload script can be found here.

Responsive images without layout reflow when they load

The most common way I've seen this addressed is to put all your images in containers and use the padding-bottom property to "pre-allocate" the height.

<div class="responsive-container">
<img src="image.jpg"/>
</div>

To do this, you need to know the aspect ratio of the image to calculate the padding.

Using the Aspect Ratio to work out the height

For example, for an aspect ratio of 16:9 (e.g. 800 x 450px) the height is 56.25% of the width so that will be the value for the padding.

.responsive-container {
width: 100%;
padding-bottom: 56.25%; /* 9/16 = aspect ratio of image */
position: relative;
height: 0;
overflow: hidden;
}

.responsive-container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
}

Different Aspect Ratios

If your images will have different aspect ratios, but you will still know then ahead of time, you set up different classes for each, e.g.

.responsive-container {
width: 100%;
position: relative;
height: 0;
overflow: hidden;
}

.ratio-16-9{
padding-bottom:56.25%; /* 9/16*100 */
}

.ratio-4-3{
padding-bottom:75%; /* 3/4*100 */
}

.ratio-1-1{
padding-bottom:100%;
}

And to use it in the html:

<div class="responsive-container ratio-16-9">
<img src="image.jpg"/>
</div>

Calculate height dynamically

Finally, if you want the CSS to calculate the height dynamically for each image, you can use CSS calc() to calculate the height like this:

calc((image_height/image_width)*100%)

So your CSS would be:
.responsive-container {
width: 100%;
position: relative;
height: 0;
overflow: hidden;
}

And you use it like this:

<div class="responsive-container" style="padding-bottom: calc((426/640)*100%);" >
<img src="image.jpg"/>
</div>

References

  • Responsive images – how to prevent reflow
  • Responsive Images Without Browser Reflow
  • MDN Web Docs for CSS calc()

CSS Column Layout flashing when loading image

I have figured out a solution by inspecting Pinterest's layout:

  1. Add a <div> wrapper around the image, and set the div wrapper's padding top to be image.height / image.width . For example: div-wrapper: { padding-top: 135%; }

  2. make the image position: absolute;.

In this way the container can be pre-defined using the ratio of the image, and the column can be computed nicely.



Related Topics



Leave a reply



Submit