Подтвердить что ты не робот

Как сохранить положение прокрутки ng-repeat в AngularJS?

DEMO

Список объектов отображается с помощью ng-repeat. Предположим, что этот список очень длинный, и пользователь прокручивается в нижнюю часть, чтобы увидеть некоторые объекты.

Пока пользователь наблюдает за объектом, в верхнюю часть списка добавляется новый элемент. Это заставляет наблюдаемый объект изменять свое положение, то есть пользователь внезапно видит что-то еще в одном месте наблюдаемого объекта.

Как вы могли бы сохранить наблюдаемый объект в том же месте, когда будут добавлены новые предметы?

PLAYGROUND HERE

4b9b3361

Ответ 1

Это может быть решена довольно элегантно, используя свойство scrollTop div. Я использовал две директивы: один обрабатывает позицию прокрутки элемента оболочки, другие регистрируют новые элементы. Дайте мне крик, если что-то неясно.

DEMO

JS:

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

HTML:

<div class="wrapper" keep-scroll>
  <div class="item" scroll-item ng-repeat="item in items  | orderBy: '-id'">
    {{ item.name }}
   </div>
</div>

Ответ 2

Вы знаете, что другие люди пытаются решить эту проблему, используя другой подход с точки зрения пользовательского интерфейса. Они не просто POP новые элементы сверху, но вместо этого они показывают небольшую ссылку на ссылку, в которой указано, сколько новых элементов добавлено с момента последнего его проверки.

[2 new items, Click here to refresh]

item 5
item 4
item 3

Посмотрите, как твиттер решает это. **[Let me attach a screenshot for you shortly.]**

Я знаю, это немного противоречит желанию, но, возможно, это лучше с точки зрения UX? Пользователь хочет узнать, поступают ли новые элементы.

Ответ 3

Вы можете прокручивать количество высоты добавленных элементов

$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

Ответ 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>

Ответ 5

Вы можете отложить добавление элементов до тех пор, пока пользователь не прокрутит верхнюю часть списка в представлении. Нет смысла показывать элементы раньше.

Он может выглядеть так (возможно, с добавленной анимацией).

Ответ 6

FaceBook Way:. Все это предлагают, вот псевдо-реализация:

MYEXAMPLE

По мере добавления новых объектов поместите их в "Больше очереди".

  <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 массива (мы должны рассматривать как очередь b/c, которую мы будем делать снизу вверх.

  • Активные сообщения
  • Ожидающие сообщения

Поскольку ваша Signal R/Service Bus заполняет ваш WaitingQueue, ваш ng-if увеличивает размер и ваш $scope.SizeOfWaitingQueue=SizeOfWaitingQueue(). Этот процесс перераспределения должен происходить на каждой итерации, поэтому вам не нужно грязно проверять размер Больше Repo

Ответ 7

Вам нужно добавить директиву scrollspy в свой контейнер, который обновляет свою позицию на каждом прокрутке пользователя и получает уведомление о каждом повторном рендере, чтобы он мог переместить его в состояние сохранения. ваш html может выглядеть так:

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

у шпиона прокрутки будут требуемые настройки переполнения css и прокрутка-x или прокрутка-y, чтобы отслеживать текущий прокрутки и не загрязнять область, в которой она также должна следить за событием, происходящим из ng-repeat, которое сообщает ему произошло изменение, и он должен установить кривую.

ng-repeat может уведомить, добавив новую директиву, прокручивающую прокрутку, которая запускает событие. не уверены, поддерживает ли curretn версия angular событие postrender.

способ позиционирования прокрутки будет зависеть от того, используете ли вы стороннюю библиотеку $.scrollTop(pos) или нет. это сделает это или должно. надеюсь, что это поможет

Ответ 8

Есть, я считаю, только несколько возможных решений

1) Не добавляйте элемент (в соответствии с другим ответом)

2) Добавьте элемент внизу, поэтому список не перемещается.

3) Добавьте элемент вверх и автоматически прокрутите экран, чтобы учитывалась высота нового элемента, и все сохраняется, как и раньше. Список будет двигаться вниз, но сам экран просмотра также будет перемещаться - так что относительно ничего не будет видно, чтобы двигаться. Ну другие элементы, которые не входят в список, будут, но это может показаться довольно приятным...

Ответ 9

Вы можете решить эту проблему с помощью 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-класс .keep-scroll для повторяющихся элементов, например:

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