Пример ngAnimate для плавной сортировки ng-repeat?

Я хотел бы увидеть функциональный пример использования angular-animate (1.2x) для сортировки списка. (В инете я встречал только сломанные скрипки и т.д.):

ng-repeat для массива [A,B,C] и позже [C, B, A] должен:

  • Переместите А вниз
  • Переместите C наверх
  • Сохранить позицию Б

(Используя абсолютное верхнее позиционирование CSS или подобное.)

Пример использования staggering (transition-delay) является бонусом.


person dani    schedule 16.12.2014    source источник


Ответы (2)


Эта проблема

Добиться желаемого может быть немного сложно.

Распространенной попыткой является использование ng-style для вычисления позиции элемента на основе его индекса в списке:

<div ng-repeat="c in countries | orderBy:q" ng-style="{ 'top': $index * 20 + 'px' }">

Демо: http://plnkr.co/edit/anv4fIrMxVDWuov6K3sw?p=preview

Проблема в том, что анимируются только некоторые элементы, и только ближе к низу.

Почему это?

Рассмотрим следующий список, отсортированный по имени (похожий на тот, что в демо выше):

  • 2 - Дания
  • 3 - Норвегия
  • 1 - Швеция

Когда вы сортируете этот список по идентификатору, перемещается только один элемент — Швеция снизу вверх. Что на самом деле происходит, так это то, что элемент Швеции удаляется из DOM и снова вставляется в новую позицию. Однако, когда элемент вставляется в DOM, связанные с ним переходы CSS обычно не происходят (я говорю «нормально», поскольку в конечном итоге это зависит от того, как реализован рассматриваемый браузер).

Два других элемента остаются в DOM, получают новые top позиции, а их переходы анимируются.

Таким образом, с этой стратегией переходы анимируются только для элементов, которые на самом деле не перемещались в DOM.

Другая стратегия заключается в том, чтобы включить модуль ngAnimate и использовать класс CSS ng-move. Почти все примеры анимированных ng-repeats используют это.

Однако это не сработает по двум причинам:

  1. Класс ng-move будет применяться только к движущимся элементам (то есть только к элементу Швеции в приведенном выше примере).

  2. Класс ng-move применяется к элементу после того, как он был вставлен в новую позицию в DOM. У вас может быть CSS, который говорит «анимировать от непрозрачности от 0 до 1», но вы не можете «анимировать от старой позиции к новой», поскольку старая позиция неизвестна, и каждый элемент должен будет перемещаться на разное расстояние.

Решение

Решение, которое я использовал в прошлом, состоит в том, чтобы использовать ng-repeat для отображения списка, но никогда не использовать базовые данные. Таким образом, все элементы DOM останутся в DOM и могут быть анимированы. Чтобы правильно отображать элементы, используйте ng-style и пользовательское свойство, например:

ng-style="{ 'top': country.position * 20 + 'px' }"

Чтобы обновить свойство position, выполните следующие действия:

  1. Создайте копию базовых данных

    Вы можете использовать angular.copy для копирования всего массива, но с большими массивами это не будет хорошо для производительности. Это также было бы ненужным, поскольку каждому объекту в скопированном массиве потребуется только уникальное свойство и свойство для сортировки:

    var tempArray = countries.map(function(country) {
      var obj = {
        id: country.id
      };
      obj[property] = country[property];
      return obj;
    });
    

    В приведенном выше примере id — это уникальное свойство, а property — это переменная, содержащая имя свойства для сортировки, например name.

  2. Сортировать копию

    Для сортировки массива используйте Array.prototype.sort() с функцией сравнения:

    tempArray.sort(function(a, b) {
      if (a[property] > b[property])
        return 1;
      if (a[property] < b[property])
        return -1;
      return 0;
    });
    
  3. Установить позицию на индекс элемента в отсортированной копии

    countries.forEach(function(country) {
      country.position = getNewPosition(country.id);
    });
    
    function getNewPosition(countryId) {
      for (var i = 0, length = tempArray.length; i < length; i++) {
        if (tempArray[i].id === countryId) return i;
      }
    }
    

Есть возможности для улучшения, но это основа.

Демо: http://plnkr.co/edit/2Ramkg3sMW9pds9ZF1oc?p=preview

Я реализовал версию, в которой использовалось смещение, но это выглядело немного странно, поскольку элементы на мгновение накладывались друг на друга.

person tasseKATT    schedule 23.12.2014
comment
Спасибо за все усилия, отличный ответ! В какой-то момент я попытался предварительно вычислить атрибут для объектов массива, но я не сохранил фактический порядок нетронутым, что, по-видимому, является ключом к предотвращению манипуляций с DOM. - person dani; 25.12.2014

взгляните на это, это очень просто: CSS:

  .animate-enter, 
    .animate-leave
    { 
        -webkit-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
        -moz-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
        -ms-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
        -o-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
        transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
        position: relative;
        display: block;
    } 

    .animate-enter.animate-enter-active, 
    .animate-leave {
        opacity: 1;
        top: 0;
        height: 30px;
    }

    .animate-leave.animate-leave-active,
    .animate-enter {
        opacity: 0;
        top: -50px;
        height: 0px;
    }

HTML:

    <!doctype html>
<html ng-app>
<head>
  <meta charset="utf-8">
  <title>Top Animation</title>
  <script>document.write('<base href="' + document.location + '" />');</script>
  <link rel="stylesheet" href="style.css">
  <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
  <script src="http://code.angularjs.org/1.1.5/angular.js"></script>
</head>
<body ng-init="names=['Igor Minar', 'Brad Green', 'Dave Geddes', 'Naomi Black', 'Greg Weber', 'Dean Sofer', 'Wes Alvaro', 'John Scott', 'Daniel Nadasi'];">
  <div class="well" style="margin-top: 30px; width: 200px; overflow: hidden;">

        <ul class="nav nav-pills nav-stacked">
          <li ng-animate="'animate'" ng-repeat="name in names">
            <a href="#"> {{name}} </a>
          </li> 
      </ul>
    </form>
  </div>
</body>
</html>
person AngularLover    schedule 16.12.2014
comment
Это использует Angular 1.1? Есть пример для 1.2? Спасибо - person dani; 16.12.2014
comment
Вам нужно использовать как минимум AngularJS 1.1.5. на 1.2 не смотрел. Что вы ищете? - person AngularLover; 16.12.2014
comment
Я хотел бы увидеть, как это делается в 1.2. Возникли проблемы с ng-move и ng-style. - person dani; 16.12.2014