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

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

<分区>

我有一个 ng-repeat,它加载了数千条记录,这些记录具有一定的复杂性,高度在 100 像素到 1200 像素之间。毋庸置疑,性能受到了很大的打击。

Infinite scrolling模块在大多数情况下都可以正常工作,直到您遇到边缘情况,在这种情况下您向下滚动到接近底部并且大部分元素已加载到 DOM 中,这让我回到原点。

Angular-vs-repeat对我来说是完美的,但我还没有想出如何计算每个后续元素的高度,因为它们不是固定的。

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

有没有人解决过这个问题?有什么建议么?代码片段会很棒。

最佳答案

ng-repeat由于与其绑定(bind)相关的开销,长列表的性能急剧下降。我特别喜欢的一个注重性能的库是 ag-grid ,方便地有an example具有可变的行高。您可能会看到它是否适合您的目的。

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

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;
          }
        }
      }
    };
  });
.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;
}
<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>

关于javascript - AngularJS 无限滚动 (ng-repeat) - 从 DOM 中移除顶部元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41767450/

相关文章:

javascript - AngularJS ngRepeat 语法错误

javascript - AngularJS 1.x 在 ng-repeat 中一次显示一项,单击下一步应显示下一项,依此类推

javascript - id下自定义selectmenu jquery

javascript - AngularJS:在指令模板函数中返回一个 promise

JavaScript - 凯撒密码

javascript - AngularJS - $watch 和 $timeout

javascript - AngularJS,MVC。未知提供者 : $ScopeProvider <- $Scope <- userCtrl

javascript - 将幻灯片动画添加到 ng-repeat

javascript - Ember.js - 组件内部模型

javascript - 如何更改链接上的域但保留路径?