【问题标题】:AngularJS infinite scrolling (ng-repeat) - removing top elements from the DOM [closed]AngularJS 无限滚动(ng-repeat) - 从 DOM 中删除顶部元素 [关闭]
【发布时间】:2017-02-10 09:38:21
【问题描述】:

我有一个 ng-repeat 可以加载数千条具有一定复杂性的记录,其高度可以在 100 像素到 1200 像素之间。毋庸置疑,性能得到了相当大的打击。

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

Angular-vs-repeat 非常适合我的情况,但我还没有弄清楚如何计算每个后续元素的高度,因为它们不是固定的。

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

以前有人解决过这个问题吗?有什么建议么?代码 sn-ps 会很棒。

【问题讨论】:

  • 没有任何例子很难得到你的帮助。你能通过codepen/...重现一个类似的案例吗?
  • 我自己也在研究同样的虚拟滚动技术,但从未真正实现它,因为那会过度设计。我也有不同高度的项目,因此需要修改现有插件。如果我们指出性能会受到影响并且我需要实现虚拟滚动,我肯定会非常仔细地查看上述指令 + 可能会引入其他功能,例如在顶部和底部保留 1.5 页元素(人们可以点击 Home 和 End键)和当前视图项目。它们之间将是空的高度调整的虚拟容器。
  • Checkout vs-repeat 演示页面,它有一个使用可变大小元素的演示kamilkp.github.io/angular-vs-repeat/#?tab=6

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


【解决方案1】:

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

如果似乎没有任何东西可以满足您的需求,您可以随时滚动自己的指令并自己处理 DOM 操作,就像我在下面一起编写的代码 sn-p 一样。它没有涵盖您提到的所有内容,但它包括无限滚动并删除旧元素,用空的<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>

【讨论】:

  • 如果向后滚动,您将如何解决/添加回项目?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-03
  • 1970-01-01
  • 2015-11-05
  • 1970-01-01
  • 2014-09-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多