Jump to content
GrahamUK33

Lazy Loading images and video

Recommended Posts

I am looking at ways of decreasing page weight without optimising images any more than I have to, I am mainly thinking about mobile devices on a poor 3G connection that will be more effected than a mobile device using a 4G connection or connected via Wi-Fi.

 

What I am looking for is a cross browser solution, but at this stage I don't know what the problems are using a Lazy Loading solution and if the benefits would outweigh them.

 

What are your experiences using a Lazy Load solution?

Share this post


Link to post
Share on other sites

how are you serving your video anyway? youtube, vimeo, <video> ? is it a background video or a streaming 'normal' one?

Share this post


Link to post
Share on other sites

The video is hosted on the same webserver as the website, and called up using <video> the video is not a full background video it is in a control box.

Share this post


Link to post
Share on other sites

The video on the page is not set to auto run, it just shows a still frame.

I ran some tests using Pingdom Website Speed Test and found that there is no receive time from the video, it shows a wait time of 36ms. I set the video preload to metadata, this did not make any difference, so I set the preload to none, this also did not make much difference, apart from not showing a still frame on the page.

I have left removed the preload as I think showing a still frame may entice a visitor to the page to play the video.

Doing the Pingdom test does show that the images are the main culprits of the time spent downloading the page.

Share this post


Link to post
Share on other sites

I've done .a lot of lazy loading over the years, it's pretty straightforward.
For performant lazy loading (Most are not!) you want a function that will get called when the page first renders and also when screen is resized.  This function will get all the elements that you wish to be lazy-loaded (Images, video etc) via a hook such as `data-lazy-load` and store the y coordinate and the url, the node and if it's loaded into an array of objects.  e.g.

[{
	y: 1000,
	url: 'http://my-cdn.com/img/123.jpg',
	node: <the actual node />,
	loaded: false
}, {
	y: 1050,
	url: src: 'http://my-cdn.com/img/abc.jpg'
	node: <the actual node />,
	loaded: false
}]

You then have another function that gets called on scroll events (It should be debounced to avoid jank).  This function gets the position of the window in relation to the document, and the window height.  It will loop over the array of lazy-resources (above) to check if the resource is within the viewport (or close to the viewport if you want to load things in a little earlier).  The `src` attribute is for those found within and have not already loaded are then updated with the `url` from the object.

 

(function() {
	var resources;

	function gatherResources() {
		resources = [];
		var elements = document.querySelector('[data-lazy-load="true"]');
        var elementsArray = [].slice.call(elements, 0);
        elementsArray.forEach(function(el) {
			resources.push({
				y: el.getBoundingClientRect().top + document.documentElement.scrollTop,
				url: el.getAttribute('src'),
				node: el,
				loaded: false
			});
        });
    }

	function handleScroll() {
      	var windowY = window.pageXOffset;
      	var windowHeight = window.innerHeight;
      	resources.forEach(function(el){
			if (el.y >= windowY + windowHeight && !el.loaded) {
				el.node.src = el.url,
				el.loaded = true
			}
		});
    }

    window.addEventListener('scroll', function() {
		window.requestAnimationFrame(handleScroll);
	});

	window.addEventListener('resize', gatherResources);
	window.addEventListener('DOMContentLoaded', gatherResources);
})();

Something like the above should do the trick.  This is just really psuedocode just to highlight the thought process, you'll have to change depending on the framework you are using (React, jQuery or native DOM - as is here)

Edited by rbrtsmith

Share this post


Link to post
Share on other sites

Thanks Robert, I did managed to find some code that does works, but I don't know how well it works as my knowledge of JavaScript is very limited. Are you able to add some of the features in the code above to what I have please.

I have lazy.gif that loads, and once scrolled up the image is replaced by photo1.jpg, ideally it would be better to load just before the image comes in to view as I have noticed that there is a delay while photo1.jpg downloads and replaces lazy.gif

 

On the HTML page

