Blog (my web musings)

Find out what's interesting me, get tips, advice, and code gems that don't fit elsewhere.

Search Blog

Let visitors select categories from your portfolio / film collection / club page, etc., and view them in a collapsed group, with this simple JS and CSS3 filter.

Where you might use a filtering script

  • to filter your portfolio (web design, graphics, print, media production, etc.)
  • to filter you film collection by genre (horror, comedy, romance, western, etc.)
  • to filter youth clubs by age or gender
  • to filter study periods by subject (or free time)

CSS-Only Filter

You can create a reasonably usable filter using CSS-only (for modern browsers and IE9+), with a combination of the :not selector and the checkbox hack, but the 2 methods shown in the demo below are difficult to refine without the help of JavaScript.

CSS-Only Content Filter (checkbox hack):


The HTML markup for both demos in the CSS-only page begin with an input, and a label, for each filter category. Note that the input id matches the "for" attribute in the label;

<input type="radio" id="blue" class="filter-input" name="filter" /><label for="blue" class="filter-label" onclick>Blue</label>

Then, filter-items are marked-up like this - with a class for each filterable category;

<div class="filter-item blue"></div>

On the CSS side, filtering is achieved with the checkbox hack. Simplified CSS follows...

Demo 1 uses opacity:0.1;, coupled with a CSS transition, to animate a fade on unselected filter-items;

#blue:checked ~ :not(.blue) { opacity:0.1 }

In English, this line means "When the #blue radio input is checked, fade-out all the filter-items that DO NOT have a .blue class".

Demo 2 uses display:none; to remove unselected filter-items from the flow of the document;

#blue:checked ~ :not(.blue) { display:none }

Almost the same as demo 1, but this line means "When the #blue radio input is checked, hide all the filter-items that DO NOT have a .blue class".

Both demonstrations work well enough, but each has pros and cons.

Demo 1 - PRO: The fade animation looks nice.
Demo 1 - CON: Items remain in the document flow, so it would be difficult to view the results on a long, heavily populated page.

Demo 2 - PRO: Items collapse together and are removed from the document flow, so filtered groups are easy to view.
Demo 2 - CON: No animation, so the visual effect is jarring.

Now, the really annoying thing is that both methods CANNOT be combined because it is not possible to animate display properties :( So, this is where we turn to JavaScript. And, while we're at it, we can give IE8 support with JS too.

JavaScript Filter

So, I had a long hard think about how I could collapse selected filter-items, AND animate them, and I came up with this possible solution, where the fade animation isn't actually performed on the filter-items at all; I will fade-in a mask over the top of filter-items, and when the mask is at the peak of its opacity (i.e. totally solid) perform the display:none; switch. When the mask fades away, only the collapsed, filtered items are visible.

JavaScript & CSS3 Content Filter:


Looks pretty good! And unlike the CSS-only version, we're not using the checkbox hack, or :not selector, so it's compatible with IE8... albeit without the fade animation... and a half second delay. IE9 suffers these limitations too, but let's not split hairs.

The filter-mask is just another div placed after the filter-items;

<div id="filter-mask"></div>

The CSS for the filter-mask fade-in-out animation looks like this;

.filter-mask { position:absolute; top:0; left:0; bottom:0; right:0; -webkit-animation:filterMask 1s ease-in-out both; animation:filterMask 1s ease-in-out both }
@-webkit-keyframes filterMask {
    0% { background:transparent }
    50% { background:#fff }
    100% { background:transparent }
@keyframes filterMask {
    0% { background:transparent }
    50% { background:#fff }
    100% { background:transparent }

Note that the CSS is applied to a "filter-mask" class, while the markup only has a "filter-mask" id. The class will be applied to the div element with JavaScript;

function filterMask(){ 
    var mask = document.getElementById('filter-mask'); // the div with 'filter-mask' id
    mask.className = 'filter-mask'; // apply 'filter-mask' class
    setTimeout(function(){ mask.className = '' }, 1000); // remove 'filter-mask' class after waiting 1 second

When activated - at the click of an input radio - the filterMask() function above applies the "filter-mask" class immediately, then waits for 1 second, and removes it. While the class is applied, the CSS fade-in-out "filterMask" animation takes effect.

Timing is crucial because midway through the CSS "filterMask" animation, JavaScript will hide all the filter-items with this filterHide() function;

function filterHide(el){ 
    for (var i = 0; i < el.length; ++i){
       el[i].style.display = 'none'; // hide

And then show the selected filter-items with this filterShow() function;

function filterShow(el){ 
    for (var i = 0; i < el.length; ++i){
       el[i].style.display = 'block'; // show

Here's a simplified version of the script so you can see how it ties together;

var filterItems = document.querySelectorAll('.filter-item'); // all filter-items
var btnBlue = document.getElementById('blue'); // input radio with 'blue' id
var getBlue = document.querySelectorAll('.blue' ); // filter-items with 'blue' class

btnBlue.onclick = function(){ // when input radio with 'blue' id is clicked
    filterMask(); // apply 'filter-mask' class and run 1 second CSS "filterMask" animation
       filterHide(filterItems); // hide all filter-items
       filterShow(getBlue) }, // show filter-items with 'blue' class
    500); // wait half a second before filtering selected 'blue' items

You could setup variables and onclick functions manually for all categories, but it could get a bit messy, so, in the final demo, I have refined the script so that they're generated dynamically from filter categories listed in an array.

Oh, before I forget, you can apply multiple category classes to filter-items, so that covers you for those items falling in to more than one group.


I've worked a bit more on the script so that the array of filter categories is no longer needed. Hiding and showing filtered items is now done through the manipulation of CSS classes rather than changing the style.display property directly with JS.

Get the complete CSS, HTML markup and JavaScript from the source of the demo page: