Feature Suggestion: Equal Height Columns #1358

Closed
kretchy opened this Issue Jan 6, 2013 · 18 comments

Comments

Projects
None yet
@kretchy

kretchy commented Jan 6, 2013

.block-grid is a cool div-feature in Foundation. One thing that would be nice is to incorporate an option to design "rows" of "cells" with equal heights, discussed over here for instance: http://css-tricks.com/fluid-width-equal-height-columns/

@jrmybtlr

This comment has been minimized.

Show comment
Hide comment
@jrmybtlr

jrmybtlr Jan 10, 2013

Would be very nice. My current workflow is giving those elements a min-height, which I then drop to 0px for small screens.

Would be very nice. My current workflow is giving those elements a min-height, which I then drop to 0px for small screens.

@ghost ghost assigned hatefulcrawdad Jan 11, 2013

@davilima6

This comment has been minimized.

Show comment
Hide comment

+1

@brianlarson

This comment has been minimized.

Show comment
Hide comment
@brianlarson

brianlarson Mar 28, 2013

Would love to see this too. Anyone know of the best workaround to do this with .panel? For instance, having a sidebar stretch below to match the main content height.

Would love to see this too. Anyone know of the best workaround to do this with .panel? For instance, having a sidebar stretch below to match the main content height.

@rainwiz

This comment has been minimized.

Show comment
Hide comment

rainwiz commented May 15, 2013

+1

@hatefulcrawdad

This comment has been minimized.

Show comment
Hide comment
@hatefulcrawdad

hatefulcrawdad May 15, 2013

Okay guys, we've been thinking about this for awhile. We hardly ever run into this as a main use-case for the grid in Foundation or for layouts we build. We definitely recognize that this is a useful feature and has its place on certain projects. We are going to say no on this for a core feature, but I didn't want to leave people hanging. Below is an example of what you can do to get that feature on your project.

Here's some example HTML of a grid snippet and a block-grid snippet. You'll notice a few data attributes, data-match-height and data-height-watch. These are used to scope the parent of the elements you want as the same height and to mark the children that will look to match heights.

<div class="row" data-match-height>
  <div data-height-watch class="small-3 columns" style="background: pink;">
    <p>Some text...</p>
  </div>
  <div data-height-watch class="small-6 columns" style="background: orange;">
    <p>Some text...</p>
    <img src="http://placehold.it/300x300">
  </div>
  <div data-height-watch class="small-3 columns" style="background: lightblue;">
    <p>Some text...</p>
  </div>
</div>

<br>

<div class="row" data-match-height>
  <div class="small-12">
    <ul class="small-block-grid-3">
      <li data-height-watch style="background: pink; height: 100px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: orange; height: 120px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: lightblue; height: 140px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: purple; height: 150px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: gray; height: 100px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: pink; height: 200px;">
        <p>Some text...</p>
      </li>
    </ul>
  </div>
</div>

From here, I created a some JS that will do the magic:

$("[data-match-height]").each(function() {

  var parentRow = $(this),
      childrenCols = $(this).find("[data-height-watch]"),
      childHeights = childrenCols.map(function(){ return $(this).height(); }).get(),
      tallestChild = Math.max.apply(Math, childHeights);

  childrenCols.css('min-height', tallestChild);

});

You can include this JS in app.js under the JS links at the bottom of your pages.

In the case where you need a sidebar to be as tall as the main content area, just use a background image that repeats down the .row that holds both.

Okay guys, we've been thinking about this for awhile. We hardly ever run into this as a main use-case for the grid in Foundation or for layouts we build. We definitely recognize that this is a useful feature and has its place on certain projects. We are going to say no on this for a core feature, but I didn't want to leave people hanging. Below is an example of what you can do to get that feature on your project.

Here's some example HTML of a grid snippet and a block-grid snippet. You'll notice a few data attributes, data-match-height and data-height-watch. These are used to scope the parent of the elements you want as the same height and to mark the children that will look to match heights.

