Responsive Lazy Loader

Chapter 5 11 mins

Learning outcomes:

  1. Using responsive images
  2. Restructuring the HTML and CSS

Introduction

In the previous Lazy Loading - Image Dimensions chapter, we saw how to avoid document reflow when lazy loading an image, by predefining its width and height values. The idea works pretty well on larger devices however this is not the case with smaller ones as well.

Absolute dimensions for an image aren't responsive at all — on a small screen, images with absolute width and height values might go out of the viewport. What we need instead are relative dimensions, and hence responsive images.

Making the vertical dimension relative

Responsive designing which is designing sites that scale perfectly on different devices is of paramount significance these days when a large percentage of internet users comes from mobile devices.

Therefore we have to resort back to make our image's dimensions relative, using percentage values. However, there is a slight problem with this.

For width we can use 100% to fit the container element into the available width, but for height we can't use percentage values.

See the explanation for why does height not take percentage values in some occasions.

Hence we have to use some other thing, apart from height, which can work with percentage values and at the same time increase the vertical dimension of the lazy image to match the natural proportions of the image.

Can you think of any?

Which of the following properties can give .lazy some visual vertical height, apart from height?

  • margin
  • padding
  • min-width
  • max-width

The solution is to use padding.

However, since, we are only concerned with the vertical dimension, i.e the height, we can instead use padding-bottom or padding-top. We will go with padding-bottom.

padding-bottom can take percentage values which are relative to the 100% width of an element. It is the property we will be replacing height with, but we aren't over even as of yet!

The last question is what value to give to padding-bottom?

If an image has a width of 500px and height of 125px, what would be the height of the image relative to its width which is considered to be 100%? In other words, what percent of the width is the height?

To calculate this, we just need to divide the height by the width and multiply the result by 100. In this case, the final value is 25% (= 125 / 500 * 100).

Generally we could express this as follows:

padding-bottom:(heightOfImage / widthofImage) * 100

Therefore now we can alter the backend function getDim() and throw in a string containing padding-bottom as well:

<?php
function getDim($src) {
    $size = getimagesize($src);
    echo 'style="padding-bottom: ' . ($size[1] / $size[0] * 100) . '"';
}
?>

Now that we know how to calculate the value of padding-bottom it's time to incorporate it in the HTML markup.

Let's start with a very basic question.

Where to place padding-bottom? Inside .lazy or .lazy_img?

Well, padding-bottom has to go inside .lazy, not .lazy_img, because if we give it to .lazy_img then the actual image will have a padding which we would have to remove when it is loaded.

By giving the padding to .lazy we can easily position the lazy image inside it using absolute positioning.

So in the first step of restructuring our markup, we ought to include our PHP getDim() function within .lazy, rather than in .lazy_img, as we did in the previous chapter:

<div class="lazy" <?php getDim("image.png"); ?>>
    <img class="lazy_img" data-src="image.png">
</div>

And this makes a responsive lazy loader, but not without some problems.

Laying out the CSS

As we just saw above, padding-bottom was given to .lazy in order to increase its vertical dimension.

Since padding increases the inner space of a given element, where its children are layed out after this inner space, we have to write some CSS in order to position the lazy image correctly.

This is accomplished by the CSS below:

.lazy {
    position: relative;
    width: 100%;
}
.lazy_img {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
}

In line 2, position: relative is given in order to make .lazy_img's positioning operate relative to .lazy.

In line 3, width: 100% makes .lazy fill all of the available width of its parent i.e <body>. Without it .lazy, would have 0px of width and no image will be seen in view.

position: absolute gets the .lazy_img position and scale freely inside its container. Lines 7-8 make it fill the dimensions of its container while lines 9-10 make it position in the correct place. Without lines 9-10 the image will be positioned below the padding of .lazy.

And with this code set up we have finally arrived at a fully responsive, reflow-preventing lazy loader. Now we can set resize our document to any dimension and the lazy image would scale proportionately and elegantly.

Responsive Lazy Loader

Restructuring the HTML

Let's suppose we have an image 30px wide and 30px tall. It's a very tiny image worth only a few bytes.

Now with the code above in place, what would happen is that this image would be scaled to fit all of the available width of its parent which is <body> and this will likewise increase its height as well.

The image is, no doubt, responsive. However, it's enlarged to fill the available width of <body>. And in this case, the enlargement is so much that pixelation is clearly visible.

To avoid this, we can employ a clever technique, detailed as follows:

As long as the available width of <body> (or any other element within which .lazy is placed) is greater than the width of the image, we go with the width of the image.

However, when the width of <body> reduces down to such a value that it becomes lesser than that of the image, then we go with the width of <body> — by using width: 100% on .lazy.

In other words, we place an uppe bound on the width of the image — it can't go beyond that limit. In the case above, this would mean that the image can't have a width greater than 30px, which is its natural width.

Does anything come to your mind when we say 'upper bound on the width of the image'? Is there any such thing in CSS?

Definitely yes, it's the max-width CSS property.

We'll need to use it to bound our lazy image's width by an upper limit.

The question is where to apply max-width — on .lazy or .lazy_img.

Let's see it together...

If we apply max-width to .lazy_img, the image's width would indeed be constrained but its vertical height won't be in proportion to its width.

This is because padding-bottom, which is set on .lazy, gets computed relative to <body>, not relative to the width of the lazy image, which is what we desire.

So, .lazy_img isn't the right choice for max-width.

Moving on to .lazy. If we apply max-width on .lazy, the same scenario happens, as with .lazy_img (detailed above). The image's width gets constrained, but its height is not in sync with its width.

Now what?

Think for a moment that padding-bottom operates relative to the parent of .lazy. Therefore, if we could somehow put this width constraint on .lazy's parent i.e <body>, we can accomplish what we want to.

But just to solve the issue of a single lazy image, we can't go on and modify the width of <body> — doing so would affect all the content on the page.

What's the solution?

Give a new parent to .lazy, that has the max-width constraint set on it.

In this way, padding-bottom would get computed relative to this new parent's width, and hence the image's height would be in sync with the image's width.

Perfect!

Below we first change the HTML of our lazy image by giving a container to .lazy. It's called .lazy-cont:

<div class="lazy-cont">
    <div class="lazy" <?php getDim("image.png"); ?>>
        <img class="lazy_img" data-src="image.png">
    </div>
</div>

Next, we retrieve the natural width of the image and set .lazy-cont's max-width to this value:

<div class="lazy-cont" style="max-width: <?php echo getimagesize('image.png')[0]?>">
    <div class="lazy" <?php getDim('image.png'); ?>>
        <img class="lazy_img" data-src="image.png">
    </div>
</div>

And voila!

We have successfully created a fully responsive lazy loader, where the word 'pixelation' doesn't exist.

Live Example

One thing we can additionally do is to refactor our function getDim(), to create all the markup for a given lazy image.

The idea is that we give it the source URL of the image, to which it returns back the complete markup for it:

Since, its purpose has changed, we've changed the name of the function to lazyImage():

<?php
function lazyImage($src) {
    $size = getimagesize($src);
    echo '<div class="lazy-cont" style="max-width: ' . $size[0] . 'px"><div class="lazy" style="padding-bottom: ' . ($size[1] / $size[0] * 100) . '%"><img class="lazy_img" data-src="' . $src . '"></div></div>';
}
?>

Now to give a lazy image, we just need to call the function, as shown below:

<?php lazyImage('image.png'); ?>