<img class="lazyload" src="images/lazy.gif" data-src="images/photo1.jpg" alt="description of photo">

at the bottom of the page just before </body> is where the JavaScript is called up.

<script src="js/lazyload.js"></script>

 

JavaScript - lazyload.js

window.onscroll=function(ev){
  lazyload();
};
function lazyload(){
  var lazyImage=document.getElementsByClassName('lazyload');
  for(var i=0;i<lazyImage.length;i++){
    if(elementInViewport(lazyImage[i])){
      lazyImage[i].setAttribute('src',lazyImage[i].getAttribute('data-src'));
    }
  }
}

function elementInViewport(el){
  var rect=el.getBoundingClientRect();
  return(
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
}

 

Share this post


Link to post
Share on other sites
21 hours ago, GrahamUK33 said:

window.addEventListener('scroll', function() {
  window.requestAnimationFrame(lazyLoad);
});

function lazyload(){
  var lazyImage=document.getElementsByClassName('lazyload');
  for(var i=0;i<lazyImage.length;i++){
    if(elementInOrNearViewport(lazyImage[i])){
      lazyImage[i].setAttribute('src',lazyImage[i].getAttribute('data-src'));
    }
  }
}

function elementInOrNearViewport(el){
  var rect=el.getBoundingClientRect();
  var offset = 200;
  return(
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + offset &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth) + offset
  );
}

 

You can see the `offset` value in the function, adjust that if necessary to meet your requirements.  Of course if the user is scrolling quickly then you will see the image load in.  That is fine and trying to avoid that will just lead to a world of pain so you're better off just accepting that this will happen.

I have also took the liberty of removing
 

window.onscroll=function(ev){
  lazyload();
};

And replacing it with a more modern equivalent and also added requestAnimationFrame which should help with your scroll performance.

Edited by rbrtsmith

Share this post


Link to post
Share on other sites

Overall the code above is still pretty horribly written but it should work for your purposes.

It's querying the DOM for those lazy load elements and calculating their position every single time a scroll even fires - which is a LOT!  The original solution I posted caches those values and only updates them if a resize event fires.

Grabbing data from an in-memory cache is extremely cheap.  Querying the DOM to find nodes and then calculating their position etc is in relation very expensive.


However my suggestion of caching does add quite a bit of complexity and maybe overkill if your page doesn't have much lazy loaded content.

Edited by rbrtsmith

Share this post


Link to post
Share on other sites

Thanks for having a look at the JavaScript, the page I am testing is http://www.newburyhistory.co.uk/ww2-newbury-spitfire this has quite a few images. The page is loading with the image lazy.gif, but it is not being replaced with the image that should be displayed. Is their something missing in the code somewhere?

 

16 hours ago, rbrtsmith said:

However my suggestion of caching does add quite a bit of complexity and maybe overkill if your page doesn't have much lazy loaded content.

Have a look and see what you think. The whole purpose to this exercise is to speed the loading of the page up without loosing any of the images mainly for visitors on slow internet connections.

Share this post


Link to post
Share on other sites

It's worth bearing in mind that this won't really help the initial page load from a users perspective as images are all loaded asynchronously (non blocking).
What lazy load helps with is server load, and the amount of data that visitors who don't scroll down the page encounter.

You will get a bigger win by focusing your attention onto compressing the images effectively.  If you find many users do not scroll then lazy loading can be useful but it comes at a cost - complexity or reduced scroll performance.

Share this post


Link to post
Share on other sites

I did some testing (Google Chrome Lighthouse) using the original code that I had, and found that the speed on the page loading was quicker and the page was a third smaller. Once the page was scrolled the other images would download and be visible.

I didn't want to reduce the images anymore than I have to keep some quality.

Share this post


Link to post
Share on other sites

If lazy.gif is just a 1px transparent GIF, you can use a Base64 encoded version to reduce HTTP requests. Even though the file is small, you still have the overhead of going back and forth to retrieve the image from the server.

http://png-pixel.com

<img class="lazyload" data-src="images/photo1.jpg" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="description of photo">

 

Share this post


Link to post
Share on other sites
4 hours ago, Jack said:

If lazy.gif is just a 1px transparent GIF, you can use a Base64 encoded version to reduce HTTP requests. Even though the file is small, you still have the overhead of going back and forth to retrieve the image from the server.

http://png-pixel.com


<img class="lazyload" data-src="images/photo1.jpg" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="description of photo">

 

Thanks Jack, that is worth some thought for page weight savings.

Share this post


Link to post
Share on other sites

are you serving different sized images at different viewport widths? so mobile gets a smaller version than desktop? 

Share this post


Link to post
Share on other sites

I have just the one image that serves both Desktop and Mobile. I can have an image for Desktop and another image for Mobile if the page is still not quick enough using a mobile on a 3G connection. I think this approach is what was suggested by @rbrtsmith with the code in his first post, along with the Lazyload code as well.

At the moment, the code loads the image lazy.gif, but it is not being replaced with the image that should be displayed. there maybe some additional code needed.

Share this post


Link to post
Share on other sites

The lighthouse app in your example is measuring total page load time.  The important point is the initial render and the time to first interaction which happens often long before the total page load.
If the initial render happens quickly the user will perceive the page to be fast, they don't care if an image 10,000 pixels out of view is still downloading.  Speed is all about perception so lazy loading can help a little but it's biggest win is on sites where users don't often scroll to the bottom of the page.

Share this post


Link to post
Share on other sites

Any idea to what I need to add to the JavaScript code to enable it to work as intended? lazy.gif is being loaded, but not being replaced when scrolling.

Share this post


Link to post
Share on other sites

Does anybody have an idea how lazy loading techniques (where the src attribute are changed on scroll) affects SEO. Search engines dont really have a viweport and dont ever scroll? Will the images ever be indexed?

Share this post


Link to post
Share on other sites

@rbrtsmith I still have a problem with the code not replacing lazy.gif what needs to be added to the JavaScript to solve this issue?

 

window.addEventListener('scroll', function() {
  window.requestAnimationFrame(lazyLoad);
});

function lazyload(){
  var lazyImage=document.getElementsByClassName('lazyload');
  for(var i=0;i<lazyImage.length;i++){
    if(elementInOrNearViewport(lazyImage)){
      lazyImage.setAttribute('src',lazyImage.getAttribute('data-src'));
    }
  }
}

function elementInOrNearViewport(el){
  var rect=el.getBoundingClientRect();
  var offset = 200;
  return(
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + offset &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth) + offset
  );
}

Edited by GrahamUK33
current code added

Share this post


Link to post
Share on other sites
16 hours ago, Nillervision said:

Does anybody have an idea how lazy loading techniques (where the src attribute are changed on scroll) affects SEO. Search engines dont really have a viweport and dont ever scroll? Will the images ever be indexed?

I'm not sure indexing images in Google will help with your ranking given Google has no idea what the image actually represents without alt tags.

Share this post


Link to post
Share on other sites
1 hour ago, GrahamUK33 said:

@rbrtsmith I still have a problem with the code not replacing lazy.gif what needs to be added to the JavaScript to solve this issue?

 

window.addEventListener('scroll', function() {
  window.requestAnimationFrame(lazyLoad);
});

function lazyload(){
  var lazyImage=document.getElementsByClassName('lazyload');
  for(var i=0;i<lazyImage.length;i++){
    if(elementInOrNearViewport(lazyImage)){
      lazyImage.setAttribute('src',lazyImage.getAttribute('data-src'));
    }
  }
}

function elementInOrNearViewport(el){
  var rect=el.getBoundingClientRect();
  var offset = 200;
  return(
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + offset &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth) + offset
  );
}

I see a typo: lazyload declared function and lazyLoad call.  That should fix it given the two functions will be hoisted.

Share this post


Link to post
Share on other sites
6 minutes ago, rbrtsmith said:

I see a typo: lazyload declared function and lazyLoad call.  That should fix it given the two functions will be hoisted.

I have changed the lazyLoad to lazyload, but still not replacing the lazy.gif on scroll.

Share this post


Link to post
Share on other sites

This code seems to work, but I don't know what 'scroll' in the first line does. Also I found that the code does not work without , again no idea what they do.

The process of replacing the lazy.gif image with another image now happens when lazy.gif is partly in the viewport which is much better.

window.addEventListener('scroll', function() {
  window.requestAnimationFrame(lazyload);
});

function lazyload() {
  var lazyImage = document.getElementsByClassName('lazyload');
  for (var i = 0; i < lazyImage.length; i++) {
    if (elementInOrNearViewport(lazyImage[i])) {
      lazyImage[i].setAttribute('src', lazyImage[i].getAttribute('data-src'));
    }
  }
}

function elementInOrNearViewport(el) {
  var rect = el.getBoundingClientRect();
  var offset = 200;
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + offset &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth) + offset
  );
}

Is there anything else that can be added to the code to make it run better/smoother?

Share this post


Link to post
Share on other sites
On 6/2/2018 at 6:24 PM, GrahamUK33 said:

This code seems to work, but I don't know what 'scroll' in the first line does. Also I found that the code does not work without , again no idea what they do.

The process of replacing the lazy.gif image with another image now happens when lazy.gif is partly in the viewport which is much better.


window.addEventListener('scroll', function() {
  window.requestAnimationFrame(lazyload);
});

function lazyload() {
  var lazyImage = document.getElementsByClassName('lazyload');
  for (var i = 0; i < lazyImage.length; i++) {
    if (elementInOrNearViewport(lazyImage[i])) {
      lazyImage[i].setAttribute('src', lazyImage[i].getAttribute('data-src'));
    }
  }
}

function elementInOrNearViewport(el) {
  var rect = el.getBoundingClientRect();
  var offset = 200;
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + offset &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth) + offset
  );
}

Is there anything else that can be added to the code to make it run better/smoother?

i'm confused, your message is saying it doesn't work and then it does work?
In answer to your question `addEventListener` is a DOM function that takes the event type (in this case scroll) and then a callback function that is called every time that event type is called.
I suggest it be a good idea to learn some JavaScript basics, good places to start are Treehouse and CodeSchool once you have a grasp on these then it will be far easier for you to write your own functions and debug things when they are not working.  `console.log()` can be useful for this.  Also learn the Chrome developer tools - this is what I used to determine the typo error.

As for making it run better I suggested caching earlier in this thread explaining briefly how you'd do this. learn what I suggested and you'll have no issues writing the code for that.
Otherwise just drop the idea of lazy loading as it's only really useful if a decent percentage of visitors don't scroll and you wish to reduce their data transfer.  I repeat from above it does not help with initial render / interaction performance. (The metrics your users really care about)

Every feature or function you add has a cost - performance, complexity etc.  It's upto you to figure out if the benefit outweighs the cost.  With lazy loading it is not always worth it (complexity cost), especially if you don't have an intimate understanding of what that code is doing.  If we add caching it reduces the scroll performance issues but adds complexity on top of what is already there... Is this cost worth it?..

Edited by rbrtsmith

Share this post


Link to post
Share on other sites

I have found JavaScript very hard to get to grips with, but HTML and CSS have been much better. I have found that it doesn't matter how much I look at JavaScript, it just looks alien.

With the aid of Forums like this, people like me can get help from experts such as yourself with the things we can't grasp. 

The code shown on my last post does seem to work.

Share this post


Link to post
Share on other sites

I also found JavaScript much more difficult to get to grips with than HTML and CSS.  The problem was I didn't spend much time actually learning the language.  Things progressed better once I started to do that.  It still took significant effort but it was totally worth it.
The courses I suggested would really help you with this.  You will have limited understanding by just building stuff, it's important to balance building stuff with learning the underlying theory.
 

Share this post


Link to post
Share on other sites
16 hours ago, GrahamUK33 said:

I have found JavaScript very hard to get to grips with, but HTML and CSS have been much better. I have found that it doesn't matter how much I look at JavaScript, it just looks alien.

With the aid of Forums like this, people like me can get help from experts such as yourself with the things we can't grasp. 

The code shown on my last post does seem to work.

Fortunately, Javascript doesn't have much in the way of syntax compared to other languages, once you start getting used to working with objects and functions, you will already start to feel a lot more comfortable. It's easy to see curly braces and the function keyword everywhere, as a beginner, and feel completely overwhelmed. Once you've worked with the language for a bit you get used to it.

Quote

Every feature or function you add has a cost - performance, complexity etc.  It's upto you to figure out if the benefit outweighs the cost.  With lazy loading it is not always worth it (complexity cost), especially if you don't have an intimate understanding of what that code is doing.  If we add caching it reduces the scroll performance issues but adds complexity on top of what is already there... Is this cost worth it?..

Absolutely. I also heard something from Addy Osmani recently where he said something like modern browsers like Chrome delay the rendering of images outside of the viewport to low priority to improve the first render.

Another way to do this nowadays would be to use the Intersection Observer API, as it can be polyfilled to use scroll events instead where it's not supported - https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API.

Share this post


Link to post
Share on other sites
22 hours ago, Jack said:

Another way to do this nowadays would be to use the Intersection Observer API, as it can be polyfilled to use scroll events instead where it's not supported - https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API.

This looks like a really nice API nice find!

Share this post


Link to post
Share on other sites
On 6/2/2018 at 4:41 PM, rbrtsmith said:

I'm not sure indexing images in Google will help with your ranking given Google has no idea what the image actually represents without alt tags.

Image indexing is a huge chunk of what Google does. Properly formatted with alt text and captions with supporting content and you can rank really well on an image search. Using lazy load means google often can't index the full page so you can miss out on a load of SEO opportunities.

As you suggested, a set of optimised images doesn't take long to load. The lazy loading on the spitfire page makes for a very poor UX especially if you don't have the image fully in the viewport, all you see is the loading gif.

@GrahamUK33 Because the images load as you scroll the presentation isn't the best. Most of the images are very small (around 50kb) so there is no need for a lazy load. Make the page wider and drop the image background so visitor can see the full sized images.

 

Share this post


Link to post
Share on other sites
22 hours ago, fisicx said:

Image indexing is a huge chunk of what Google does. Properly formatted with alt text and captions with supporting content and you can rank really well on an image search. Using lazy load means google often can't index the full page so you can miss out on a load of SEO opportunities.

 As you suggested, a set of optimised images doesn't take long to load. The lazy loading on the spitfire page makes for a very poor UX especially if you don't have the image fully in the viewport, all you see is the loading gif.

@GrahamUK33 Because the images load as you scroll the presentation isn't the best. Most of the images are very small (around 50kb) so there is no need for a lazy load. Make the page wider and drop the image background so visitor can see the full sized images.

 

I'm not convinced the image indexing really matters, some of the largest organisations such as here at Sky lazy load imges and we rely heavily on SEO.  I trust the SEO experts here and other huge media orgs know what they are doing ;)
We lazy load here because a decent % of users do not scroll which the cost savings for our CDNs is massive, not to mention not forcing users to download an array of images they might not actually see.

Bad UX from lazy-loading is due to poor implementation.  If you scroll down quickly you might see a loading image with the real one fading in.  That isn't necessarily poor UX.  Saving significant amounts of data transfer to users who don't always scroll down is a big UX improvement given we're all on limited data plans.

Edited by rbrtsmith

Share this post


Link to post
Share on other sites

Lazy loading has been an interesting project, the idea was to reduce the initial weight of the page to help with visitors to the website 3G connection. 

Lazy loading has reduced the initial weight of the page, but as mentioned in a post above by @fisicxthat on scroll there is a delay to display the image while it is being downloaded, which gives the impression of the page being slow. Other than that, I have also found that the photos have been removed from a Google image search. The lazy.gif has appeared in some SERPS, which I have implemented Structured Data Markup to force the images I want displayed. 

I think for the type of content that I have, the negatives of using lazy loading outweighs the positives. I think if the website is using images to display boarders or some sort of styling that does not need to be found on a Google images search then lazy loading is ideal. I feel this has not been a waste of time, it has opened my eyes to how it works and where I can use that feature, I would not have known this without giving it a try. Thanks @rbrtsmith for your help with the JavaScript.

Edited by GrahamUK33

Share this post


Link to post
Share on other sites

Just remember total page weight does not matter in terms of perceived performance.  It's the time to first render and first interactions (For JavaScript driven apps) that the user really notices.  Both of these can be measured with Chrome dev tools and with various network throttling options. 
Lazy loading doesn't help at all with perceived performance.  The benefit of lazy-loading is to reduce server load and reduce the data transferred for users that aren't scrolling through (Not for page load times but to save them money).

The fact you might see loading indicators on images doesn't tend to give a feeling of slow performance, especially if it's done slightly out of the screen and you have a placeholder matching the dimensions to prevent the page from jumping around as images are loaded in.  It's worth bearing in mind that most of the largest media organisations make use of lazy loading. 
Facebook being the most obvious example - Obviously they have the technical capability to ensure that it's done in a very performant way.

Edited by rbrtsmith

Share this post


Link to post
Share on other sites
1 hour ago, rbrtsmith said:

The fact you might see loading indicators on images doesn't tend to give a feeling of slow performance, especially if it's done slightly out of the screen and you have a placeholder matching the dimensions to prevent the page from jumping around as images are loaded in.

I did exactly this for a client. The images loaded just before they were due to display on the screen. I think I set it to something like 200px above the top of the image. so unless they were scrolling really quickly they didn't notice the lazy load.

Looking at the ww2 spitfire page the largest files are the slides at the top. Those are the main elements slowing down the pageload not the spitfire images.

Share this post


Link to post
Share on other sites

@fisicx the three hero images (slideshow) are on all the pages throughout the site, it forms the look and feel of the design of the website - they are staying.

I have changed the offset from 200 to 600 in the JavaScript for the Spitfire page. Now when scrolling the page at a normal reading pace, the lazy.gif image is being replaced by the image that needs to be displayed outside the viewing area. This bit now works much better for the visitor.

Being that src="images/lazy.gif" I still have a problem with the images (spitfire1.jpg, spitfire2.jpg, spitfire3.jpg, etc) from the page not being included in a Google image search. How can I over come this? is there a standard workaround?

Share this post


Link to post
Share on other sites

I’m not saying don’t use the hero images. I was just saying they are slowing down your page load speed.

if you want google to index the spitfire images don’t use lazy load. There is no simple alternative

Share this post


Link to post
Share on other sites

Google’s John Mueller: lazy loading images don’t always work with Google

There are various ways to lazy-load images, it's certainly worth thinking about how the markup could work for image search indexing (some work fine, others don't). We're looking into making some clearer recommendations too.

— John ☆.o(≧▽≦)o.☆ (@JohnMu) 28. February 2018

 

This is all very well, but he doesn't give an example or recommendation of what will work, let along where you can find this out. It seems to me that Google want you to design websites for the mobile user and use techniques such as lazy loading, but then penalise you by not including the images within their image search.

I wonder when the recommendations are going to be released, or if they have been release where they are.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing

    No registered users viewing this page.

  • Member Statistics

    • Total Members
      58,259
    • Most Online
      4,970

    Newest Member
    BarbaraBlanc
    Joined
  • Forum Statistics

    • Total Topics
      65,768
    • Total Posts
      453,362
×