Lazyload Angular

// 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"
				});

			}
		);

  

posted @ 2016-08-05 11:38  小陈同学  阅读(317)  评论(0编辑  收藏  举报