Truly equalised heights on Divis Blog Grid Cards

In this article we will cover the necessary steps to make something work which currently annoys a whole lot of people who use Divi: the uneven sized cards in the Divi Blog Grid module. We highly recommend you read through the whole article to understand what we are doing and why we are doing it, so in case a future Divi update breaks the code, you can fix it yourself. It’s always good to understand the things you do instead of simply copy-pasting snippets which you don’t understand.

The JavaScript code we will use is quiet easy to understand and besides that, we also need a little bit of CSS knowledge. Both are skills which every website owner should have a basic understanding of – and even if you don’t know much about JS and CSS, today is the day you can learn your first of many lessons. 👊

The Problem

Unequal sized article cards in Divis Blog module

Lets start with the problem. It’s always good to know what you want before you start. A clear goal avoids confusion on the way. So whats the problem with Divis Blog Grid module? Nothing, if you like masonry layouts. But what if you want equally sized cards? That is not so easy. We could say:

How can we make all cards equally sized in the grid?

The module is lacking any settings to equalise the height of the cards. Pure CSS also won’t work due to the modules HTML output . On desktops and tablets, Divi puts all the articles in multiple, independent columns. That prevents us from using CSS to get equally sized cards – at least without giving fixed height values to all articles – but that either leads to overflowing text or weird empty areas. It would be better to have a dynamic way of findig the largest visible article and adjust the height of all other items. Now the problem becomes clearer again:

How can we find the largest article in the visible grid and apply its size to all other visible article in the same grid?

Now that sounds like a good description of the problem. We can easily solve these kind of tasks using JavaScript. Let’s do it! It’s not so difficult. 👌

The Solution

Let’t get to the solution. We’ll go over it piece by piece. If you are just here for the code, feel free to scroll all the way to the bottom to just copy the finished result. But don’t complain or blame us if something is not working. Read through the tutorial and you’ll be able to fix any issues yourself. Now everybody: grab a coffee or a tea, get seated and enjoy the show.

Preparing JavaScript and running your first lines of code

We start with a simple script tag. We’ll need it for putting our own code in Divis Theme options later. If you are more advanced and want to use the code in an existing JavaScrip file, e. g. from your Child Theme, you can skip this step.

1

2

3

<script>

</script>

In between the script tag, we can now start writing our JavaScript. To make it a little bit easier to deal with the HTML, we can use jQuery. jQuery is just a library for JavaScript, which comes with a lot of handy functions. It’s already shipped within Divi so we can just make use of it. But jQuery in WordPress comes with one limitation or downside. You have to write “jQuery” instead of the short form “$” every time you want to use it, which can be quiet annoying. So to enable the “$” shortcut, we encapsulate our actual code in a function and pass jQuery itself as the argument to the function. By defining the parameter of the function as “$”, we now can use $ as a synonym for jQuery. Add this code in between the script tags

1

2

3

(function($){

})(jQuery);

Enough with the preparations, time to start coding. So what are the steps to achieve our goal. Lets break them down:

Get all the visible articles

Find out which one the largest is

Get the size from the largest article

Apply the size to every other article

So how do we get the articles? Well, we can use jQuery to get any HTML-element on the page, if we know the CSS selector of that element. So lets check the HTML of our blog grid to find out the required CSS selector. To do that, use your browsers debug-tools. Every major browser comes with some sort of these tools. Often you can access them by right-clicking somewhere on a website and choose “inspect”.

If you see this screen for the first time, it might look a bit scary but in reality it just wants to be your best friend. In the upper left you have the element selection tool (the one with the cursor on it). If you click it, you can click on any element on your page and the corresponding element will light up. In the lower part, you have the HTML on the left and the CSS on the right. This is the complete HTML and CSS as it is seen and interpreted by the browser.

After selecting the article in the grid, we can see a bunch of things. First of all, every article seems to be an “article” element. Articles are organised in three columns. The columns are inside a tree of “div” elements. The outmost element, which is the blog grid itself, has the classes “et_pb_module”, “et_pb_blog_grid_wrapper”, “et_pb_blog_0” and fourth class which just happens to be “sensei-grid”. But this is no magical coincident. We actually gave the blog this class intentionally. Why? There are multiple reasons: we might want to use the code on multiple pages of the same websites but we do not want to use the functionality we are developing on every single blog grid on the whole site. Thats why we use a custom CSS class. Then we can decide on which blog grid we want to enable the functionality and other modules on the same site are not affected.

Of course you could also use any other element selector to get a common surrounding element of the columns. For the sake of this tutorial, we will go with “.sensei-grid”. The “.” before the element indicates that it is a class. In CSS you can address elements by their element tag (e. g. “article” or “grid”), by their CSS ID (e. g. “#myelementid”) or their CSS Class (e. g. “.sensei-grid”). In the Advanced tab of your modules, rows or sections you can add your own CSS ID or multiple classes. Here you don’t add the “.” but just the classes you want (e. g. “my-first-class my-second-class”). So lets find out how tall the articles are. Write this code inside the jQuery function we created earlier:

1

2

3

4

5

6

7

$(document).ready(function(){

$(".sensei-grid").each(function(){

$(this).find("article").each(function(){

console.log("this article is "+$(this).height()+"px height");

});

});

});

First, we use the “$” to call jQuery. We pass in “document” – which is a JavaScript keyword, a reference to the “page” which the code runs on – and then call the “ready” function to which we pass a callback. We do this to tell jQuery that we want to wait till the whole document (the current page) is ready. If the page is not ready, we can’t safely manipulate the content. Instead we simply wait. Once the document is ready, the function we passed to the “ready” function gets executed by jQuery.

Inside the callback, we use jQuery again. This time to get each Sensei Grid. We simply pass the CSS selector to jQuery and get all the elements. In CSS, a class selector can return multiple elements. Since we could have two or more blog modules on the same page, we want to find the largest article for each one separately so using the “each” function and passing in another callback function, we can execute the same code multiple times.

Again we use jQuery but this time with the “this” keyword, which is a reference to the outer element on which we run the “each” function. Imagine you have two blog modules. jQuery will find the first one and “$(this)” will be the blog grid with the classes “sensei-grid” and “et_pb_blog_0”. When jQuery runs the same callback for the second module, $(this) will represent the blog grid wich has the CSS classes “sensei-grid” but this time it has the class “et_pb_blog_1” – another module.

Next we use the “find” function to find every “article” element inside the blog module. This will find all articles, no matter in which column they are. But only in the current module. Since we now again potentially have more than one result, we use the “each” function again to iterate over each article. Now we can finally print out the height of each article on the console.

The full code by now should look like this:

1

2

3

4

5

6

7

8

9

10

11

<script>

(function($){

$(document).ready(function(){

$(".sensei-grid").each(function(){

$(this).find("article").each(function(){

console.log("this article is "+$(this).height()+" height");

});

});

});

})(jQuery);

</script>

Copy the code and head over to your Divi Theme Options. Select the “Integration” tab and paste the code into the “Add code to the < head > of your blog” field.

Save your settings and open the page with the blog grid. Open the inspector of your browser again, but this time check out the console. If you have done everything right, you should see the output. Maybe you need to force refresh the page by holding down the shift key on your keyboard while refreshing the page. Also sometimes caching plugins can be an issue and you might need to purge the cache to see the result.

Get the tallest article and change the others articles heights

Alright. Now that we have all the articles in the blog module and know their individual heights, it’s time for the next step: find out which article is the tallest and then apply it’s height to all other articles. To do this, we can simply store each height in an array and then use JavaScripts Math library to find the largest number in this field. The last step is to use the max value and apply it to the other articles. Since we need the articles multiple times, lets remove the innermost each-loop (starting with “$(this).find(…)” and instead use this code.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

vararticles=$(this).find("article");

varheights=[];

articles.each(function(){

varheight=0;

height+=$(this).outerHeight()-$(this).height();

height+=$(this).find(".et_pb_image_container").outerHeight(true);

height+=$(this).find(".entry-title").outerHeight(true);

height+=$(this).find(".post-meta").outerHeight(true);

height+=$(this).find(".post-content").outerHeight(true);

heights.push(height);

});

varmax_height=Math.max.apply(Math,heights);

articles.each(function(){

$(this).height(max_height);

});

We still first get all the articles but this time we store all the articles in a variable called articles. Then we create a array to store the heights of each article. Next we loop over each article, calculate the height from its content and save the height in the array.

Are you asking yourself where the 4 class selectors come from? We used the inspector again to look at the HTML structure of each article. You’ll find that these are the exact elements in our article. But we also need to consider paddings and borders. We can calculate that by subtracting the elements height from its outer height.

Once we have all the heights, we can use the Math library to get the max value from the array and store it in another variable (learn more about Math here). Finally we loop over each article again and set the height to the value we previously found. Phew. Our code now should look like this:

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

<script>

(function($){

$(document).ready(function(){

$(".sensei-grid").each(function(){

vararticles=$(this).find("article");

varheights=[];

articles.each(function(){

varheight=0;

height+=$(this).find(".et_pb_image_container").outerHeight(true);

height+=$(this).find(".entry-title").outerHeight(true);

height+=$(this).find(".post-meta").outerHeight(true);

height+=$(this).find(".post-content").outerHeight(true);

heights.push(height);

});

varmax_height=Math.max.apply(Math,heights);

articles.each(function(){

$(this).height(max_height);

});

});

});

})(jQuery);

</script>

Refresh the page with the blog grid and you already should see the result. If not, you might need to force a refresh again. You might ask yourself why we calculate the height of the article by using the individual content heights instead of simply using outerHeight() on each of the articles. Why it is important to calculate the height from scratch in each run, we will see in a second.

React to size changes

There are a bunch of things going on under Divis hood which can cause a size change of the blog grids content. But not only that. The user could simply resize the browser or flip the screen of a mobile device. That can activate certain media queries and the size of the content changes. We need to react to size changes and recalculate the article heights based on the new content size. If we would use the articles outerHeight() we would run into problems as after the first calculation run all articles already have the same height. Therefore the height will never change after the first run.

Okay so lets handle size changes. Modern browsers support Mutation Observers. A MutationObserver is a JavaScript object which allows you to monitor changes in a given element. This in combination with a observer on the window should be sufficient for us to deal with the content size changes.

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

$(".sensei-grid").each(function(){

varblog=$(this);

equalise_articles(blog);

varobserver=newMutationObserver(function(mutations){

equalise_articles(blog);

});

varconfig={subtree:true,childList:true};

observer.observe(blog[0],config);

});

functionequalise_articles(blog){

vararticles=blog.find("article");

varheights=[];

articles.each(function(){

varheight=0;

//height += $(this).outerHeight() - $(this).height();

height+=$(this).find(".et_pb_image_container").outerHeight(true);

height+=$(this).find(".entry-title").outerHeight(true);

height+=$(this).find(".post-meta").outerHeight(true);

height+=$(this).find(".post-content").outerHeight(true);

heights.push(height);

});

varmax_height=Math.max.apply(Math,heights);

articles.each(function(){

$(this).height(max_height);

});

}

As you can see, for a better reusability we moved the code from inside the each-loop of the sensei-grids into its own function. We do this for better reusability. In the each-loop we now get the blog grid we are iterating over. We pass each grid to the function we just created. This will apply the first round of equalising the heights. Then we setup a MutationObserver for each blog grid.

Every time the MutationObserver detects a change inside the module, it will call the callback function we pass into the constructor. Of course we use this opportunity to re-apply the equalising. Finally we need to handle a resize of the browser window. We can observe the browser window for resizing and yet again equalise the blog grids.

1

2

3

4

5

$(window).resize(function(){

$(".sensei-grid").each(function(){

equalise_articles($(this));

});

});

Our code now looks as the following:

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

39

40

41

42

43

44

45

46

47

<script>

(function($){

$(document).ready(function(){

$(window).resize(function(){

$(".sensei-grid").each(function(){

equalise_articles($(this));

});

});

$(".sensei-grid").each(function(){

varblog=$(this);

equalise_articles(blog);

varobserver=newMutationObserver(function(mutations){

equalise_articles(blog);

});

varconfig={subtree:true,childList:true};

observer.observe(blog[0],config);

});

functionequalise_articles(blog){

vararticles=blog.find("article");

varheights=[];

articles.each(function(){

varheight=0;

//height += $(this).outerHeight() - $(this).height();

height+=$(this).find(".et_pb_image_container").outerHeight(true);

height+=$(this).find(".entry-title").outerHeight(true);

height+=$(this).find(".post-meta").outerHeight(true);

height+=$(this).find(".post-content").outerHeight(true);

heights.push(height);

});

varmax_height=Math.max.apply(Math,heights);

articles.each(function(){

$(this).height(max_height);

});

}

});

})(jQuery);

</script>

So far so good. When you reload the page, you will see that the articles heights are perfectly equal. Also when you resize the browser window you still should end up with a nicely, equalised blog grid. Here we created some articles to show you how it looks like when you don’t have images on all articles:

Fixing the paging

Speaking about images. There is one last thing we need to take care of. When you enable paging on the blog grid module and click the button to show older entries, you’ll run into a problem. The articles are completely wrongly sized. Thats because the images take some time to load which interferes with our calculations. So the final step is to add yet another function in which we can call our function to equalise the heights after the images are loaded:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

$(document).ajaxComplete(function(){

$(".sensei-grid").imagesLoaded().then(function(){

$(".sensei-grid").each(function(){

equalise_articles($(this));

});

});

});

$.fn.imagesLoaded=function(){

var$imgs=this.find('img[src!=""]');

if(!$imgs.length){return$.Deferred().resolve().promise();}

vardfds=[];

$imgs.each(function(){

vardfd=$.Deferred();

dfds.push(dfd);

varimg=newImage();

img.onload=function(){dfd.resolve();}

img.onerror=function(){dfd.resolve();}

img.src=this.src;

});

return$.when.apply($,dfds);

}

As you might know, Divis blog grid uses Ajax to load older entries without leaving and reloading the whole page. We can use this to our advantage and wait till the Ajax call finishes. We then use a little helper function to execute our own code after all images inside the grids have been loaded. Once the images are completely loaded and their size is known, we can re-calculate the heights. Now we get the correct height including the image and no article is to short – no text ist cut off.

The helper function is a little bit “advanced”. Basically we scan each grid for images and then use the Promise library to execute some code after certain conditions are met. In our case the loading (or the failure to load) of the images of the blog grids.

Final Code and Thoughts

Thats it, we are done. Lets have a look at our final result. It came out less than 100 lines of code and wasn’t too hard to create or was it? This is what your code now should look like:

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

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

<script>

(function($){

$(document).ready(function(){

$(window).resize(function(){

$(".sensei-grid").each(function(){

equalise_articles($(this));

});

});

$(".sensei-grid").each(function(){

varblog=$(this);

equalise_articles($(this));

varobserver=newMutationObserver(function(mutations){

equalise_articles(blog);

});

varconfig={subtree:true,childList:true};

observer.observe(blog[0],config);

});

functionequalise_articles(blog){

vararticles=blog.find("article");

varheights=[];

articles.each(function(){

varheight=0;

//height += $(this).outerHeight() - $(this).height();

height+=$(this).find(".et_pb_image_container").outerHeight(true);

height+=$(this).find(".entry-title").outerHeight(true);

height+=$(this).find(".post-meta").outerHeight(true);

height+=$(this).find(".post-content").outerHeight(true);

heights.push(height);

});

varmax_height=Math.max.apply(Math,heights);

articles.each(function(){

$(this).height(max_height);

});

}

$(document).ajaxComplete(function(){

$(".sensei-grid").imagesLoaded().then(function(){

console.log("images loaded");

$(".sensei-grid").each(function(){

equalise_articles($(this));

});

});

});

$.fn.imagesLoaded=function(){

var$imgs=this.find('img[src!=""]');

if(!$imgs.length){return$.Deferred().resolve().promise();}

vardfds=[];

$imgs.each(function(){

vardfd=$.Deferred();

dfds.push(dfd);

varimg=newImage();

img.onload=function(){dfd.resolve();}

img.onerror=function(){dfd.resolve();}

img.src=this.src;

});

return$.when.apply($,dfds);

}

});

})(jQuery);

</script>

If this was your first time using jQuery and you have not understood everything we did here: no worries. The more you use JavaScript and jQuery, the more you will understand. But hey, maybe you are now interested in starting to learn JavaScript at all. If so, we recommend reading or watching a few tutorials on the web or on YouTube to get a better understanding of whats going on. We hope it was not too difficult to follow.

If you already have experience with jQuery, this should be easy enough to be understood well, or isn’t it? If so, let us know in the comments. We hope you enjoy this little tutorial. Let us know what you think and would you like to see little snippets like that in the WordPress Plugin Directory as free download?

3 Comments

Alanna on 24. May 2018 at 07:00

WOOOOHHHHOOOO!! This has beeen driving me crazy for so long. Thank you. Tried the CSS way with no luck but this way worked! Is there a way to hide the excerpt and meta, so its just the image and title?

Disclaimer

We use cookies to ensure that we give you the best experience by analysing usage and traffic on our page. If you want to learn more about how we use cookies or if you want to revoke the usage of analytic programs, please visit our privacy policy.Accept!