Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- angular.module('bnLazySrc', [])
- .service('LazyLoadContainer', function($rootScope){
- var self = this;
- self.containers = [];
- return {
- getContainers : function() {
- return self.containers;
- },
- addContainer : function(container) {
- self.containers.push(container);
- }
- }
- })
- .directive(
- "bnLazySrc",
- function( $window, $document, LazyLoadContainer) {
- // 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.
- 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;
- scrollingContainers = LazyLoadContainer.getContainers();
- for(container in scrollingContainers) {
- el = $(scrollingContainers[container]);
- el.on( "scroll.bnLazySrc_" + container, windowChanged );
- }
- // 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;
- scrollingContainers = LazyLoadContainer.getContainers();
- for(container in scrollingContainers) {
- el = $(scrollingContainers[container]);
- el.off( "scroll.bnLazySrc_" + container, windowChanged );
- }
- // 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"
- });
- }
- );
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement