30

我正在开发一个“艺术画廊”应用程序。

随意拉下github 上的源代码并使用它。

具有完整源代码的 Plunker。

目前让 Masonry 与 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功能:

  • 追加”项目,而是重新排列容器中的每个项目。
  • 当从结果集中过滤项目时触发重新加载是否有效。

在我在上面给出链接的应用程序的上下文中,这个问题变得很容易复制。

我正在寻找一种使用指令来利用的解决方案:

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

而不是.masonry('reload')每次都执行。

.masonry('reload')当从结果集中删除元素时。


编辑

该项目已更新为使用下面的工作解决方案。

在GitHub 上获取源代码

在Plunker上查看工作版本

4

6 回答 6

19

我一直在玩这个,@ganaraj 的回答非常简洁。如果您$element.masonry('resize');在他的控制器的appendBrick方法中添加 a 并考虑图像加载,那么它看起来就可以了。

这是一个 plunker 叉子:http ://plnkr.co/edit/8t41rRnLYfhOF9oAfSUA

这是必要的原因是因为只有在元素上初始化砌体或调整容器大小时才会计算列数,此时我们没有任何砖块,因此它默认为单列。

如果您不想使用“调整大小”方法(我认为它没有记录在案),那么您可以调用 $element.masonry() 但这会导致重新布局,因此您只想在何时调用它添加了第一块砖。

编辑:我已将上面的 plunker 更新为仅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);
      }); 
    }
  };
});
于 2013-05-19T12:35:34.870 回答
7

这不是您正在寻找的(prependappend),但应该正是您正在寻找的:

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

您的指令版本会reload针对每个brick. 对于整个列表更改,此版本仅触发一次重新加载。

方法很简单:

  1. bricks在父母中注册新masonry controller
  2. $watch对于注册bricks和火灾的变化masonry('reload')
  3. 删除元素时brick从注册表中删除 -bricks$on('$destroy')
  4. ?
  5. 利润

您可以扩展这种方法来做您想做的事情(使用prependand 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);
        });
      }
    };
  });

编辑: 我忘记了一件事(加载图像) - 加载所有图像后只需调用“重新加载”。稍后我会尝试编辑代码。

于 2013-05-20T23:22:57.263 回答
3

嘿,我刚刚为 AngularJS 制作了砌体指令,它比我见过的大多数实现简单得多。在这里查看要点:https ://gist.github.com/CMCDragonkai/6191419

它与 AMD 兼容。需要 jQuery、imagesLoaded 和 lodash。适用于动态数量的项目、AJAX 加载的项目(即使是初始项目)、窗口大小调整和自定义选项。Prepended items, appended items, reloaded items...等73行!

这是一个显示它工作的 plunkr:http ://plnkr.co/edit/ZuSrSh?p=preview (没有 AMD,但代码相同)。

于 2013-08-09T05:44:05.307 回答
1

Angular 记录最少的特性之一是它的指令控制器(尽管它位于www.angularjs.org - Tabs 的首页)。

这是一个使用这种机制的修改后的 plunker。

http://plnkr.co/edit/NmV3m6DZFSpIkQOAjRRE

人们确实使用指令控制器,但它已被用于(和滥用)它可能不适合的事情。

在上面的 plunker 中,我只修改了 directives.js 文件。指令控制器是指令之间通信的一种机制。有时,在一个指令中完成所有事情是不够/容易的。在这种情况下,您已经创建了两个指令,但是让它们交互的正确方法是通过指令控制器。

我无法弄清楚您何时想要添加以及何时想要添加。我目前只实现了“追加”。

另附注:如果资源尚未实现承诺,您可以自己实现它们。做到这一点并不难。我注意到您正在使用回调机制(我不推荐)。你已经在那里做出了承诺,但你仍然在使用我无法理解的回调。

这是否为您的问题提供了适当的解决方案?

有关文档,请参阅http://docs.angularjs.org/guide/directive > 指令定义对象 > 控制器。

于 2013-05-15T08:57:55.457 回答
0

我相信我遇到了完全相同的问题:

ng-repeat 循环中的许多图像,并且希望在加载并准备好时对其应用砌体/同位素。

问题是即使在 imagesLoaded 被回调后,仍有一段时间图像不“完整”,因此无法正确测量和布局。

我提出了以下适用于我的解决方案,并且只需要一个布局通道。它分三个阶段发生

  1. 等待图像加载(当最后一个从循环中添加时 - 使用 jQuery 图像加载插件)。
  2. 等待所有图像“完成”
  3. 布置图像。

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>

我希望这有帮助。

于 2013-05-21T08:39:05.510 回答
-1

您可以将它们都合并到一个指令中,而不是使用两个指令。就像是:

.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>
于 2013-05-16T10:55:52.447 回答