如何在AngularJS中保留ng-repeat的滚动位置?

时间:2014-05-19 11:32:37

标签: angularjs scroll angularjs-ng-repeat

DEMO

使用ng-repeat呈现对象列表。假设此列表非常长,用户滚动到底部以查看某些对象。

当用户观察对象时,新项目将添加到列表顶部。这会导致观察对象改变其位置,即用户突然在观察对象的同一位置看到其他东西。

如果添加新项目,您将如何将观察对象保持在同一位置?

PLAYGROUND HERE

9 个答案:

答案 0 :(得分:30)

使用scrollTop的{​​{1}}属性可以非常优雅地解决它。我使用了两个指令 - 一个处理包装元素的滚动位置,另一个指定新元素。 如果有什么不清楚的话,请给我一个喊叫。

DEMO

JS:

div

HTML:

.directive("keepScroll", function(){

  return {

    controller : function($scope){
      var element = null;

      this.setElement = function(el){
        element = el;
      }

      this.addItem = function(item){
        console.log("Adding item", item, item.clientHeight);
        element.scrollTop = (element.scrollTop+item.clientHeight+1);
       //1px for margin from your css (surely it would be possible
       // to make it more generic, rather then hard-coding the value)
      };

    },

    link : function(scope,el,attr, ctrl) {

     ctrl.setElement(el[0]);

    }

  };

})

.directive("scrollItem", function(){

  return{
    require : "^keepScroll",
    link : function(scope, el, att, scrCtrl){
      scrCtrl.addItem(el[0]);
    }
  }
})

答案 1 :(得分:7)

您知道其他人正在尝试使用不同的UI方法解决此问题。 它们不只是在顶部展示新项目,而是在顶部显示一个小的可点击链接,说明自上次检查后添加了多少新项目。

[2 new items, Click here to refresh]

item 5
item 4
item 3

查看Twitter如何解决这个问题。 **[Let me attach a screenshot for you shortly.]**

我知道这与你想要的东西有点矛盾,但也许这在用户体验方面更好? 用户想知道是否有新物品进入。

答案 2 :(得分:5)

您可以滚动添加元素的高度

$scope.addNewItem = function() {
    var wrapper = document.getElementsByClassName('wrapper')[0];

    $scope.items = $scope.items.concat({
      id: $scope.items.length,
      name: "item " + $scope.items.length
    });
    // will fail if you observe the item 0 because we scroll before the view is updated;
    wrapper.scrollTop+=101; //height+margings+paddings
  };

我正在使用从控制器访问DOM的不良做法。更模块化的方法是创建一个指令,它将处理所有情况并在视图更新后更改滚动位置。

http://jsbin.com/zofofapo/8/edit

演示

或者,对于项目不是同样高的情况,您可以看到插入前剩余多少滚动,并在插入后重新设置

$scope.addNewItem = function() {
    var wrapper = document.getElementsByClassName('wrapper')[0],
        scrollRemaining = wrapper.scrollHeight - wrapper.scrollTop;

    $scope.items = $scope.items.concat({
      id: $scope.items.length,
      name: "item " + $scope.items.length
    });
    // will fail if you observe the item 0 because we scroll before the view is updated;
    $timeout(function(){
      wrapper.scrollTop = wrapper.scrollHeight - scrollRemaining;
    },0);
  };

http://jsbin.com/zofofapo/9/edit

演示

答案 3 :(得分:4)

以下是对Arthur版本的改进,无论滚动的上方还是下方添加了添加的项目,都会阻止滚动: JS Bin

angular.module("Demo", [])

.controller("DemoCtrl", function($scope) {
  $scope.items = [];
  
  for (var i = 0; i < 10; i++) {
    $scope.items[i] = {
      id: i,
      name: 'item ' + i
    };
  }
  
  $scope.addNewItemTop = function() {
    $scope.items.unshift({
      id: $scope.items.length,
      name: "item " + $scope.items.length
    });
  };
  
  $scope.addNewItemMiddle = function() {
    $scope.items.splice(5, 0, {
      id: $scope.items.length,
      name: "item " + $scope.items.length
    });
  };
  
  $scope.addNewItemBottom = function() {
    $scope.items = $scope.items.concat({
      id: $scope.items.length,
      name: "item " + $scope.items.length
    });
  };
})

.directive("keepScroll", function(){
  
  return {

    controller : function($scope){
      var element = 0;
      
      this.setElement = function(el){
        element = el;
      };

      this.addItem = function(item){
        console.group("Item");
        console.log("OffsetTop: " + item.offsetTop);
        console.log("ScrollTop: " + element.scrollTop);
        
        if(item.offsetTop <= element.scrollTop) {
          console.log("Adjusting scorlltop position");
          element.scrollTop = (element.scrollTop+item.clientHeight+1); //1px for margin
        } else {
          console.log("Not adjusting scroll");
        }
        console.groupEnd("Item");
      };
      
    },
    
    link : function(scope,el,attr, ctrl) {
      
     ctrl.setElement(el[0]);
      
    }
    
  };
  
})

