AngularJS无限滚动(ng-repeat) - 从DOM中删除顶部元素

时间:2017-01-20 15:44:24

标签: javascript angularjs angularjs-ng-repeat infinite-scroll

我有一个ng-repeat,它可以加载数千个记录,其复杂程度可以达到100px到1200px之间。毋庸置疑,表现会受到很大影响。

在大多数情况下,

Infinite scrolling模块可以正常工作,直到你遇到一个边缘情况,你已经向下滚动到底部并且大部分元素已经被加载到DOM中,这让我回到原点之一。

Angular-vs-repeat对我的情况来说是完美的,但我还没弄清楚如何计算每个后面元素的高度,因为它们没有固定。

让我回到无限滚动。 我假设如果顶部元素(在视口上方)将被替换为空DIV,其计算高度等于它们的总高度总和,则性能不会成为问题。向上滚动会将它们渲染回dom并减去空DIV的高度。

以前有人解决过这个问题吗?有什么建议?代码片段很精彩。

1 个答案:

答案 0 :(得分:0)

ng-repeat由于与其绑定相关的开销而导致长列表性能急剧下降。我特别喜欢的一个具有表现意识的图书馆是ag-grid,其中an example方便地具有可变的行高。{3}}您可能会看到它是否适用于您的目的。

如果没有任何内容似乎符合您的需求,您可以随时滚动自己的指令并自行处理DOM操作,就像我在下面汇总的代码片段一样。它并不涵盖你提到的所有内容,但它包括无限滚动和删除旧元素,用空<div>替换它们的高度,而不使用ng-repeat。

&#13;
&#13;
angular.module('SuperList', [])
  .controller('mainCtrl', ['$scope', '$compile',
    function($scope, $compile) {

      // Magic numbers
      var itemsPerLoad = 4;
      var thresholdPx = 1200;
      var removeThresholdPx = 1600;

      // Options to control your directive are cool
      $scope.listOptions = {
        items: [],
        renderer: renderer,
        threshold: thresholdPx,
        removeThreshold: removeThresholdPx,
        loadFn: loadNewItems
      };

      // This function creates a div for each item in our dataset whenever
      // it's called by the directive
      function renderer(item) {
        var itemElem = angular.element('<div></div');
        itemElem.css('height', item.height + 'px');
        itemElem.html(item.text);
        return itemElem;

        // If each row needs special angular behavior, you can compile it with
        // something like the following instead of returning basic html
        // return $compile(itemElem)($scope);
      }

      // This gets called by the directive when we need to populate more items
      function loadNewItems() {
        // Let's do it async like we're getting something from the server
        setTimeout(function() {
          for (var i = 0; i < itemsPerLoad; i++) {
            // Give each item random text and height
            $scope.listOptions.items.push({
              text: Math.random().toString(36).substr(2, Infinity),
              height: Math.floor(100 + Math.random() * 1100)
            });
          }
          // Call the refresh function to let the directive know we've loaded
          // We could, of course, use $watch in the directive and just make
          // sure a $digest gets called here, but doing it this way is much faster.
          $scope.listOptions.api.refresh();
        }, 500);

        // return true to let the directive know we're waiting on data, so don't
        // call this function again until that happens
        return true;
      }
    }
  ])
  .directive('itemList', function() {
    return {
      restrict: 'A',
      scope: {
        itemList: '='
      },
      link: function(scope, element, attrs) {
        var el = element[0];
        var emptySpace = angular.element('<div class="empty-space"></div>');
        element.append(emptySpace);

        // Keep a selection of previous elements so we can remove them
        // if the user scrolls far enough
        var prevElems = null;
        var prevHeight = 0;
        var nextElems = 0;
        var nextHeight = 0;

        // Options are defined above the directive to keep things modular
        var options = scope.itemList;

        // Keep track of how many rows we've rendered so we know where we left off
        var renderedRows = 0;

        var pendingLoad = false;

        // Add some API functions to let the calling scope interact
        // with the directive more effectively
        options.api = {
          refresh: refresh
        };

        element.on('scroll', checkScroll);

        // Perform the initial setup
        refresh();

        function refresh() {
          addRows();
          checkScroll();
        }

        // Adds any rows that haven't already been rendered. Note that the
        // directive does not process any removed items, so if that functionality
        // is needed you'll need to make changes to this directive
        function addRows() {
          nextElems = [];
          for (var i = renderedRows; i < options.items.length; i++) {
            var e = options.renderer(options.items[i]);
            nextElems.push(e[0])
            element.append(e);
            renderedRows++;
            pendingLoad = false;
          }
          nextElems = angular.element(nextElems);
          nextHeight = el.scrollHeight;

          // Do this for the first time to initialize
          if (!prevElems && nextElems.length) {
            prevElems = nextElems;
            prevHeight = nextHeight;
          }
        }

        function checkScroll() {
          // Only check if we need to load if there isn't already an async load pending
          if (!pendingLoad) {
            if ((el.scrollHeight - el.scrollTop - el.clientHeight) < options.threshold) {
              console.log('Loading new items!');
              pendingLoad = options.loadFn();

              // If we're not waiting for an async event, render the new rows
              if (!pendingLoad) {
                addRows();
              }
            }
          }
          // if we're past the remove threshld, remove all previous elements and replace 
          // lengthen the empty space div to fill the space they occupied
          if (options.removeThreshold && el.scrollTop > prevHeight + options.removeThreshold) {
            console.log('Removing previous elements');
            prevElems.remove();
            emptySpace.css('height', prevHeight + 'px');

            // Stage the next elements for removal
            prevElems = nextElems;
            prevHeight = nextHeight;
          }
        }
      }
    };
  });
&#13;
.item-list {
  border: 1px solid green;
  width: 600px;
  height: 300px;
  overflow: auto;
}
.item-list > div {
  border: 1px solid blue;
}
.item-list > .empty-space {
  background: #aaffaa;
}
&#13;
<html>

<head>
  <link rel="stylesheet" href="test.css">
</head>

<body ng-app="SuperList" ng-controller="mainCtrl">
  <div class="item-list" item-list="listOptions"></div>

  <script src="https://opensource.keycdn.com/angularjs/1.5.8/angular.min.js"></script>
  <script src="test.js"></script>
</body>

</html>
&#13;
&#13;
&#13;