Sunday, December 8, 2013

Working with CSS-Only Height Transitions and Content Styling

For some reason working with height in HTML is a challenge. DIVs conveniently fill their parent's width, but height is a different story. Adding a transition to the height becomes even more challenging and has caused me some recent grief. Even after finding a solution, I was still not completely happy with the result and spent some additional time finding a reasonable approach to make the content look correct in both the collapsed and expanded states.

As part of the solution, I'd like to limit the amount of Javascript to only what is necessary to toggle classes on the element that should be expanded/collapsed. Everything else will be defined via CSS to create the animation and style the content:

   $( '.expandable .toggle' ).click(function( e ) {

      var $target = $(this),
          $exp = $target.closest( '.expandable' );

      $target.toggleClass( 'dropup' );
      $exp.toggleClass( 'in' );

      return false;
   });


The code assumes there is a base class expandable assigned to element(s) that should change size and, contained within those elements, is some kind of toggle element to allow the user to switch between the expanded and collapsed views. Now we need to figure out what styles need to be defined to make it work.

What doesn't work

Trying to transition between an explicit height and auto will not work:

.expandable-bad {
   height: 48px;
   overflow: hidden;
   transition: height 0.55s ease-in-out 0s;
}

.expandable-bad.in {
   height: auto;
}


Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.


While the boxes do expand/collapse, there is no smooth transition between those states. Percentages only work inside a parent with a fixed height. However, I may not know how tall the content will be fully expanded and don't want to assume one height. I could calculate the height on each box, but if I'm going to stick to my goal of not using Javascript to calculate anything, I'll have to find an alternative CSS-only solution.

What does work

Instead of height, we can use max-height to solve our problem:

.expandable {
   max-height: 48px;
   overflow: hidden;
   transition: max-height 0.55s ease-in-out 0s;
}

.expandable.in {
   max-height: 1000px;
}




This mimics auto height because, if the height is not needed, the container will not be sized to the specified height. It will only be constrained once it reaches the defined maximum. Fortunately, this style can also be animated with a CSS transition so this looks like a viable solution as long as you don't exceed the defined maximum. The only thing you do need to know with this approach is how tall the content should start. If you're trying to fit a certain number of lines of text, you'll have to do some work to find the correct value.

Better clipping

Notice how the text doesn't hide until it reaches the border of the container? It would be nice if it would clip with the matching 15px top padding while in the collapsed state. The best way I found to achieve this is to use the :after pseudo-element to add and style content to cover the bottom text. As an extra visual cue, I added an ellipsis to the clipping box to signify there is more content in the box. However, once I do that, I need it to hide when the content is expanded so I'll add a transition to match the one defined in the expandable class:


/* add to existing expandable */
.expandable {
   position: relative;
   ...
}

.more:after {
   background-color: #FFFFFF;
   bottom: 0;
   content: "...";
   display: block;
   height: 15px;
   padding-bottom: 10px;
   position: absolute;
   width: 100%;
   transition: bottom 0.55s ease-in-out 0s;
}

.more.in:after {
   bottom: -25px;
}


This isn't a necessary addition to the height transition. But I think it makes the collapsed content look better:



I've been trying to use transitions whenever possible. Sometimes you have to be a little creative to make them work, but overall, they can reduce the amount of code required to create the effect.