Building a Filtering Component with CSS Animations & jQuery

George Martsoukos
Share

Some months ago, I wrote an article about MixItUp, a popular jQuery plugin for filtering and sorting. In today’s article, I’ll show you how to build your own simple filterable component with jQuery and CSS animations.

Without further ado, let’s get started!

Setting Up the HTML

As a first step, I’ll show you the HTML structure of the component. Consider the following markup:

<div class="cta filter">
  <a class="all active" data-filter="all" href="#" role="button">Show All</a>
  <a class="green" data-filter="green" href="#" role="button">Show Green Boxes</a>
  <a class="blue" data-filter="blue" href="#" role="button">Show Blue Boxes</a>
  <a class="red" data-filter="red" href="#" role="button">Show Red Boxes</a>
</div>

<div class="boxes">
  <a class="red" data-category="red" href="#">Box1</a>
  <a class="green" data-category="green" href="#">Box2</a>
  <a class="blue" data-category="blue" href="#">Box3</a>

 <!-- other anchor/boxes here ... -->

</div>

Notice that I’ve set up some pretty basic markup. Here’s an explanation of it:

  • First, I’ve defined the filter buttons and the elements that I want to filter (we’ll call them target elements).
  • Next, I’ve grouped the target elements into three categories (blue, green, and red) and I gave them the data-category attribute. The value of this attribute determines the category that each element belongs to.
  • I’ve also assigned the data-filter attribute to the filter buttons. The value of this attribute specifies the desired filter category. For instance, the button with the data-filter="red" attribute/value will only show the elements that belong to the red category. On the other hand, the button with data-filter="all" will show all the elements.

Now that you’ve had an overview of the required HTML, we can move on to explore the CSS.

Setting Up the CSS

Each time a filter category is active, its corresponding filter button receives the active class. By default, the button with the data-filter="all" attribute gets this class.

Box with active class

Here are the associated styles:

.filter a {
  position: relative;
}

.filter a.active:before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  display: inline-block;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 15px 15px 0 0;
  border-color: #333 transparent transparent transparent;
}

In addition, I’m going to use flexbox to create the layout for the target elements.

Using flexbox for the layout

See the related styles below:

.boxes {
  display: flex;
  flex-wrap: wrap;
}

.boxes a {
  width: 23%;
  border: 2px solid #333;
  margin: 0 1% 20px 1%;
  line-height: 60px;
}

Lastly, I’m defining two different CSS keyframe animations that I’ll use later on to reveal the elements:

@keyframes zoom-in {
  0% {
   transform: scale(.1);
  }
  100% {
    transform: none;
  }
}

@keyframes rotate-right {
  0% {
    transform: translate(-100%) rotate(-100deg);
  }
  100% {
    transform: none;
  }
}

.is-animated {
  animation: .6s zoom-in;
  // animation: .6s rotate-right; 
}

With the markup and CSS in place, we can start building the JavaScript/jQuery.

Setting Up the jQuery

Have a look at the code below, after which I’ll explain what’s happening:

var $filters = $('.filter [data-filter]'),
    $boxes = $('.boxes [data-category]');

$filters.on('click', function(e) {
  e.preventDefault();
  var $this = $(this);
  $filters.removeClass('active');
  $this.addClass('active');

  var $filterColor = $this.attr('data-filter');

  if ($filterColor == 'all') {
    $boxes.removeClass('is-animated')
      .fadeOut().promise().done(function() {
        $boxes.addClass('is-animated').fadeIn();
      });
  } else {
    $boxes.removeClass('is-animated')
      .fadeOut().promise().done(function() {
        $boxes.filter('[data-category = "' + $filterColor + '"]')
          .addClass('is-animated').fadeIn();
      });
  }
});

Each time a filter button is clicked, the following happens:

  • The active class is removed from all buttons and assigned only to the selected button.
  • The value of the button’s data-filter attribute is retrieved and evaluated.
  • If the value of data-filter is all, all elements should appear. To do so, I first hide them and then, when all elements become hidden, I show them using the rotate-right or zoom-in CSS animations.
  • If the value is not all, the target elements of the corresponding category should appear. To do so, I first hide all elements and then, when all of them become hidden, I show only the elements of the associated category using the rotate-right or zoom-in CSS animations.

At this point, it’s important to clarify one thing. Notice the syntax for the fadeOut() method in the above code. It looks as follows:

$boxes.fadeOut().promise().done(function() {
  // callback's body
});

You’re probably more familiar with this syntax though:

$boxes.fadeOut(function() {
  // callback's body
});

These declarations have different meanings:

  • In the first case, the callback is executed only after all target elements become hidden. You can learn more info about this approach by visiting the promise() section of jQuery’s docs.
  • In the second case, the callback is fired multiple times. Specifically, it’s executed each time an element becomes hidden.

The demo below uses the zoom-in animation:

See the Pen A sorting/filtering component with CSS and jQuery (with zoom animation) by SitePoint (@SitePoint) on CodePen.

And this demo uses the rotate-right animation:

See the Pen A sorting/filtering component with CSS and jQuery (with rotate animation) by SitePoint (@SitePoint) on CodePen.

Of course, the aforementioned CSS animations are optional. If you don’t like these specific animations, feel free to remove them and reveal the elements using only jQuery’s fadeIn() method.

Now that you understand how the component works, I’ll show you how to create a different variation of it.

Animating Elements Sequentially

Until now, you may have noticed that all elements appear at the same time. I’ll now modify the code to show them sequentially:

$filters.on('click', function(e) {

  // same code as above here

  if ($filterColor == 'all') {
    $boxes.removeClass('is-animated')
      .fadeOut().finish().promise().done(function() {
        $boxes.each(function(i) {
          $(this).addClass('is-animated').delay((i++) * 200).fadeIn();
        });
      });
  } else {
    $boxes.removeClass('is-animated')
      .fadeOut().finish().promise().done(function() {
        $boxes.filter('[data-category = "' + $filterColor + '"]').each(function(i) {
          $(this).addClass('is-animated').delay((i++) * 200).fadeIn();
        });
      });
  }
});

The code above looks similar to the previous one but there are a few distinct differences:

  • First, I use the each() method to iterate through the target elements. Plus, as it loops, I’m getting the index of the current element (which is zero-based) and multiplying it by a number (e.g. 200). The derived number is passed as an argument to the delay method. This number indicates the amount of milliseconds that each element should wait before fading in.
  • Next, I use the finish() method to stop the currently-running animations for the selected elements under specific cases. To understand its usage, here’s a scenario: Click on a filter button and then, before all elements appear, click on the button again. You’ll notice that all elements disappear. Similarly, run this test again after removing the two instances of this method. In such a case, you’ll see that the elements receive some undesired effects. Sometimes calling this method properly can be tricky. For this example, I had to experiment a bit until I found where I should place it.

The demo below animates the filtered elements sequentially using the zoom-in animation:

See the Pen Sequential filtering/sorting component with CSS & jQuery by SitePoint (@SitePoint) on CodePen.

The demo below animates the filtered elements sequentially using the rotate-right animation:

See the Pen Sequential filtering/sorting with CSS and jQuery by SitePoint (@SitePoint) on CodePen.

Conclusion

This same component could be built without jQuery and may have better performance, but the ability to use jQuery’s fadeIn() and fadeOut() methods allows for simpler code that takes advantage of certain features available to jQuery.

Let me know in the comments if you have a different solution, or a way to improve the code.