<div class="row" data-match-height>
  <div data-height-watch class="small-3 columns" style="background: pink;">
    <p>Some text...</p>
  </div>
  <div data-height-watch class="small-6 columns" style="background: orange;">
    <p>Some text...</p>
    <img src="http://placehold.it/300x300">
  </div>
  <div data-height-watch class="small-3 columns" style="background: lightblue;">
    <p>Some text...</p>
  </div>
</div>

<br>

<div class="row" data-match-height>
  <div class="small-12">
    <ul class="small-block-grid-3">
      <li data-height-watch style="background: pink; height: 100px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: orange; height: 120px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: lightblue; height: 140px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: purple; height: 150px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: gray; height: 100px;">
        <p>Some text...</p>
      </li>
      <li data-height-watch style="background: pink; height: 200px;">
        <p>Some text...</p>
      </li>
    </ul>
  </div>
</div>

From here, I created a some JS that will do the magic:

$("[data-match-height]").each(function() {

  var parentRow = $(this),
      childrenCols = $(this).find("[data-height-watch]"),
      childHeights = childrenCols.map(function(){ return $(this).height(); }).get(),
      tallestChild = Math.max.apply(Math, childHeights);

  childrenCols.css('min-height', tallestChild);

});

You can include this JS in app.js under the JS links at the bottom of your pages.

In the case where you need a sidebar to be as tall as the main content area, just use a background image that repeats down the .row that holds both.

@fatlinesofcode

This comment has been minimized.

Show comment
Hide comment
@fatlinesofcode

fatlinesofcode May 20, 2013

If you want to always have square thumbnails add a square transparent png and then add the real image as background-image.

<ul class="small-block-grid-4 large-block-grid-5">
  <li>
          <div style="background-image: url('myimage.jpg')">
          <img src="1x1.png"/>
          </div>
  </li>
</ul>

li{
    div{
      background: no-repeat top center;
      background-size: cover;
    }
    img{
      min-width: 100%;
    }
  }

If you want to always have square thumbnails add a square transparent png and then add the real image as background-image.

<ul class="small-block-grid-4 large-block-grid-5">
  <li>
          <div style="background-image: url('myimage.jpg')">
          <img src="1x1.png"/>
          </div>
  </li>
</ul>

li{
    div{
      background: no-repeat top center;
      background-size: cover;
    }
    img{
      min-width: 100%;
    }
  }
@mhayes

This comment has been minimized.

Show comment
Hide comment
@mhayes

mhayes Jul 23, 2013

Contributor

If someone is still looking for a way to accomplish equal-height panels, you can add the following JS snippet to your page:

$('.row[data-match-height]').each(function(){
  var self = $(this);
  self.find('.panel').height(self.height());
  $(window).on('resize', function(event) {
    var row_width = self.width();
    var panels = self.find('.panel');
    // Reset height of the panels
    panels.height('');
    var h = self.height();
    panels.each(function() {
      var panel = $(this);
      if ((row_width - panel.width()) > 40) {
        panel.height(h);
      }
    });
  });
});

To use it, just create a <div> container with a data-matching-height attribute, like so:

<div class="row" data-match-height>
  <div class="large-4 columns">
    <div class="panel">
      <h4>Panel 1</h4>
    </div>
  </div>
  <div class="large-4 columns">
    <div class="panel">
      <h4>Panel 1</h4>
    </div>
  </div>
  <div class="large-4 columns">
    <div class="panel">
      <h4>Panel 1</h4>
    </div>
  </div>
</div>

This will ensure that panels are equal height until they are viewed on a mobile device, at which point they will resort to their natural height. Hope that helps, happy coding!

Contributor

mhayes commented Jul 23, 2013

If someone is still looking for a way to accomplish equal-height panels, you can add the following JS snippet to your page:

$('.row[data-match-height]').each(function(){
  var self = $(this);
  self.find('.panel').height(self.height());
  $(window).on('resize', function(event) {
    var row_width = self.width();
    var panels = self.find('.panel');
    // Reset height of the panels
    panels.height('');
    var h = self.height();
    panels.each(function() {
      var panel = $(this);
      if ((row_width - panel.width()) > 40) {
        panel.height(h);
      }
    });
  });
});

To use it, just create a <div> container with a data-matching-height attribute, like so:

<div class="row" data-match-height>
  <div class="large-4 columns">
    <div class="panel">
      <h4>Panel 1</h4>
    </div>
  </div>
  <div class="large-4 columns">
    <div class="panel">
      <h4>Panel 1</h4>
    </div>
  </div>
  <div class="large-4 columns">
    <div class="panel">
      <h4>Panel 1</h4>
    </div>
  </div>
</div>

This will ensure that panels are equal height until they are viewed on a mobile device, at which point they will resort to their natural height. Hope that helps, happy coding!

@jaffe75

This comment has been minimized.

Show comment
Hide comment
@jaffe75

jaffe75 Aug 24, 2013

Would love to see this work for Tabs as well. Any thoughts?

jaffe75 commented Aug 24, 2013

Would love to see this work for Tabs as well. Any thoughts?

@kalisurfer

This comment has been minimized.

Show comment
Hide comment
@kalisurfer

kalisurfer Sep 3, 2013

After implementing the above feature, my panels get really really long (as in heights). Please note that the loop generates around 84 items.

Here is the code

<% for (var i=0; i < data.length; i++){ %>
  <% if (i==0) { %>
    <div class="large-6 small-12 columns">
  <% } else { %>
    <div class="large-3 small-6 columns">
  <% } %>



      <a href="/details/<%= data[i]._id %>"><img src="<%= data[i].get('thumbUrl') %>" /></a>
      <div class="panel">
        <div class="row">
          <div class="small-2 columns gametitle"><h5><%= i+1 %></h5></div>
          <div class="small-10 columns gametitle"><h6><%= data[i].title %></h6></div>
        </div>
         <div class="row">
          <div class="large-12 columns gametitle"><%= data[i].get('trendIndex') %></div>
        </div>
      </div> 
    </div>
  <% } %>
</div>

After implementing the above feature, my panels get really really long (as in heights). Please note that the loop generates around 84 items.

Here is the code

<% for (var i=0; i < data.length; i++){ %>
  <% if (i==0) { %>
    <div class="large-6 small-12 columns">
  <% } else { %>
    <div class="large-3 small-6 columns">
  <% } %>



      <a href="/details/<%= data[i]._id %>"><img src="<%= data[i].get('thumbUrl') %>" /></a>
      <div class="panel">
        <div class="row">
          <div class="small-2 columns gametitle"><h5><%= i+1 %></h5></div>
          <div class="small-10 columns gametitle"><h6><%= data[i].title %></h6></div>
        </div>
         <div class="row">
          <div class="large-12 columns gametitle"><%= data[i].get('trendIndex') %></div>
        </div>
      </div> 
    </div>
  <% } %>
</div>
@jmarston4

This comment has been minimized.

Show comment
Hide comment
@jmarston4

jmarston4 Sep 5, 2013

@hatefulcrawdad - can you share why you don't see this is a main use case? It seems having for portfolio type situations (image in one half of a row, text description in the other) would arise quite a bit. I am not sure I agree a JS solution is "right" for those who still need to support (dummies) with JS turned off. Dummies or not, in some corporate spaces we have to support them or lose 5-10% of customers.

@hatefulcrawdad - can you share why you don't see this is a main use case? It seems having for portfolio type situations (image in one half of a row, text description in the other) would arise quite a bit. I am not sure I agree a JS solution is "right" for those who still need to support (dummies) with JS turned off. Dummies or not, in some corporate spaces we have to support them or lose 5-10% of customers.

@KarlEyeWeb

This comment has been minimized.

Show comment
Hide comment
@KarlEyeWeb

KarlEyeWeb Nov 1, 2013

@hatefulcrawdad in case anyone else has this issue with the Javascript provided.

If you want to get the height of a div which just contains an image you'll need to wrap the function inside of a $(window).load() as this will then only trigger once the whole page has loaded, including images. Whereas $(document).ready() only checks that the DOM elements have been created before triggering, therefore giving inaccurate results.

If you want to include padding too you can change $(this).height(); to $(this).outerHeight();

This is what I use for more accurate results:

    $(window).load(function() {
        $("[data-match-height]").each(function() {
            var parentRow = $(this),
                childrenCols = $(this).find("[data-height-watch]"),
                childHeights = childrenCols.map(function(){ return $(this).outerHeight(); }).get(),
                tallestChild = Math.max.apply(Math, childHeights);
            childrenCols.css('min-height', tallestChild);
        });
    });

@hatefulcrawdad in case anyone else has this issue with the Javascript provided.

If you want to get the height of a div which just contains an image you'll need to wrap the function inside of a $(window).load() as this will then only trigger once the whole page has loaded, including images. Whereas $(document).ready() only checks that the DOM elements have been created before triggering, therefore giving inaccurate results.

If you want to include padding too you can change $(this).height(); to $(this).outerHeight();

This is what I use for more accurate results:

    $(window).load(function() {
        $("[data-match-height]").each(function() {
            var parentRow = $(this),
                childrenCols = $(this).find("[data-height-watch]"),
                childHeights = childrenCols.map(function(){ return $(this).outerHeight(); }).get(),
                tallestChild = Math.max.apply(Math, childHeights);
            childrenCols.css('min-height', tallestChild);
        });
    });
@gormus

This comment has been minimized.

Show comment
Hide comment
@gormus

gormus Nov 15, 2013

Contributor

@KarlEyeWeb, may I suggest a little tweak to your code. Here is how I changed to make it work with nested grids.

I only changed the childrenCols selector to find immediate children:

$(window).load(function() {
  $("[data-match-height]").each(function() {
    var parentRow = $(this),
      childrenCols = $("> [data-height-watch]", this),
      childHeights = childrenCols.map(function(){ return $(this).outerHeight(); }).get(),
      tallestChild = Math.max.apply(Math, childHeights);
    childrenCols.css('min-height', tallestChild);
  });
});
Contributor

gormus commented Nov 15, 2013

@KarlEyeWeb, may I suggest a little tweak to your code. Here is how I changed to make it work with nested grids.

I only changed the childrenCols selector to find immediate children:

$(window).load(function() {
  $("[data-match-height]").each(function() {
    var parentRow = $(this),
      childrenCols = $("> [data-height-watch]", this),
      childHeights = childrenCols.map(function(){ return $(this).outerHeight(); }).get(),
      tallestChild = Math.max.apply(Math, childHeights);
    childrenCols.css('min-height', tallestChild);
  });
});
@gormus

This comment has been minimized.

Show comment
Hide comment
@gormus

gormus Nov 15, 2013

Contributor

If you'd like to have vertical lines between each column, except the one displayed on the edge, here is my "work-in-progress" solution.

[data-height-watch] {
  border-left: 1px solid gray;
}
[data-match-height] > [data-height-watch][data-height-watch-novrule] {
  border-left: 0 none;
}

@media only screen and (max-width:1023px) {
  [data-height-watch] {
    min-height: 1px !important;
    border-left: 0 none !important;
  }
}

I add [data-height-watch-novrule] attribute to the columns where I don't want any vertical lines:

<div class="row" data-match-height>
  <div class="medium-12 large-9 large-push-3 columns" data-height-watch>
    Main column on the right
  </div>
  <div class="large-3 large-pull-9 columns" data-height-watch data-height-watch-novrule>
    Sidebar column on the left
  </div>
</div>
Contributor

gormus commented Nov 15, 2013

If you'd like to have vertical lines between each column, except the one displayed on the edge, here is my "work-in-progress" solution.

[data-height-watch] {
  border-left: 1px solid gray;
}
[data-match-height] > [data-height-watch][data-height-watch-novrule] {
  border-left: 0 none;
}

@media only screen and (max-width:1023px) {
  [data-height-watch] {
    min-height: 1px !important;
    border-left: 0 none !important;
  }
}

I add [data-height-watch-novrule] attribute to the columns where I don't want any vertical lines:

<div class="row" data-match-height>
  <div class="medium-12 large-9 large-push-3 columns" data-height-watch>
    Main column on the right
  </div>
  <div class="large-3 large-pull-9 columns" data-height-watch data-height-watch-novrule>
    Sidebar column on the left
  </div>
</div>
@shadysamir

This comment has been minimized.

Show comment
Hide comment
@shadysamir

shadysamir Dec 29, 2013

The code above by @hatefulcrawdad is what I needed. Except that it doesnt count for box-sizing model used by foundation (and all modern frameworks). While JQuery height will return the actual height regardless of margins and padding this is not what we need in this case. This is why the value from css("height") is what we need to set as min-height. My code below applies equal heights to ALL block grids, something I think should be done by foundation as default. If you wish to customize that, change the selectors.

TODO: Disable script if large only is used and screen is small.

$("ul[class *= 'block-grid']").each(function() {
  var parentRow = $(this);
  childrenCols = $(this).find("li");
  childHeights = childrenCols.map(function() {
    return $(this).height();
  }).get();
  childCssHeights = childrenCols.map(function() {
    return $(this).css('height');
  }).get();
  tallestChild = Math.max.apply(Math, childHeights);
  cssIndex = childHeights.indexOf(tallestChild);
  childrenCols.css('min-height', childCssHeights[cssIndex]);
});

The code above by @hatefulcrawdad is what I needed. Except that it doesnt count for box-sizing model used by foundation (and all modern frameworks). While JQuery height will return the actual height regardless of margins and padding this is not what we need in this case. This is why the value from css("height") is what we need to set as min-height. My code below applies equal heights to ALL block grids, something I think should be done by foundation as default. If you wish to customize that, change the selectors.

TODO: Disable script if large only is used and screen is small.

$("ul[class *= 'block-grid']").each(function() {
  var parentRow = $(this);
  childrenCols = $(this).find("li");
  childHeights = childrenCols.map(function() {
    return $(this).height();
  }).get();
  childCssHeights = childrenCols.map(function() {
    return $(this).css('height');
  }).get();
  tallestChild = Math.max.apply(Math, childHeights);
  cssIndex = childHeights.indexOf(tallestChild);
  childrenCols.css('min-height', childCssHeights[cssIndex]);
});
@rafibomb

This comment has been minimized.

Show comment
Hide comment
@rafibomb

rafibomb Jan 17, 2014

Contributor

This will be part of 5.1. We'll close this in favor of #3950

Contributor

rafibomb commented Jan 17, 2014

This will be part of 5.1. We'll close this in favor of #3950

@snappercridge

This comment has been minimized.

Show comment
Hide comment
@snappercridge

snappercridge Feb 5, 2014

I'm hoping the issue in 5.1 will address a substantial need in this function to not just measure the height of all the element, but also depending on their row. In other words what if the second row of content block's tallest element is different from row to row. This addition should include that adjustment as outlined here: http://css-tricks.com/examples/EqualHeightsInRows/

I'm hoping the issue in 5.1 will address a substantial need in this function to not just measure the height of all the element, but also depending on their row. In other words what if the second row of content block's tallest element is different from row to row. This addition should include that adjustment as outlined here: http://css-tricks.com/examples/EqualHeightsInRows/

@joshbedo

This comment has been minimized.

Show comment
Hide comment
@joshbedo

joshbedo Jun 26, 2014

If you guys want a non-hacky/non javascript solution you can use pseudo selectors like shown here
https://webdesign.tutsplus.com/tutorials/quick-tip-solving-the-equal-height-column-conundrum--cms-20403

All the JavaScript solutions i've seen for this are entirely overkill and you should not listen to the window resize event to calculate your layout.

If you guys want a non-hacky/non javascript solution you can use pseudo selectors like shown here
https://webdesign.tutsplus.com/tutorials/quick-tip-solving-the-equal-height-column-conundrum--cms-20403

All the JavaScript solutions i've seen for this are entirely overkill and you should not listen to the window resize event to calculate your layout.

@brianlarson

This comment has been minimized.

Show comment
Hide comment
@brianlarson

brianlarson Jun 30, 2014

I believe we're all set now with Foundation's new Equalizer component: http://foundation.zurb.com/docs/components/equalizer.html

I believe we're all set now with Foundation's new Equalizer component: http://foundation.zurb.com/docs/components/equalizer.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment