jQuery 用 AngularJS 砌筑

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16504151/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-26 17:16:05  来源:igfitidea点击:

Masonry with AngularJS

javascriptjqueryangularjs

提问by Dan Kanze

I am developing an "art gallery" app.

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

Feel free to pull down the source on githuband play around with it.

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

Plunker with full source.

具有完整源代码的Plunker。

The current work around for getting Masonry to play nice with Angular:

当前使 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');
        });
      });
    }
  };    
});

This doesn't work well because:

这行不通,因为:

  • As the content grows, so does the overhead of tiggering reload on the entire container.
  • 随着内容的增长,在整个容器上触发重新加载的开销也会增加。

The reloadfunction:

reload函数:

  • Does not"append" items, rather re-arranges every item in the container.
  • Doeswork for triggering a reload when items are filtered out of a result set.
  • 附加”项目,而是重新排列容器中的每个项目。
  • 当项目从结果集中被过滤掉时,是否可以触发重新加载。

In context with the app I've given links to above, this problem becomes very easy to replicate.

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

I am looking for a solution that will use directives to leverage:

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

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

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

Rather than executing .masonry('reload')every time.

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

.masonry('reload')for when elements are removed from result set.

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



EDIT

编辑

The project has been updated to use the working solution below.

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

Grab the source on GitHub

GitHub 上获取源代码

See a working version on Plunker

Plunker上查看工作版本

回答by James Sharp

I've been playing around with this a bit more and @ganaraj's answer is pretty neat. If you stick a $element.masonry('resize');in his controller's appendBrickmethod and account for the images loading then it looks like it works.

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

Here's a plunker fork with it in: http://plnkr.co/edit/8t41rRnLYfhOF9oAfSUA

这是一个带有它的 plunker 叉:http://plnkr.co/edit/8t41rRnLYfhOF9oAfSUA

The reason this is necessary is because the number of columns is only calculated when masonry is initialized on the element or the container is resized and at this point we haven't got any bricks so it defaults to a single column.

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

If you don't want to use the 'resize' method (I don't think it's documented) then you could just call $element.masonry() but that causes a re-layout so you'd want to only call it when the first brick is added.

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

Edit:I've updated the plunker above to only call resizewhen the list grows above 0 length and to do only one "reload" when multiple bricks are removed in the same $digest cycle.

编辑:我已经更新了上面的 plunker,只resize在列表长度超过 0 时调用,并且在同一个 $digest 周期中移除多个砖块时只执行一次“重新加载”。

Directive code is:

指令代码为:

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

回答by g00fy

This is not exacly what you are looking for (prependand append), but should be just what you are looking for:

这不完全是您要寻找的(prependappend),而应该正是您要寻找的:

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

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

Your version of the directive triggers reloadfor every brick. This version triggers only reload only once for the whole list change.

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

The approach is very simple:

方法很简单:

  1. Register new bricksin parent masonrycontroller
  2. $watchfor changes in the registered bricksand fire masonry('reload')
  3. Remove brickfrom bricksregistry when you are removing the element - $on('$destroy')
  4. ?
  5. Profit
  1. bricks在父级注册新masonrycontroller
  2. $watch对于注册bricks和火灾的变化masonry('reload')
  3. 删除元素时brickbricks注册表中删除 -$on('$destroy')
  4. ?
  5. 利润

You can extend this approach to do what you wanted (use prependand append) but I don't see any reason why you would want to do that. This also would be much more complicated, since you would have to manually track the order of the elements. I also don't belive it would be any faster - on the contrary it may be slower, since you would have to trigger multiple append/prependif your changes a lot of bricks.

您可以扩展这种方法来做您想做的事情(使用prependappend),但我看不出您有任何理由要这样做。这也会更加复杂,因为您必须手动跟踪元素的顺序。我也不相信它会更快 - 相反它可能会更慢,因为append/prepend如果您更改了很多砖,您将不得不触发多个。

I am not quite sure, but I guess you could use ng-animatefor this (the JavaScriptanimation version)

我不太确定,但我想你可以用ng-animate这个(JavaScript动画版)

We have implemented something similar for tilingevents in our calendar app. This solution turned out to be the fastest. If anyone has better solution, I'd love to see that.

我们已经为tiling日历应用程序中的事件实现了类似的功能。结果证明这个解决方案是最快的。如果有人有更好的解决方案,我很乐意看到。

For those who want to se the code:

对于那些想要设置代码的人:

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:there is one thing I forgot about (loading images) - just call 'reload' when all images were loaded. Ill try to edit the code later.

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

回答by CMCDragonkai

Hey I just made masonry directive for AngularJS that is far more simpler than most of the implementations I've seen. Check the gist out here: https://gist.github.com/CMCDragonkai/6191419

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

It's compatible with AMD. Requires jQuery, imagesLoaded and lodash. Works with dynamic amount of items, AJAX loaded items (even with initial items), window resizing, and custom options. Prepended items, appended items, reloaded items... etc. 73 lines!

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

Here's a plunkr showing it work: http://plnkr.co/edit/ZuSrSh?p=preview(without AMD, but the same code).

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

回答by ganaraj

One of the least documented feature of Angular is its Directive Controllers ( though it is on the front page of www.angularjs.org- Tabs ).

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

Here is a modified plunker that makes use of this mechanism.

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

http://plnkr.co/edit/NmV3m6DZFSpIkQOAjRRE

http://plnkr.co/edit/NmV3m6DZFSpIkQOAjRRE

People do use Directive Controllers but it has been used ( and abused ) for things it probably was not meant for.

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

In the plunker above I have only modified the directives.js file. Directive controllers are a mechanism for communication between directives. Sometimes , it is not sufficient / easy to do everything in one directive. In this case, you have already created two directives but the right way to make them interact is through a directive controller.

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

I was not able to figure out when you wanted to prepend and when you wanted to append. I have only implemented "append" currently.

我无法弄清楚你什么时候想要添加以及什么时候想要添加。我目前只实施了“追加”。

Also on a side note : If resources doesnt already implement promises, you can implement them yourself. It isnt really hard to do that. I noticed you are using a callback mechanism (which I wouldnt recommend ). You have already put in promises there but still you are using callbacks which I was not able to understand why.

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

Does this provide a proper solution to your problem ?

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

For documentation see http://docs.angularjs.org/guide/directive> Directive Definition Object > controller.

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

回答by Conrad

I believe that I have had exactly the same problem:

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

Many images in a ng-repeat loop, and want to apply masonry/isotope to them when they are loaded and ready.

ng-repeat 循环中的许多图像,并希望在加载并准备好后将砌体/同位素应用于它们。

The issue is that even after imagesLoaded is called back there is a period of time when the images are not 'complete', and so can not be measured and layed out properly.

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

I have come up with the following solution that works for me and only requires one layout pass. It occurs in three stages

我想出了以下对我有用的解决方案,并且只需要一次布局传递。它发生在三个阶段

  1. Wait for images to load (when the last one is added from the loop - uses the jQuery images loaded plugin).
  2. Wait for all images to 'complete'
  3. Layout the images.
  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'
    });
}

This works with layout like this

这适用于这样的布局

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

I hope this helps.

我希望这有帮助。

回答by James Sharp

Rather than using two directives you could incorporate them both into one directive. Something like:

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

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

and html usage:

和 html 用法:

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