Implementing a grid layout can quickly turn into a tricky task. If you have grid items that are always the same height, then you can probably make do with a Flexbox grid or some other CSS grid implementation. However, if you’re dealing with user-generated content, chances are, you don’t have the luxury of equal height components. One longer or shorter component would either stretch the other components in its row, or leave some unpleasant whitespace at the bottom of the row. All of a sudden, our beloved CSS grid has become insufficient.
- Absolutely positioned elements are taken out of the regular flow of their container.
- An element with absolute positioning will be positioned relative to its closest, relatively positioned parent.
- We can move an element around by setting a value for the “position” attribute, and manipulating the values of the “top” and “left” attributes.
Using these facts, we will manually position each item in the grid. Let’s get to it then. The first thing we need is a grid class. This class will contain methods that will initialize and manipulate the grid. The following properties are defined for a grid.
- containerClass [String]: Class or id of the container
- container [Dom element]: Actual container element
- items [Dom elements]: The items in the grid
- static [Boolean]: Is the grid content static?
- size [Integer]: number of items in the grid
- gutter[Integer]: Space between elements in px
- maxColumns[Integer]: Maximum number of columns
- useMin[Boolean]: Append next element to the shortest column?
- started[Boolean]: Has the grid been initialized?
Let’s have a look at the constructor:
The last line of the constructor calls the init() function, which initializes the container element and its items. Before I show you that function, we need a way to know if every item in the grid is present. For this, we create a “ready” function:
For static content, the grid is always ready because every item is always present in the container. However, if you’re fetching data from an api and rendering with a frontend framework, there might be a slight delay before all the items are present. In that case, the grid is considered ready when the number of items in the container is equal to, or greater than, the expected number passed in through the constructor.
Now we can write the initialization function.
We’ve done a couple of important things here. First, the container must be relatively positioned to make sure that the items stay within the grid. Second, every item in the grid must be positioned absolutely. This places each item in the top-left corner of the container (0, 0).
Let’s setup the grid for the current container width. To begin, we need a function to calculate the width of a single column. We also need a function that calculates the possible number of columns, and the left over whitespace. Each column will be represented by an object containing information about its height and its index (its position in the list of columns):
The width of a column is, simply, the width of an item plus the width of the gutter (the space after the item). In the setup function, we create a list of columns based on the current width of the window and calculate the leftover whitespace, which will be used to center the grid items.
The next stage involves positioning each item properly, in the grid. We will select a column object from the list and append an element to the column it describes. There are two ways to go about distributing the items. We could distribute in-order by always selecting the next column in the list, or, we could go for a more even layout by selecting the shortest column. This is where the “useMin” property comes into play.
Now, we can position the items. For each item in the grid, we calculate its distance from the top and left using the height and index of the next column. We then update the column’s height.
The last line in the function stretches the container to the height of the longest column (basically the height of the grid). This is necessary because the container’s elements are absolutely positioned and, as a result, they would have no effect on the container’s height.
We’ve gotten to the final stage. Once
positionItems() is called, every item in the grid should move into its proper position. However, we need to handle the case where there’s a delay before the grid’s items can be rendered. We will do this by periodically checking the grid’s state. Once it’s ready, we can call
You might have noticed that
this.listen() gets called after the grid is initialized. The
listen() function calls
positionItems() if the grid is ready and listens for changes to the container’s size. If the grid isn’t ready, it waits until the grid is ready.
And that’s it! All you need to do now is create a Grid and listen for changes.
You can see a live demo on JSFIDDLE.
Now you know how to implement a flexible grid. Why reinvent the wheel though? Magic Grid is open source and super easy to use. There are other, much larger, libraries out there for more complex grid layouts but if all you need is a grid for equal width items, why add all that extra code to your project? I like keeping things simple and Magic Grid, with zero dependencies, is as simple as it gets. Magic Grid is hosted on Github so feel free to use and modify as you wish. If you find any bugs in the code, please open an issue on the issues page. I Hope you found this article useful in some way.
👌 Thanks for your time.