.directive("scrollItem", function(){
  
  
  return{
    require : "^keepScroll",
    link : function(scope, el, att, scrCtrl){
      scrCtrl.addItem(el[0]);
    }
  };
});
.wrapper {
  width: 200px;
  height: 300px;
  border: 1px solid black;
  overflow: auto;
  /* Required for correct offsetParent */
  position: relative; 
}
.item {
  background-color: #ccc;
  height: 100px;
  margin-bottom: 1px;
}
<!DOCTYPE html>
<html>
<head>
<script src="//code.angularjs.org/1.3.0-beta.7/angular.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body ng-app="Demo" ng-controller="DemoCtrl">
  <div class="wrapper" keep-scroll>
    <div class="item" scroll-item ng-repeat="item in items">
      {{ item.name }}
    </div>
  </div>
  <button ng-click="addNewItemTop()">
    Add New Item Top
  </button>
  <button ng-click="addNewItemMiddle()">
    Add New Item Middle
  </button>
  <button ng-click="addNewItemBottom()">
    Add New Item Bottom
  </button>
</body>
</html>

答案 4 :(得分:3)

您可以推迟添加项目,直到用户将列表顶部滚动到视图中。以前没有必要渲染这些项目。

It could look like this(可能附加动画)。

答案 5 :(得分:2)

FaceBook方式:每个人都在暗示这一点,这是一个伪实现:

MYEXAMPLE

添加新对象后,将其弹出为“更多队列&#34;。

  <div style="height:15px">
      <button  ng-if="moreQueue"ng-click="transferWait()"> See More {{ moreQueue }}
      </button >
    </div>  
  <div class="wrapper">
    <div class="item" ng-repeat="item in items | orderBy: '-id'">
      {{ item.name }}
    </div>
  </div>

MessageHandlerController将至少有2个数组(我们应该将其视为队列&#39; s b / c我们将从底部向上弹出。

  • 有效消息
  • 等待消息

当您的 信号R /服务总线 填充WaitingQueue时,您的ng-if呈现的尺寸会增加$scope.SizeOfWaitingQueue=SizeOfWaitingQueue()。这种重新分配过程应该在每次迭代时进行,因此您不必脏检查 更多 大小的回购

答案 6 :(得分:1)

您需要向容器添加一个scrollspy指令,以更新其在每个用户滚动上的位置,并在每次重复渲染时收到通知,以便它可以将其自身重新定位到其保存状态。你的HTML可能看起来像这样

<div scrollspy id="myscrollspy">
     <ul>
       <li ng-repeat="" notifyscroll></li>
     </ul>
</div>

滚动间谍将具有所需的css溢出设置和scroll-x或scroll-y以跟踪当前滚动并避免污染范围它还应该观察来自ng-repeat的事件告诉他发生了变化,应该设置scrool。

ng-repeat可以通过附加启动事件的新指令通知滚动来通知。不确定currenttn版本的角度是否支持postrender事件。

定位滚动的方式取决于您是使用第三方库$ .scrollTop(pos)还是否。这样做或应该。 希望它有所帮助

答案 7 :(得分:0)

我相信,只有一些可能的解决方案

1)不要添加项目(按照其他答案)

2)在底部添加项目,因此列表不会移动。

3)在顶部和scroll the screen automatically添加项目,以便考虑新项目的高度,并且所有内容看起来都像以前一样。该列表将向下移动,但可视屏幕本身也将移动 - 因此相对来说没有任何东西可以移动。那些不属于列表的其他元素会,但实际上可能看起来很不错......

答案 8 :(得分:0)

您可以使用ng-animate解决此问题:

.animation('.keep-scroll', [function () {
    var keepScroll = function(element, leave){
        var elementPos = element.offset().top;
        var scrollPos = document.body.scrollTop;

        if(elementPos < scrollPos){
            var height = element[0].clientHeight;
            if(leave){
                height *= (-1);
            }
            document.body.scrollTop += height;
        }
    };

    return {
        enter: function (element, doneFn) {
            keepScroll(element);
            doneFn();
        },
        leave: function (element, doneFn) {
            keepScroll(element, true);
            doneFn();
        }
    };
}])

只需将css-class .keep-scroll分配给重复的元素,如下所示:

<div ng-repeat="item in items" class="keep-scroll">...</div>