Grid Pinterest Like With Angular

After few days without posting because I was traveling back to france, today I will talk about one way to create a grid like Pinterest. There is many plugins to do that but most of them are really big and based on jquery so you need to load a lot of stuff just to use 10% of a library. So I decided to develop my own one just using directives in angular and this without jquery or alternative lib.

So first we will have to think about what we need.

We need:

  • to compute the width of the grid
  • to compute the height of the grid
  • to compute the number of elements per rows
  • to compute the positions of each elements

And my goal is to use it like that

1
2
3
<grid list="my_list">
  this is my element  <!-- the display of my element -->
</grid>

So let’s create the template for this new directive.

1
2
3
<div resize ng-style='computePositions()'>
  <div ng-repeat='elem in list'></div>
</div>

For now, nothing really complicated, one container with a style computed and inside the ng-repeat with my list. You can see the resize directive I introduce in a previous article.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
App.directive 'grid', ->
  {
    restrict: 'E'
    replace: true
    template: "<div resize ng-style='computePositions()'><div ng-repeat='elem in list'></div></div>"

    scope:
      list: "="

    controller: ($scope, $element, $attrs) ->
      parent     = $element.parent()[0]

      margin      =          -> parseInt($attrs.margin, 10) || 15
      width       =          -> parseInt($attrs.width, 10)  || 225
      elemWidth   =          -> width() + 2 * margin()
      elemHeight  = (height) -> height  + 2 * margin()
      elemPerLine =          -> parseInt parent.offsetWidth / elemWidth(), 10
      gridWidth   =          -> elemPerLine() * elemWidth()

      $scope.computePositions = ->
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
App.directive('grid', function() {
  return {
    restrict: 'E',
    replace: true,
    template: "<div resize ng-style='computePositions()'><div ng-repeat='elem in list'></div></div>",
    scope: {
      list: "="
    },
    controller: function($scope, $element, $attrs) {
      var elemHeight, elemPerLine, elemWidth, gridWidth, margin, parent, width;
      parent = $element.parent()[0];
      margin = function() {
        return parseInt($attrs.margin, 10) || 15;
      };
      width = function() {
        return parseInt($attrs.width, 10) || 225;
      };
      elemWidth = function() {
        return width() + 2 * margin();
      };
      elemHeight = function(height) {
        return height + 2 * margin();
      };
      elemPerLine = function() {
        return parseInt(parent.offsetWidth / elemWidth(), 10);
      };
      gridWidth = function() {
        return elemPerLine() * elemWidth();
      };
      return $scope.computePositions = function() {};
    }
  };
});
Toggle coffeescript/javascript

Here is the first part of the directive, we just define some functions to help a bit the computation, like the margin between each elements which can be gave as a parameter of the grid, the same for the width of elements then the full width and height of elements including margins, the number of elements by row and the width of the grid. Now we can create the computePositions function.

This function will compute the position for each elements and then return the style of the grid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$scope.computePositions = ->
  bottoms    = (0 for i in [0...elemPerLine()])
  maxHeight = 0
  for elem in $element.children()
    top   = null
    index = 0
    for bottom, i in bottoms when not top? or top > bottom
      top   = bottom
      index = i

    left   = (index * elemWidth()) % gridWidth() + margin()
    height = top + elemHeight(elem.offsetHeight)
    maxHeight = height if maxHeight < height

    bottoms[index]  = height
    elem.style.left = left + 'px'
    elem.style.top  = top + margin() + 'px'

  {
    width: "#{gridWidth()}px"
    height: "#{maxHeight}px"
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$scope.computePositions = function() {
  var bottom, bottoms, elem, height, i, index, left, maxHeight, top, _i, _j, _len, _len1, _ref;
  bottoms = (function() {
    var _i, _ref, _results;
    _results = [];
    for (i = _i = 0, _ref = elemPerLine(); 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
      _results.push(0);
    }
    return _results;
  })();
  maxHeight = 0;
  _ref = $element.children();
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    elem = _ref[_i];
    top = null;
    index = 0;
    for (i = _j = 0, _len1 = bottoms.length; _j < _len1; i = ++_j) {
      bottom = bottoms[i];
      if (!((top == null) || top > bottom)) {
        continue;
      }
      top = bottom;
      index = i;
    }
    left = (index * elemWidth()) % gridWidth() + margin();
    height = top + elemHeight(elem.offsetHeight);
    if (maxHeight < height) {
      maxHeight = height;
    }
    bottoms[index] = height;
    elem.style.left = left + 'px';
    elem.style.top = top + margin() + 'px';
  }
  return {
    width: "" + (gridWidth()) + "px",
    height: "" + maxHeight + "px"
  };
};
Toggle coffeescript/javascript

To keep it simple, this algorithm will create an array to store the bottom of each columns and add the position of the element to the smallest column (the one with the smallest bottom).

Now you just need to set your grid container in relative and your items as absolute and it’s done and because of the resize, everytime you will resize your window, all the positions will be recomputed to be sure to maximize the number of items in your container.

See the Pen cxgpF by Anthony (@antho1404) on CodePen

Comments

Copyright © 2014 - Anthony Estebe -