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

Масонство с AngularJS

Я разрабатываю приложение "художественная галерея".

Не стесняйтесь вытаскивать источник на github и поиграть с ним.

Plunker с полным исходным кодом

Текущая работа для того, чтобы заставить масонство играть хорошо с Angular:

.directive("masonry", function($parse) {
  return {
    restrict: 'AC',
    link: function (scope, elem, attrs) {
      elem.masonry({ itemSelector: '.masonry-brick'});
    }
  };     
})
.directive('masonryBrick', function ($compile) {
  return {
    restrict: 'AC',
    link: function (scope, elem, attrs) {
      scope.$watch('$index',function(v){
        elem.imagesLoaded(function () {
          elem.parents('.masonry').masonry('reload');
        });
      });
    }
  };    
});

Это не работает, потому что:

  • По мере того, как контент растет, также накладные расходы на перезагрузку во всем контейнере.

Функция reload:

  • Не добавляет элементы "append", а перенастраивает каждый элемент в контейнере.
  • Работает ли для запуска перезагрузки, когда элементы отфильтровываются из набора результатов.

В контексте приложения, которое я привел выше, эта проблема очень легко реплицируется.

Я ищу решение, которое будет использовать директивы для использования:

.masonry('appended', elem) и .masonry('prepended', elem)

Вместо выполнения .masonry('reload') каждый раз.

.masonry('reload'), когда элементы удаляются из набора результатов.


ИЗМЕНИТЬ

Проект был обновлен для использования рабочего решения ниже.

Возьмите источник на GitHub

См. рабочую версию на Plunker

4b9b3361

Ответ 1

Я играл с этим немного больше, а ответ @ganaraj довольно опрятен. Если вы придерживаетесь $element.masonry('resize'); в своем методе appendBrick контроллера и учетной записи загрузка изображений тогда выглядит так, как будто она работает.

Здесь вилка плункера с ней: http://plnkr.co/edit/8t41rRnLYfhOF9oAfSUA

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

Если вы не хотите использовать метод "resize" (я не думаю, что он задокументирован), вы можете просто вызвать $element.masonry(), но это приведет к повторной компоновке, поэтому вы хотите только назовите его, когда добавлен первый кирпич.

Изменить: Я обновил плункер выше, чтобы вызывать только resize, когда список растет выше 0 длины и выполняет только одну "перезагрузку", когда несколько кирпичей удаляются в одном и том же $digest цикл.

Код директивы:

angular.module('myApp.directives', [])
  .directive("masonry", function($parse, $timeout) {
    return {
      restrict: 'AC',
      link: function (scope, elem, attrs) {
        elem.masonry({ itemSelector: '.masonry-brick'});
        // Opitonal Params, delimited in class name like:
        // class="masonry:70;"
        //elem.masonry({ itemSelector: '.masonry-item', columnWidth: 140, gutterWidth: $parse(attrs.masonry)(scope) });
      },
      controller : function($scope,$element){
          var bricks = [];
          this.appendBrick = function(child, brickId, waitForImage){
            function addBrick() {
              $element.masonry('appended', child, true);

              // If we don't have any bricks then we're going to want to 
              // resize when we add one.
              if (bricks.length === 0) {
                // Timeout here to allow for a potential
                // masonary timeout when appending (when animating
                // from the bottom)
                $timeout(function(){
                  $element.masonry('resize');  
                }, 2);  
              }

              // Store the brick id
              var index = bricks.indexOf(brickId);
              if (index === -1) {
                bricks.push(brickId);
              }
            }

            if (waitForImage) {
              child.imagesLoaded(addBrick);      
            } else {
              addBrick();
            }
          };

          // Removed bricks - we only want to call masonry.reload() once
          // if a whole batch of bricks have been removed though so push this
          // async.
          var willReload = false;
          function hasRemovedBrick() {
            if (!willReload) {
              willReload = true;
              $scope.$evalAsync(function(){
                willReload = false;
                $element.masonry("reload");
              });
            }
          }

          this.removeBrick = function(brickId){
              hasRemovedBrick();
              var index = bricks.indexOf(brickId);
              if (index != -1) {
                bricks.splice(index,1);
              }
          };
      }
    };     
  })
  .directive('masonryBrick', function ($compile) {
    return {
      restrict: 'AC',
      require : '^masonry',
      link: function (scope, elem, attrs, MasonryCtrl) {

      elem.imagesLoaded(function () {
        MasonryCtrl.appendBrick(elem, scope.$id, true);
      });

      scope.$on("$destroy",function(){
          MasonryCtrl.removeBrick(scope.$id);
      }); 
    }
  };
});

Ответ 2

Это не то, что вы ищете (prepend и append), но должно быть именно то, что вы ищете:

http://plnkr.co/edit/dmuGHCNTCBBuYpjyKQ8E?p=preview

Ваша версия директивы запускает reload для каждого brick. Эта версия запускает только перезагрузку только один раз для изменения всего списка.

Подход очень прост:

  • Зарегистрировать новый bricks в родительском masonry controller
  • $watch для изменений зарегистрированного bricks и огня masonry('reload')
  • Удалите brick из bricks реестра при удалении элемента - $on('$destroy')
  • Доход

Вы можете расширить этот подход, чтобы делать то, что вы хотели (используйте prepend и append), но я не вижу причин, почему вы захотите это сделать. Это также будет намного сложнее, так как вам придется вручную отслеживать порядок элементов. Я также не верю, что это будет быстрее - наоборот, это может быть медленнее, так как вам придется запускать несколько append/prepend, если вы меняете много кирпичей.

Я не совсем уверен, но я думаю, вы могли бы использовать ng-animate для этого (версия анимации JavaScript)

Мы внедрили нечто подобное для событий tiling в нашем приложении для календаря. Это решение оказалось самым быстрым. Если у кого-то есть лучшее решение, я бы с удовольствием это увидел.

Для тех, кто хочет выполнить код:

angular.module('myApp.directives', [])
  .directive("masonry", function($parse) {
    return {
      restrict: 'AC',
      controller:function($scope,$element){
        // register and unregister bricks
        var bricks = [];
        this.addBrick = function(brick){
          bricks.push(brick)
        }
        this.removeBrick = function(brick){
          var index = bricks.indexOf(brick);
          if(index!=-1)bricks.splice(index,1);
        }
        $scope.$watch(function(){
          return bricks
        },function(){
          // triggers only once per list change (not for each brick)
          console.log('reload');
          $element.masonry('reload');
        },true);
      },
      link: function (scope, elem, attrs) {
        elem.masonry({ itemSelector: '.masonry-brick'});
      }
    };     
  })
  .directive('masonryBrick', function ($compile) {
    return {
      restrict: 'AC',
      require:'^masonry',
      link: function (scope, elem, attrs,ctrl) {
        ctrl.addBrick(scope.$id);

        scope.$on('$destroy',function(){
          ctrl.removeBrick(scope.$id);
        });
      }
    };
  });

Edit: есть одна вещь, о которой я забыл (загрузка изображений) - просто позвоните "перезагрузить", когда все изображения загружены. Я попытаюсь позже отредактировать код.

Ответ 3

Эй, я только что сделал дирекцию кладки для AngularJS, которая намного проще, чем большинство реализаций, которые я видел. Проверьте суть: https://gist.github.com/CMCDragonkai/6191419

Совместим с AMD. Требуется jQuery, imagesLoaded и lodash. Работает с динамическим количеством элементов, загруженными элементами AJAX (даже с начальными элементами), изменением размера окна и настраиваемыми параметрами. Преобладающие предметы, приложенные предметы, перезагруженные предметы... и т.д. 73 строки!

Здесь plunkr показывает, что он работает: http://plnkr.co/edit/ZuSrSh?p=preview (без AMD, но тот же код).

Ответ 4

Одна из наименее документированных функций Angular - это его контрольные контроллеры (хотя она находится на первой странице www.angularjs.org - вкладки).

Вот модифицированный плункер, который использует этот механизм.

http://plnkr.co/edit/NmV3m6DZFSpIkQOAjRRE

Люди используют Directive Controllers, но были использованы (и злоупотребляли) для вещей, которые, вероятно, не были предназначены.

В plunker выше я только изменил файл directives.js. Директивные контроллеры - это механизм для связи между директивами. Иногда, это недостаточно/легко сделать все в одной директиве. В этом случае вы уже создали две директивы, но правильный способ заставить их взаимодействовать через управляющий контроллер.

Мне не удалось выяснить, когда вы хотите добавить и когда хотите добавить. В настоящее время я реализовал "append".

Также на стороне примечания: если ресурсы уже не реализуют promises, вы можете реализовать их самостоятельно. Это не очень сложно сделать. Я заметил, что вы используете механизм обратного вызова (который я бы не рекомендовал). Вы уже ввели promises, но все же вы используете обратные вызовы, которые я не смог понять, почему.

Обеспечивает ли это правильное решение вашей проблемы?

Для документации см. http://docs.angularjs.org/guide/directive > Объект определения директивы > контроллер.

Ответ 5

Я считаю, что у меня была такая же проблема:

Много изображений в цикле ng-repeat и хотите применить к ним кладки/изотопы, когда они загружены и готовы.

Проблема заключается в том, что даже после обратного вызова imagesLoaded существует период времени, когда изображения не являются "завершенными" и поэтому не могут быть измерены и правильно отложены.

Я придумал следующее решение, которое работает для меня и требует только одного макета. Это происходит в три этапа

  • Дождитесь загрузки изображений (когда последний добавлен из цикла - использует загруженный плагин jQuery).
  • Дождитесь завершения всех изображений
  • Разместите изображения.

angularApp.directive('checkLast', function () {
    return {
        restrict: 'A',
        compile: function (element, attributes) {
            return function postLink(scope, element) {
                if (scope.$last === true) {
                    $('#imagesHolder').imagesLoaded(function () {
                        waitForRender();
                    });
                }
            }
        }
    }
});

function waitForRender() {
    //
    // We have to wait for every image to be ready before we can lay them out
    //
    var ready = true;
    var images = $('#imagesHolder').find('img');
    $.each(images,function(index,img) {
        if ( !img.complete ) {
            setTimeout(waitForRender);
            ready = false;
            return false;
        }
    });
    if (ready) {
        layoutImages();
    }
}

function layoutImages() {
    $('#imagesHolder').isotope({
        itemSelector: '.imageHolder',
        layoutMode: 'fitRows'
    });
}

Это работает с макетом, подобным этому

<div id="imagesHolder">
    <div class="imageHolder"
         check-last
         ng-repeat="image in images.image"
        <img ng-src="{{image.url}}"/>
    </div>
</div>

Надеюсь, это поможет.

Ответ 6

Вместо использования двух директив вы можете включить их в одну директиву. Что-то вроде:

.directive("masonry", function($timeout) {
    return {
        restrict: 'AC',
        template: '<div class="masonry-brick" ng-repeat="image in pool | filter:{pool:true}">' +
                        '<span>{{image.albumTitle|truncate}}</span>' +
                        '<img ng-src="{{image.link|imageSize:t}}"/>' +
                  '</div>',
        scope: {
            pool: "="
        },
        link: function(scope, elem, attrs){
            elem.masonry({itemSelector: '.masonry-brick'});

            // When the pool changes put all your logic in for working out what needs to be prepended
            // appended etc
            function poolChanged(pool) {

                //... Do some logic here working out what needs to be appended, 
                // prepended...

                // Make sure the DOM has updated before continuing by doing a $timeout
                $timeout(function(){
                    var bricks = elem.find('.masonry-brick');
                    brick.imagesLoaded(function() {
                        // ... Do the actual prepending/appending ...
                    });
                });
            }

            // Watch for changes to the pool
            scope.$watch('pool', poolChanged, true); // The final true compares for 
                                                     // equality rather than reference
        }
    }
});

и html:

<div class="masonry" pool="pool"></div>