Advertisement
Guest User

bn-lazy-load

a guest
May 30th, 2014
669
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. angular.module('bnLazySrc', [])
  2. .service('LazyLoadContainer', function($rootScope){
  3.  
  4.     var self = this;
  5.  
  6.     self.containers = [];
  7.  
  8.     return {
  9.         getContainers : function() {
  10.             return self.containers;
  11.         },
  12.         addContainer : function(container) {
  13.             self.containers.push(container);
  14.         }
  15.     }
  16. })
  17. .directive(
  18.     "bnLazySrc",
  19.     function( $window, $document, LazyLoadContainer) {
  20.  
  21.  
  22.         // I manage all the images that are currently being
  23.         // monitored on the page for lazy loading.
  24.         var lazyLoader = (function() {
  25.  
  26.             // I maintain a list of images that lazy-loading
  27.             // and have yet to be rendered.
  28.             var images = [];
  29.  
  30.             // I define the render timer for the lazy loading
  31.             // images to that the DOM-querying (for offsets)
  32.             // is chunked in groups.
  33.             var renderTimer = null;
  34.             var renderDelay = 100;
  35.  
  36.             // I cache the window element as a jQuery reference.
  37.             var win = $( $window );
  38.  
  39.             // I cache the document document height so that
  40.             // we can respond to changes in the height due to
  41.             // dynamic content.
  42.             var doc = $document;
  43.             var documentHeight = doc.height();
  44.             var documentTimer = null;
  45.             var documentDelay = 2000;
  46.  
  47.             // I determine if the window dimension events
  48.             // (ie. resize, scroll) are currenlty being
  49.             // monitored for changes.
  50.             var isWatchingWindow = false;
  51.  
  52.  
  53.             // ---
  54.             // PUBLIC METHODS.
  55.             // ---
  56.  
  57.  
  58.             // I start monitoring the given image for visibility
  59.             // and then render it when necessary.
  60.             function addImage( image ) {
  61.  
  62.                 images.push( image );
  63.  
  64.                 if ( ! renderTimer ) {
  65.  
  66.                     startRenderTimer();
  67.  
  68.                 }
  69.  
  70.                 if ( ! isWatchingWindow ) {
  71.  
  72.                     startWatchingWindow();
  73.  
  74.                 }
  75.  
  76.             }
  77.  
  78.  
  79.             // I remove the given image from the render queue.
  80.             function removeImage( image ) {
  81.  
  82.                 // Remove the given image from the render queue.
  83.                 for ( var i = 0 ; i < images.length ; i++ ) {
  84.  
  85.                     if ( images[ i ] === image ) {
  86.  
  87.                         images.splice( i, 1 );
  88.                         break;
  89.  
  90.                     }
  91.  
  92.                 }
  93.  
  94.                 // If removing the given image has cleared the
  95.                 // render queue, then we can stop monitoring
  96.                 // the window and the image queue.
  97.                 if ( ! images.length ) {
  98.  
  99.                     clearRenderTimer();
  100.  
  101.                     stopWatchingWindow();
  102.  
  103.                 }
  104.  
  105.             }
  106.  
  107.  
  108.             // ---
  109.             // PRIVATE METHODS.
  110.             // ---
  111.  
  112.  
  113.             // I check the document height to see if it's changed.
  114.             function checkDocumentHeight() {
  115.  
  116.                 // If the render time is currently active, then
  117.                 // don't bother getting the document height -
  118.                 // it won't actually do anything.
  119.                 if ( renderTimer ) {
  120.  
  121.                     return;
  122.  
  123.                 }
  124.  
  125.                 var currentDocumentHeight = doc.height();
  126.  
  127.                 // If the height has not changed, then ignore -
  128.                 // no more images could have come into view.
  129.                 if ( currentDocumentHeight === documentHeight ) {
  130.  
  131.                     return;
  132.  
  133.                 }
  134.  
  135.                 // Cache the new document height.
  136.                 documentHeight = currentDocumentHeight;
  137.  
  138.                 startRenderTimer();
  139.  
  140.             }
  141.  
  142.  
  143.             // I check the lazy-load images that have yet to
  144.             // be rendered.
  145.             function checkImages() {
  146.  
  147.                 // Log here so we can see how often this
  148.                 // gets called during page activity.
  149.  
  150.  
  151.                 var visible = [];
  152.                 var hidden = [];
  153.  
  154.                 // Determine the window dimensions.
  155.                 var windowHeight = win.height();
  156.                 var scrollTop = win.scrollTop();
  157.  
  158.                 // Calculate the viewport offsets.
  159.                 var topFoldOffset = scrollTop;
  160.                 var bottomFoldOffset = ( topFoldOffset + windowHeight );
  161.  
  162.                 // Query the DOM for layout and seperate the
  163.                 // images into two different categories: those
  164.                 // that are now in the viewport and those that
  165.                 // still remain hidden.
  166.                 for ( var i = 0 ; i < images.length ; i++ ) {
  167.  
  168.                     var image = images[ i ];
  169.  
  170.                     if ( image.isVisible( topFoldOffset, bottomFoldOffset ) ) {
  171.  
  172.                         visible.push( image );
  173.  
  174.                     } else {
  175.  
  176.                         hidden.push( image );
  177.  
  178.                     }
  179.  
  180.                 }
  181.  
  182.                 // Update the DOM with new image source values.
  183.                 for ( var i = 0 ; i < visible.length ; i++ ) {
  184.  
  185.                     visible[ i ].render();
  186.  
  187.                 }
  188.  
  189.                 // Keep the still-hidden images as the new
  190.                 // image queue to be monitored.
  191.                 images = hidden;
  192.  
  193.                 // Clear the render timer so that it can be set
  194.                 // again in response to window changes.
  195.                 clearRenderTimer();
  196.  
  197.                 // If we've rendered all the images, then stop
  198.                 // monitoring the window for changes.
  199.                 if ( ! images.length ) {
  200.  
  201.                     stopWatchingWindow();
  202.  
  203.                 }
  204.  
  205.             }
  206.  
  207.  
  208.             // I clear the render timer so that we can easily
  209.             // check to see if the timer is running.
  210.             function clearRenderTimer() {
  211.  
  212.                 clearTimeout( renderTimer );
  213.  
  214.                 renderTimer = null;
  215.  
  216.             }
  217.  
  218.  
  219.             // I start the render time, allowing more images to
  220.             // be added to the images queue before the render
  221.             // action is executed.
  222.             function startRenderTimer() {
  223.  
  224.                 renderTimer = setTimeout( checkImages, renderDelay );
  225.  
  226.             }
  227.  
  228.  
  229.             // I start watching the window for changes in dimension.
  230.             function startWatchingWindow() {
  231.  
  232.                 isWatchingWindow = true;
  233.  
  234.                 scrollingContainers = LazyLoadContainer.getContainers();
  235.                 for(container in scrollingContainers) {
  236.                     el = $(scrollingContainers[container]);
  237.                     el.on( "scroll.bnLazySrc_" + container, windowChanged );
  238.                 }
  239.  
  240.                 // Listen for window changes.
  241.                 win.on( "resize.bnLazySrc", windowChanged );
  242.                 win.on( "scroll.bnLazySrc", windowChanged );
  243.  
  244.                 // Set up a timer to watch for document-height changes.
  245.                 documentTimer = setInterval( checkDocumentHeight, documentDelay );
  246.  
  247.             }
  248.  
  249.  
  250.             // I stop watching the window for changes in dimension.
  251.             function stopWatchingWindow() {
  252.  
  253.                 isWatchingWindow = false;
  254.  
  255.                 scrollingContainers = LazyLoadContainer.getContainers();
  256.                 for(container in scrollingContainers) {
  257.                     el = $(scrollingContainers[container]);
  258.                     el.off( "scroll.bnLazySrc_" + container, windowChanged );
  259.                 }
  260.  
  261.                 // Stop watching for window changes.
  262.                 win.off( "resize.bnLazySrc" );
  263.                 win.off( "scroll.bnLazySrc" );
  264.  
  265.                 // Stop watching for document changes.
  266.                 clearInterval( documentTimer );
  267.  
  268.             }
  269.  
  270.  
  271.             // I start the render time if the window changes.
  272.             function windowChanged() {
  273.  
  274.                 if ( ! renderTimer ) {
  275.  
  276.                     startRenderTimer();
  277.  
  278.                 }
  279.  
  280.             }
  281.  
  282.  
  283.             // Return the public API.
  284.             return({
  285.                 addImage: addImage,
  286.                 removeImage: removeImage
  287.             });
  288.  
  289.         })();
  290.  
  291.  
  292.         // ------------------------------------------ //
  293.         // ------------------------------------------ //
  294.  
  295.  
  296.         // I represent a single lazy-load image.
  297.         function LazyImage( element ) {
  298.  
  299.             // I am the interpolated LAZY SRC attribute of
  300.             // the image as reported by AngularJS.
  301.             var source = null;
  302.  
  303.             // I determine if the image has already been
  304.             // rendered (ie, that it has been exposed to the
  305.             // viewport and the source had been loaded).
  306.             var isRendered = false;
  307.  
  308.             // I am the cached height of the element. We are
  309.             // going to assume that the image doesn't change
  310.             // height over time.
  311.             var height = null;
  312.  
  313.  
  314.             // ---
  315.             // PUBLIC METHODS.
  316.             // ---
  317.  
  318.  
  319.             // I determine if the element is above the given
  320.             // fold of the page.
  321.             function isVisible( topFoldOffset, bottomFoldOffset ) {
  322.  
  323.                 // If the element is not visible because it
  324.                 // is hidden, don't bother testing it.
  325.                 if ( ! element.is( ":visible" ) ) {
  326.  
  327.                     return( false );
  328.  
  329.                 }
  330.  
  331.                 // If the height has not yet been calculated,
  332.                 // the cache it for the duration of the page.
  333.                 if ( height === null ) {
  334.  
  335.                     height = element.height();
  336.  
  337.                 }
  338.  
  339.                 // Update the dimensions of the element.
  340.                 var top = element.offset().top;
  341.                 var bottom = ( top + height );
  342.  
  343.                 // Return true if the element is:
  344.                 // 1. The top offset is in view.
  345.                 // 2. The bottom offset is in view.
  346.                 // 3. The element is overlapping the viewport.
  347.                 return(
  348.                     (
  349.                         ( top <= bottomFoldOffset ) &&
  350.                         ( top >= topFoldOffset )
  351.                         )
  352.                     ||
  353.                     (
  354.                         ( bottom <= bottomFoldOffset ) &&
  355.                         ( bottom >= topFoldOffset )
  356.                         )
  357.                     ||
  358.                     (
  359.                         ( top <= topFoldOffset ) &&
  360.                         ( bottom >= bottomFoldOffset )
  361.                         )
  362.                     );
  363.  
  364.             }
  365.  
  366.  
  367.             // I move the cached source into the live source.
  368.             function render() {
  369.  
  370.                 isRendered = true;
  371.  
  372.                 renderSource();
  373.  
  374.             }
  375.  
  376.  
  377.             // I set the interpolated source value reported
  378.             // by the directive / AngularJS.
  379.             function setSource( newSource ) {
  380.  
  381.                 source = newSource;
  382.  
  383.                 if ( isRendered ) {
  384.  
  385.                     renderSource();
  386.  
  387.                 }
  388.  
  389.             }
  390.  
  391.  
  392.             // ---
  393.             // PRIVATE METHODS.
  394.             // ---
  395.  
  396.  
  397.             // I load the lazy source value into the actual
  398.             // source value of the image element.
  399.             function renderSource() {
  400.  
  401.                 element[ 0 ].src = source;
  402.  
  403.             }
  404.  
  405.  
  406.             // Return the public API.
  407.             return({
  408.                 isVisible: isVisible,
  409.                 render: render,
  410.                 setSource: setSource
  411.             });
  412.  
  413.         }
  414.  
  415.  
  416.         // ------------------------------------------ //
  417.         // ------------------------------------------ //
  418.  
  419.  
  420.         // I bind the UI events to the scope.
  421.         function link( $scope, element, attributes ) {
  422.  
  423.             var lazyImage = new LazyImage( element );
  424.  
  425.             // Start watching the image for changes in its
  426.             // visibility.
  427.             lazyLoader.addImage( lazyImage );
  428.  
  429.  
  430.             // Since the lazy-src will likely need some sort
  431.             // of string interpolation, we don't want to
  432.             attributes.$observe(
  433.                 "bnLazySrc",
  434.                 function( newSource ) {
  435.  
  436.                     lazyImage.setSource( newSource );
  437.  
  438.                 }
  439.             );
  440.  
  441.  
  442.             // When the scope is destroyed, we need to remove
  443.             // the image from the render queue.
  444.             $scope.$on(
  445.                 "$destroy",
  446.                 function() {
  447.  
  448.                     lazyLoader.removeImage( lazyImage );
  449.  
  450.                 }
  451.             );
  452.  
  453.         }
  454.  
  455.  
  456.         // Return the directive configuration.
  457.         return({
  458.             link: link,
  459.             restrict: "A"
  460.         });
  461.  
  462.     }
  463. );
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement