Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Claude Englebert and Ciqala Burt

Lazy Loading Image With AngularJS

By Ben Nadel on

At InVision, we deal with a lot of image assets. Not only does loading these images consume concurrent HTTP requests, it greatly increases the weight of the page. As such, I've been trying to implement lazy-loading techniques that offer a quicker loading experience for the end user. Unfortunately, this task has proven harder than expected; in fact, my first approach to lazy-loading images actually made the load time feel a little bit worse. Slowly, however, my lazy-loading image technique is getting better.


 
 
 

 
  
 
 
 

View this demo in my JavaScript-Demos project on GitHub.

The concept of lazy-loading images is rather straightforward: don't load an image until the image is within (or close to within) the bounds of the browser's viewport. From an implementation standpoint, however, it's a bit more complicated. Not only do you have to monitor the state of the document and the window, you have to do so in a way that doesn't force too many repaints (which can seriously hurt performance).

That last point - forcing repaints - is especially interesting in an AngularJS context. And, it's even more interesting in the context of the ngRepeat directive. When the ngRepeat directive renders a collection in your View, it transcludes and links each repeat-Node individually. If you're not doing anything special, this implementation won't matter; however, if you have a directive on your repeat-Node that queries the state of the DOM, it prevents the browser from chunking DOM updates.

I'm only just starting to understand how the browser optimizes rendering; but, from what I think is happening, here is how a "vanilla" ngRepeat will work in conjunction with the browser performance optimizations:

  • Append DOM node.
  • Append DOM node.
  • Append DOM node.
  • Append DOM node.
  • RENDER / REPAINT LAYOUT.

As you can see, the browser won't repaint until the end of the DOM updates.

Now, if our ngRepeat DOM node has a directive that needs to query the state of the DOM (such as determining if the given DOM is within the bounds of the browser's viewport for the purposes of lazy-loading images), the same ngRepeat will look like this:

  • Append DOM node (with DOM-querying directive).
  • RENDER / REPAINT LAYOUT.
  • Append DOM node (with DOM-querying directive).
  • RENDER / REPAINT LAYOUT.
  • Append DOM node (with DOM-querying directive).
  • RENDER / REPAINT LAYOUT.
  • Append DOM node (with DOM-querying directive).
  • RENDER / REPAINT LAYOUT.

Because each transcluded node within the ngRepeat attempts to query the DOM for layout state, the browser can't chunk the DOM updates - the browser has to repaint after each DOM change. This can have significant performance implications.

Coming back to the lazy-loading of images, this browser optimization is important to understand because you don't want to accidentally cancel out the benefits of chunking by adding code that is "supposed to" speed up page loads. The following is my attempt to organize the lazy loading of images in AngularJS such that all DOM queries and DOM updates are grouped separately. Furthermore, I group the DOM queries executed by the lazy-loading images into a asynchronous tick of the JavaScript event loop, which is buffered by a timeout.

The following code watches for three different events that may require images to load:

  • Window scroll (the most obvious).
  • Window resize.
  • Document resize (based on dynamic content).

The window-based events are fairly self-explanatory; but, what about "Document resize?" By this, I mean anything that changes the dimensions of the content without changing the dimensions of the window. This is a harder change to monitor and, unlike changes to the window, probably requires some form of polling. However, since it is, perhaps, the least meaningful / likely property to change, the polling doesn't have to be high-impact.

Anyway, what follows in my latest attempt in creating an affective, but optimized approach to lazy-loading images in an AngularJS context:

  • <!doctype html>
  • <html ng-app="Demo" ng-controller="AppController">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Lazy Loading Images With AngularJS
  • </title>
  •  
  • <style type="text/css">
  •  
  • /* Add "click" styles because newer releases of AngularJS don't seem to add the HREF value. */
  • a[ ng-click ] {
  • cursor: pointer ;
  • text-decoration: underline ;
  • }
  •  
  • a.box {
  • background-color: #FAFAFA ;
  • border: 1px solid #CCCCCC ;
  • display: block ;
  • height: 200px ;
  • line-height: 200px ;
  • text-align: center ;
  • width: 684px ;
  • }
  •  
  • ul.photos {
  • list-style-type: none ;
  • margin: 16px 0px 16px 0px ;
  • padding: 0px 0px 0px 0px ;
  • width: 700px ;
  • }
  •  
  • ul.photos:after {
  • content: "" ;
  • clear: both ;
  • display: block ;
  • height: 0px ;
  • }
  •  
  • li.photo {
  • background-color: #FAFAFA ;
  • border: 1px solid #CCCCCC ;
  • border-radius: 4px 4px 4px 4px ;
  • float: left ;
  • margin: 0px 10px 10px 0px ;
  • padding: 5px 5px 5px 5px ;
  • }
  •  
  • li.photo img {
  • border: 1px solid #EEEEEE ;
  • border-radius: 3px 3px 3px 3px ;
  • display: block ;
  • }
  •  
  • img[ bn-lazy-src ] {
  • background-image: url( "./checkered.png" ) ;
  • }
  •  
  • </style>
  • </head>
  • <body>
  •  
  • <h1>
  • Lazy Loading Images With AngularJS
  • </h1>
  •  
  • <p>
  • You have {{ photos.size }} photos in your set.
  •  
  • <a ng-click="rebuildSet()">Rebuild set</a>.
  • <a ng-click="changeSource()">Change src</a>.
  • <a ng-click="clearPhotos()">Clear</a>.
  • </p>
  •  
  • <a ng-show="isBoxVisible" ng-click="hideBox()" class="box">
  • This is a big thing that may change,
  • causing the DOCUMENT HEIGHT to change.
  • </a>
  •  
  • <ul class="photos">
  •  
  • <li ng-repeat="photo in photos" class="photo">
  •  
  • <img
  • bn-lazy-src="{{ photo.src }}"
  • width="150"
  • height="150"
  • alt="Christina Cox"
  • />
  •  
  • </li>
  •  
  • </ul>
  •  
  •  
  •  
  • <!-- Load scripts. -->
  • <script
  • type="text/javascript"
  • src="../../vendor/jquery/jquery-2.0.3.min.js">
  • </script>
  • <script
  • type="text/javascript"
  • src="../../vendor/angularjs/angular-1.0.7.min.js">
  • </script>
  • <script type="text/javascript">
  •  
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the root of the application.
  • app.controller(
  • "AppController",
  • function( $scope ) {
  •  
  • // I flag the visibility of the big box.
  • $scope.isBoxVisible = true;
  •  
  • // Build up a large set of images, all with unique
  • // SRC values so that the browser cannot cache them.
  • $scope.photos = buildPhotoSet( 200 );
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I change the SRC values of the existing photo set
  • // in order to examine how changes to source will
  • // affect rendered / non-rendered images.
  • $scope.changeSource = function() {
  •  
  • var now = ( new Date() ).getTime();
  •  
  • // Update all SRC attribute to point to "1.jpg".
  • for ( var i = 0 ; i < $scope.photos.length ; i++ ) {
  •  
  • var photo = $scope.photos[ i ];
  •  
  • photo.src = photo.src.replace( /\d\./i, "1." );
  •  
  • }
  •  
  • };
  •  
  •  
  • // I clear the current photo set.
  • $scope.clearPhotos = function() {
  •  
  • $scope.photos = [];
  •  
  • };
  •  
  •  
  • // I hide the big box, allowing the document to change
  • // its dimensions (and possibly show more images than
  • // were visible beforehand).
  • $scope.hideBox = function() {
  •  
  • $scope.isBoxVisible = false;
  •  
  • };
  •  
  •  
  • // I rebuild the entire photo set.
  • $scope.rebuildSet = function() {
  •  
  • $scope.photos = buildPhotoSet( 20 );
  •  
  • };
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I return a photo set of the given size. Each photo
  • // will have a unique SRC value.
  • function buildPhotoSet( size ) {
  •  
  • var photos = [];
  • var now = ( new Date() ).getTime();
  •  
  • for ( var i = 0 ; i < size ; i++ ) {
  •  
  • var index = ( ( i % 3 ) + 1 );
  • var version = ( now + i );
  •  
  • photos.push({
  • id: ( i + 1 ),
  • src: ( "christina-cox-" + index + ".jpg?v=" + version )
  • });
  •  
  • }
  •  
  • return( photos );
  •  
  • }
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I lazily load the images, when they come into view.
  • app.directive(
  • "bnLazySrc",
  • function( $window, $document ) {
  •  
  •  
  • // I manage all the images that are currently being
  • // monitored on the page for lazy loading.
  • var lazyLoader = (function() {
  •  
  • // I maintain a list of images that lazy-loading
  • // and have yet to be rendered.
  • var images = [];
  •  
  • // I define the render timer for the lazy loading
  • // images to that the DOM-querying (for offsets)
  • // is chunked in groups.
  • var renderTimer = null;
  • var renderDelay = 100;
  •  
  • // I cache the window element as a jQuery reference.
  • var win = $( $window );
  •  
  • // I cache the document document height so that
  • // we can respond to changes in the height due to
  • // dynamic content.
  • var doc = $document;
  • var documentHeight = doc.height();
  • var documentTimer = null;
  • var documentDelay = 2000;
  •  
  • // I determine if the window dimension events
  • // (ie. resize, scroll) are currenlty being
  • // monitored for changes.
  • var isWatchingWindow = false;
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I start monitoring the given image for visibility
  • // and then render it when necessary.
  • function addImage( image ) {
  •  
  • images.push( image );
  •  
  • if ( ! renderTimer ) {
  •  
  • startRenderTimer();
  •  
  • }
  •  
  • if ( ! isWatchingWindow ) {
  •  
  • startWatchingWindow();
  •  
  • }
  •  
  • }
  •  
  •  
  • // I remove the given image from the render queue.
  • function removeImage( image ) {
  •  
  • // Remove the given image from the render queue.
  • for ( var i = 0 ; i < images.length ; i++ ) {
  •  
  • if ( images[ i ] === image ) {
  •  
  • images.splice( i, 1 );
  • break;
  •  
  • }
  •  
  • }
  •  
  • // If removing the given image has cleared the
  • // render queue, then we can stop monitoring
  • // the window and the image queue.
  • if ( ! images.length ) {
  •  
  • clearRenderTimer();
  •  
  • stopWatchingWindow();
  •  
  • }
  •  
  • }
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I check the document height to see if it's changed.
  • function checkDocumentHeight() {
  •  
  • // If the render time is currently active, then
  • // don't bother getting the document height -
  • // it won't actually do anything.
  • if ( renderTimer ) {
  •  
  • return;
  •  
  • }
  •  
  • var currentDocumentHeight = doc.height();
  •  
  • // If the height has not changed, then ignore -
  • // no more images could have come into view.
  • if ( currentDocumentHeight === documentHeight ) {
  •  
  • return;
  •  
  • }
  •  
  • // Cache the new document height.
  • documentHeight = currentDocumentHeight;
  •  
  • startRenderTimer();
  •  
  • }
  •  
  •  
  • // I check the lazy-load images that have yet to
  • // be rendered.
  • function checkImages() {
  •  
  • // Log here so we can see how often this
  • // gets called during page activity.
  • console.log( "Checking for visible images..." );
  •  
  • var visible = [];
  • var hidden = [];
  •  
  • // Determine the window dimensions.
  • var windowHeight = win.height();
  • var scrollTop = win.scrollTop();
  •  
  • // Calculate the viewport offsets.
  • var topFoldOffset = scrollTop;
  • var bottomFoldOffset = ( topFoldOffset + windowHeight );
  •  
  • // Query the DOM for layout and seperate the
  • // images into two different categories: those
  • // that are now in the viewport and those that
  • // still remain hidden.
  • for ( var i = 0 ; i < images.length ; i++ ) {
  •  
  • var image = images[ i ];
  •  
  • if ( image.isVisible( topFoldOffset, bottomFoldOffset ) ) {
  •  
  • visible.push( image );
  •  
  • } else {
  •  
  • hidden.push( image );
  •  
  • }
  •  
  • }
  •  
  • // Update the DOM with new image source values.
  • for ( var i = 0 ; i < visible.length ; i++ ) {
  •  
  • visible[ i ].render();
  •  
  • }
  •  
  • // Keep the still-hidden images as the new
  • // image queue to be monitored.
  • images = hidden;
  •  
  • // Clear the render timer so that it can be set
  • // again in response to window changes.
  • clearRenderTimer();
  •  
  • // If we've rendered all the images, then stop
  • // monitoring the window for changes.
  • if ( ! images.length ) {
  •  
  • stopWatchingWindow();
  •  
  • }
  •  
  • }
  •  
  •  
  • // I clear the render timer so that we can easily
  • // check to see if the timer is running.
  • function clearRenderTimer() {
  •  
  • clearTimeout( renderTimer );
  •  
  • renderTimer = null;
  •  
  • }
  •  
  •  
  • // I start the render time, allowing more images to
  • // be added to the images queue before the render
  • // action is executed.
  • function startRenderTimer() {
  •  
  • renderTimer = setTimeout( checkImages, renderDelay );
  •  
  • }
  •  
  •  
  • // I start watching the window for changes in dimension.
  • function startWatchingWindow() {
  •  
  • isWatchingWindow = true;
  •  
  • // Listen for window changes.
  • win.on( "resize.bnLazySrc", windowChanged );
  • win.on( "scroll.bnLazySrc", windowChanged );
  •  
  • // Set up a timer to watch for document-height changes.
  • documentTimer = setInterval( checkDocumentHeight, documentDelay );
  •  
  • }
  •  
  •  
  • // I stop watching the window for changes in dimension.
  • function stopWatchingWindow() {
  •  
  • isWatchingWindow = false;
  •  
  • // Stop watching for window changes.
  • win.off( "resize.bnLazySrc" );
  • win.off( "scroll.bnLazySrc" );
  •  
  • // Stop watching for document changes.
  • clearInterval( documentTimer );
  •  
  • }
  •  
  •  
  • // I start the render time if the window changes.
  • function windowChanged() {
  •  
  • if ( ! renderTimer ) {
  •  
  • startRenderTimer();
  •  
  • }
  •  
  • }
  •  
  •  
  • // Return the public API.
  • return({
  • addImage: addImage,
  • removeImage: removeImage
  • });
  •  
  • })();
  •  
  •  
  • // ------------------------------------------ //
  • // ------------------------------------------ //
  •  
  •  
  • // I represent a single lazy-load image.
  • function LazyImage( element ) {
  •  
  • // I am the interpolated LAZY SRC attribute of
  • // the image as reported by AngularJS.
  • var source = null;
  •  
  • // I determine if the image has already been
  • // rendered (ie, that it has been exposed to the
  • // viewport and the source had been loaded).
  • var isRendered = false;
  •  
  • // I am the cached height of the element. We are
  • // going to assume that the image doesn't change
  • // height over time.
  • var height = null;
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I determine if the element is above the given
  • // fold of the page.
  • function isVisible( topFoldOffset, bottomFoldOffset ) {
  •  
  • // If the element is not visible because it
  • // is hidden, don't bother testing it.
  • if ( ! element.is( ":visible" ) ) {
  •  
  • return( false );
  •  
  • }
  •  
  • // If the height has not yet been calculated,
  • // the cache it for the duration of the page.
  • if ( height === null ) {
  •  
  • height = element.height();
  •  
  • }
  •  
  • // Update the dimensions of the element.
  • var top = element.offset().top;
  • var bottom = ( top + height );
  •  
  • // Return true if the element is:
  • // 1. The top offset is in view.
  • // 2. The bottom offset is in view.
  • // 3. The element is overlapping the viewport.
  • return(
  • (
  • ( top <= bottomFoldOffset ) &&
  • ( top >= topFoldOffset )
  • )
  • ||
  • (
  • ( bottom <= bottomFoldOffset ) &&
  • ( bottom >= topFoldOffset )
  • )
  • ||
  • (
  • ( top <= topFoldOffset ) &&
  • ( bottom >= bottomFoldOffset )
  • )
  • );
  •  
  • }
  •  
  •  
  • // I move the cached source into the live source.
  • function render() {
  •  
  • isRendered = true;
  •  
  • renderSource();
  •  
  • }
  •  
  •  
  • // I set the interpolated source value reported
  • // by the directive / AngularJS.
  • function setSource( newSource ) {
  •  
  • source = newSource;
  •  
  • if ( isRendered ) {
  •  
  • renderSource();
  •  
  • }
  •  
  • }
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I load the lazy source value into the actual
  • // source value of the image element.
  • function renderSource() {
  •  
  • element[ 0 ].src = source;
  •  
  • }
  •  
  •  
  • // Return the public API.
  • return({
  • isVisible: isVisible,
  • render: render,
  • setSource: setSource
  • });
  •  
  • }
  •  
  •  
  • // ------------------------------------------ //
  • // ------------------------------------------ //
  •  
  •  
  • // I bind the UI events to the scope.
  • function link( $scope, element, attributes ) {
  •  
  • var lazyImage = new LazyImage( element );
  •  
  • // Start watching the image for changes in its
  • // visibility.
  • lazyLoader.addImage( lazyImage );
  •  
  •  
  • // Since the lazy-src will likely need some sort
  • // of string interpolation, we don't want to
  • attributes.$observe(
  • "bnLazySrc",
  • function( newSource ) {
  •  
  • lazyImage.setSource( newSource );
  •  
  • }
  • );
  •  
  •  
  • // When the scope is destroyed, we need to remove
  • // the image from the render queue.
  • $scope.$on(
  • "$destroy",
  • function() {
  •  
  • lazyLoader.removeImage( lazyImage );
  •  
  • }
  • );
  •  
  • }
  •  
  •  
  • // Return the directive configuration.
  • return({
  • link: link,
  • restrict: "A"
  • });
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

Notice that instead of using the typical ngSrc directive, I am using bnLazySrc to delay the loading of images until they are within the viewport of the browser. As images are transcluded by the ngRepeat directive, they are added to a centralized queue of "hidden" images. Then, as this queue grows and shrinks in size, the lazyLoader turns on and off monitoring, respectively.

Currently, this only takes into account vertical scrolling since horizontal scrolling is almost never an issue in any application that I've ever written. And, it doesn't take into account any peri-viewport calculations. But, it's working fairly well (at least in this white-page demo). If nothing else, it's a really interesting problem to try and solve - I'll probably try to refactor this into a module and break out the various parts such that they can be injected as arguments rather than being defined inline.

Tweet This Fascinating post by @BenNadel - Lazy Loading Image With AngularJS Thanks my man — you rock the party that rocks the body!



Reader Comments

Thanks a lot for this great post, it enlighten me a lot about angularJS. I misunderstood that ng-src is lazy load by default.

Thanks again for another great post :)

Reply to this Comment

@Dung,

I believe that ngSrc works by simply delaying the creation of the "Src" attribute until the ngSrc attribute has been interpolated (ie. the {{values}} have been evaluated). Other than that, I believe it works like a normal src attribute.

@Ali,

My pleasure! Glad you enjoyed it :)

Reply to this Comment

Hi Ben,

I love your articles, and especially the accompanying videos, but I unforetunately can't watch them on my iPhone/iPad, because they're all in Flash.

Any plans to adopt HTML5 video?

Reply to this Comment

@Matt,

Responsive design is not something that I've really wrapped my head around yet. Something about it just doesn't click in my head. As such, it's a little hard for me to think about images that don't have an explicit width/height. If I think I understand what you're doing, your "wrapper" DIV takes care of the dimensions, and then the inner IMG has relative dimensions to the wrapper. Then, when the image loads, none of the dimensions need to change. Is that correct?

Reply to this Comment

@Bob,

I really do what to move to an HTML5 video viewing, but I'm not sure what the best move is. It's been on my radar for a long time :( Maybe I just need to start using YouTube videos. Right now, I have all my videos recorded (via JING) as SWF files. I wonder if I can upload those to YouTube. I'll check.

Hopefully, I'll have something figured out soon!

Reply to this Comment

@Ben

Correct. Like all things responsive we're dealing with relative, proportional layout but media with implied dimensions and mixing the two is a challenge.

Reply to this Comment

This is exactly what I am trying to do. Problem is that I get doc has no method height(). Is $document a jQuery object?

Reply to this Comment

OK found it, $document is a jQuery object and jQlite does not have .height() or .on().

Performance is great though, thanks for sharing.

Reply to this Comment

@Matthew,

Yes, correct - the built-in JQlite doesn't have all the same jQuery methods. If you load jQuery before you load AngularJS, however, it should use jQuery (full version) instead.

Glad you're liking it!

Reply to this Comment

I'm seeing issues with use of ng-src in an ng-repeat block; the image actually rendered seems, in some cases, to be left over from previous renderings of the block.

The ng-repeat in question is bound to an array of items, and each item has an "Image" property that I use as a bound variable in the ng-src attribute.

On certain events (searching / filtering), I pre-construct the entire array and and use it to update the value pointed at in bound ng-repeat variable.

When I do this switch, items that don't have an Image seem just to inherit the image of the previous occupier of that slot. Struggling to reproduce on demand, or abstract into a small POC, but has anyone seen similar behaviour?

Reply to this Comment

Great little module. Is there a way to modify this such that it can lazy load images in a modal window?

My scrolling list has about 400 items, and it has a set height with

  • overflow-y: scroll

, and I think this is causing some issues. I changed the

  • var doc = $('.modal');

, but I think the overflow is causing the directive to fail.

Any ideas?

Reply to this Comment

Thanks, great job, it was very useful, I don't know if can you help me?, I have a function when scroll load more items, so I am pushing 20 at the end, but they are no showing the images, I am sure that every item is calling the directive but not all them are updating the source. Thanks.

Reply to this Comment

Fantastic code, Ben. Thanks - this really speeds up lazy loading significantly within an ng-repeat.

One thing I noticed, though, was that it was taking a while before any images showed up above the fold.

I'll try and dig into your code to see if I can figure out why this is (could really be my issue as well...), but in the meantime, I put together a really simple fix that loads the first x images immediately, then uses your lazy loader for the rest:

  • <span ng-if="$index >= 8">
  • <img bn-lazy-src="{{i.image}}.png" height="100" width="100">
  • </span>
  • <span ng-if="$index < 8">
  • <img src="{{i.image}}.png" height="100" width="100">
  • </span>

Reply to this Comment

Great directive. There are two other events that may require images to be loaded that you haven't considered. Element scroll and element resize.

Currently the directive doesn't trigger checking when the scroll is in an element with overflow.
Would be great if you could set up separate instances of the lazyLoader and pass an element to watch for scrolling & resize on.

Perhaps another directive placed on the element to watch that initiates the instance of the lazyLoader?

Guess this is similar to Dave Ackerman's question about lazy loading in a modal.

Reply to this Comment

Any plan to publish this directive as a module? Maybe in bower to easily reuse it

Reply to this Comment

First of all, tnx for the amazing work :)

I had a problem using this when my main window was not scrollable but an element in the page had overflow-y:auto;
The lazy load does not respond to changes then.

I've build a simple solution(bit dirty) to fix this:
http://pastebin.com/1khxFJKN

I've added the LazyLoadContainer. This way you can do this in your app config:

app.run(function (LazyLoadContainer) {
LazyLoadContainer.addContainer(
'#content'
);
});

Reply to this Comment

I worked on your code to make it work without jQuery.
You can see the result here

http://embed.plnkr.co/GImgkb4x3qIsnnNTrnRF/preview

I have a couple of issues that I wasn't able to solve with jqLite and that are:

1. check the visibility of an element without using the is(':visible') method
2. bind scroll on the single element instead of the whole window (I am not sure this can affect performance)
3. I had to create a hack since isVisible is called on different types of element. At the beginning is called on a jqLite element, than on a pure DOM element.
I think the problem is in

hidden.push(image); <- this is a DOM element and not a jqLite element

and

images = hidden;

Any feedback is welcome.

Reply to this Comment

Hey Ben,

i'm utilizing your bn-lazy-src directive in my project. when i change any $scope variable (create a new turn) then all the lazily loaded images flash.. is there any way to prevent that?

otherwise i dig your robust solution, great work!

Reply to this Comment

Thanks for your nice work. i also utilize. it works perfect under the ng. i will look and study every detail after finishing my project. thanks again.

Reply to this Comment

@Ben,

Thanks for sharing. It helped me understand what i needed myself :). I made something using the srcset setup, so while being lazy (only loaded when it comes into view) and listening on resize. Demo is available overhere http://squadracorse.github.io/ng-lazy-image/ with github link and as a bower install. Feedback is welcome. Thanks!

Reply to this Comment

Hi Ben,

Many thanks for sharing. I am using your code but I have an issue. If the image element does not have width and height attribute, element.is(":visible") turns false although it is visible. So i change the code like as follows;

if (!element.is(":visible") && element.width() != 0) {
return (false);
}

FYI

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.