【问题标题】:Lazy loading Angular views and controllers on page scroll在页面滚动时延迟加载 Angular 视图和控制器
【发布时间】:2013-12-05 20:52:32
【问题描述】:

我有一个使用 Laravel 和 Angular 的微型网站。这是一个响应式的单页微型网站,分为 5 个部分。我想延迟加载它们以减少一次加载。

<body ng-app>

 <div id="wrapper">
  <section id="intro">1</section>
  <section id="Second">2</section>
  <section id="Third">3</section>
  <section id="Fourth">4</section>
  <section id="Fifth">5</section>
 </div>

</body>

我希望在页面加载时加载 1 和 2,然后当您向下滚动页面时,加载另一个视图并以漂亮的淡入方式加载,然后加载其交互式项目。

【问题讨论】:

    标签: javascript angularjs lazy-initialization


    【解决方案1】:

    在这种情况下,延迟加载控制器可能没有必要(或有效),但可以这样做。

    这里有很多事情要解决,所以我将分部分处理。

    滚动视图延迟加载(动画)。

    Live demo here (click).

    标记:

    <div class="container">
      <section
        ng-repeat="section in loadedSections"
        ng-include="section+'.html'"
        scroll-load
        scroll-load-from="sections"
        scroll-load-to="loadedSections"
        ng-animate="{enter:'section-animate-enter'}"
      ></section>
    </div>
    

    动画 CSS:

    .section-animate-enter {
      -webkit-transition: 1.5s linear all;
        transition: 1.5s linear all;
        opacity: 0;
        left: 100%;
    }
    .section-animate-enter.section-animate-enter-active {
        opacity: 1;
        left: 0;
    }
    

    角度逻辑:

    app.controller('myCtrl', function($scope) {
      $scope.sections = ['top','mid','bottom']; //html files to load (top.html, etc)
      $scope.loadedSections = [$scope.sections[0]]; //loaded html files
    });
    
    app.directive('scrollLoad', function($compile) {
      return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          var to = scope[attrs.scrollLoadTo]; //$scope.loadedSections
          var from = scope[attrs.scrollLoadFrom]; //$scope.sections
    
          $window = angular.element(window);
          $window.bind('scroll', function(event) {
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || 0;
            var scrollPos = scrollTop + document.documentElement.clientHeight;
            var elemBottom = element[0].offsetTop + element.height();
            if (scrollPos >= elemBottom) { //scrolled to bottom of scrollLoad element
              $window.unbind(event); //this listener is no longer needed.
              if (to.length < from.length) { //if there are still elements to load
                //use $apply because we're in the window event context
                scope.$apply(to.push(from[to.length])); //add next section
              }
            }
          });
        }
      };
    });
    

    延迟加载控制器和滚动视图(动画)。

    Live demo here (click).

    标记:

    <div class="container">
      <!-- the "lazy" directive will get the controller first, then add ng-include -->
      <section
        ng-repeat="section in loadedSections"
        lazy="section"
        scroll-load
        scroll-load-from="sections"
        scroll-load-to="loadedSections"
        ng-animate="{enter:'section-animate-enter'}"
      ></section>
    </div>
    

    角度逻辑:

    var $appControllerProvider; //see below
    
    var app = angular.module('myApp', []);
    
    app.config(function($controllerProvider) {
      $appControllerProvider = $controllerProvider; //cache this so that we can lazy load controllers
    });
    
    app.controller('myCtrl', function($scope) {
      $scope.sections = ['top','mid','bottom']; //html files to load (top.html, etc)
      $scope.loadedSections = [$scope.sections[0]]; //loaded html files
    });
    
    app.directive('scrollLoad', function($compile) {
      return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          var to = scope[attrs.scrollLoadTo]; //$scope.loadedSections
          var from = scope[attrs.scrollLoadFrom]; //$scope.sections
    
          $window = angular.element(window);
          $window.bind('scroll', function(event) {
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || 0;
            var scrollPos = scrollTop + document.documentElement.clientHeight;
            var elemBottom = element[0].offsetTop + element.height();
            if (scrollPos >= elemBottom) { //scrolled to bottom of scrollLoad element
              $window.unbind(event); //this listener is no longer needed.
              if (to.length < from.length) { //if there are still elements to load
                //use $apply because we're in the window event context
                scope.$apply(to.push(from[to.length])); //add next section
              }
            }
          });
        }
      };
    });
    
    app.factory('myService', function($http, $q) {
      return {
        getController: function(fileName) {
          return $http.get(fileName+'.js').then(function(response) {
            return response.data;
          });
        }
      }
    });
    
    app.directive('lazy', function(myService, $compile, $q) {
      /* I store the directive in a variable then return it later
       * so that I can abstract directive logic into other functions below */
      var directiveReturn = {
        restrict: 'A',
        link: function(scope, element, attrs) {
          var loadName = scope.$eval(attrs.lazy);
    
          //this is straightforward - see the "addScript" function for explanation
          myService.getController(loadName).then(function(js) {
            return addScript(loadName, js, scope);
          }).then(function() {
            //the controller has been lazy loaded into angular
            //now use "ng-include" to lazy load the view.
            var ngInc = angular.element('<span></span>')
              .attr('ng-include', "'"+loadName+".html'")
              .attr('ng-controller', loadName+'Ctrl');
              element.append(ngInc);
              $compile(ngInc)(scope);
          });
        } //link
      }; //directive return
    
      /*
       * This is the magic.
       */
      var scriptPromises = {};
      function addScript(loadName, js, scope) {
        if (!scriptPromises[loadName]) { //if this controller hasn't already been loaded
          var deferred = $q.defer();
          //cache promise (which caches the controller when resolved)
          scriptPromises[loadName] = deferred.promise;
    
          //inject controller into a script tag
          var script = document.createElement('script');
          script.src = 'data:text/javascript,' + encodeURI(js);
          script.onload = function() {
            //this is how you lazy load a controller
            $appControllerProvider.register(loadName, window[loadName+'Ctrl']);
            //now that the controller is registered with angular, resolve the promise
            //then, it is safe to add markup that uses this controller with ng-controller
            scope.$apply(deferred.resolve());
          };
          //when this script loads, the controller will be registered and promise is resolved
          document.body.appendChild(script);
          return deferred.promise;
        }
        else { //controller already loaded
          return scriptPromises[loadName]; //use cached controller
        }
      }
      return directiveReturn;
    });
    

    【讨论】:

    • @m59...谢谢你的解释。我确实在查看您创建的演示时遇到了问题。
    • @TikaL13 该演示非常适合我。 Plunkr 偶尔会出现一些加载问题,但通常应该没问题。为了安全起见,我使用 Chrome。
    • 对于这种类型的构建,什么是好的和高效的文件夹结构?我使用 Laravel 作为后端,所以这些部分实际上是刀片模板。我猜用这种方法使用 Laravel 的刀片模板系统在这里并不理想。
    • @TikaL13 你可以让它看起来像你想要的!想想如何最好地抽象细节。您可能不想像我一样直接加载文件,而是将其抽象为其他内容...您甚至可以设置服务器操作来为您处理模板内容,例如 $http.get('templates-api/get/me/this); 以及介于两者之间的所有内容...跨度>
    • 最后一个问题。如果我想在向下滚动到这些其他部分时更改 URL,此设置是否可以使用该功能?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-09
    • 2019-06-14
    • 2023-02-19
    • 2011-03-14
    • 1970-01-01
    相关资源
    最近更新 更多