いまどきのWebサイトでよく見かける、タイルやカードを使ったレイアウト。ボタンをクリックするとアニメーションしながらタイルやカードの絞り込みができるコードの紹介です。専用のjQueryプラグインもありますが、CSS3とjQueryでも簡単に作れるようです。
先日、絞り込みと並び替え(フィルタリングとソート)表示によく使われるjQueryプラグイン、MixItUpの記事を書きました。今回は、jQueryとCSSアニメーションを使って、シンプルに絞り込みができるコンポーネントを構築する方法について説明します。
それではさっそく始めましょう!
HTMLの設定
最初にHTMLの設定です。次のコードを見てください。
<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>
わりと基本的なHTMLです。簡単に説明すると、次のようになります。
- 最初に絞り込みボタンと絞り込みたい要素(“ターゲット要素”と呼びます)を定義しました。
- 次に、ターゲット要素を、blue(青)、 green(緑)、red(赤)の3つのカテゴリーに分け、属性をdata-categoryとしました。各要素がどのカテゴリーに属するかを決定する値となります。
- 最後に、絞り込みボタンにdata-filter属性を指定しました。この属性の値は、絞り込みたいカテゴリーを決定します。例えば、data-filter="red"のボタンは、redのカテゴリーに属する要素のみを表示します。また、data-filter="all"のボタンは、すべての要素を表示します。
ベースとなるHTMLのおさらいをしたところで、CSSの設定へ移りましょう。
CSSの設定
絞り込むカテゴリーがアクティブになるたび、絞り込みボタンにactiveクラスが返されます。デフォルトのクラスは、data-filter="all"です。
ここに関連するスタイルをいくつかについて、下のコードを参照してください。
.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;
}
続いて、Flexboxでターゲット要素のレイアウトをします。
下記のスタイルを見てください。
.boxes {
display: flex;
flex-wrap: wrap;
}
.boxes a {
width: 23%;
border: 2px solid #333;
margin: 0 1% 20px 1%;
line-height: 60px;
}
最後に、要素の表示に使う、2種類のCSSキーフレームアニメーションの定義をしておきます。
@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;
}
HTMLとCSSを書き終わったら、JavaScript/jQueryのコーディングします。
jQueryの設定
詳細を説明する前に、下のコードを見てみてください。
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();
});
}
});
絞り込みボタンがクリックされるたびに、以下のことが起こります。
- すべてのボタンからactiveクラスが外れ、選択したボタンのみに適用されます。
- ボタンのdata-filter属性の値が返されます。
- data-filterの値がallの場合、すべての要素が表示されます。そのためには、最初にすべての要素を一度非表示にしたあと、CSSアニメーションのrotate-rightまたはzoom-inを使って表示します。
- もし値がallでない場合は、各カテゴリーのターゲット要素が表示されます。そのためには、最初にすべての要素を一度非表示にしたあと、返された値のターゲット要素のみをCSSアニメーションのrotate-rightまたはzoom-inを使って表示します。
このとき、fadeOut()の構文に注目してください。次のようになっています。
$boxes.fadeOut().promise().done(function() {
// callback's body
});
しかし、次のような書き方のほうが、なじみがあるのではないでしょうか。
$boxes.fadeOut(function() {
// callback's body
});
これら2つの設定には、それぞれ異なる意味があります。
- 最初のコードは、ターゲット要素が非表示になったあとにのみ、data-filter属性の値が返されます。jQueryドキュメントのpromise()で詳しく説明されていますので、参照してみてください。
- 次のコードは、data-filter属性の値を何度返そうとしてもキャンセルされています。正確に言うと、それぞれの要素が非表示になるたび、data-filter属性の値はキャンセルされています。
zoom-inアニメーションを使った例:
rotate-rightアニメーションを使った例:
もちろん、CSSアニメーションはオプションです。例のようなアニメーションが不要な場合は、アニメーションを省いてjQueryのfadeIn()だけで表示しても大丈夫です。
では、コンポーネントの仕組みが分かったところで、応用編へ進みましょう。
要素の順次アニメ化
ここまで、要素の表示タイミングがすべて同時です。要素が順番に連続して表示されるように、コードを書き換えてみましょう。
$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();
});
});
}
});
上記のコードは前のコードと似ていますが、以下のように、明らかに違う点があります:
- 最初に、each()を使ってターゲット要素を反復させています。そして、ループ中、現在の要素(ゼロベース)のインデックスを受け取り、例では200という数字を掛けています。“200”は、delayの引数として渡され、要素が表示されるまでに待機すべき時間(ミリ秒)を示します。
- 次に、選択された要素に対して現在実行中のアニメーションがいつ停止すべきか、finish()を使って設定しています。使い方を理解できるように、テストしてみましょう:絞り込みボタンをクリックしてすべての要素が表示される前に、再度ボタンをクリックすると、すべての要素が非表示になって、また表示しようとします。先ほどの要素の表示のタイミングが同じ状態でテストをすると、エフェクトがかからない状態で表示されます。finish()を正確に実行するのはときどきトリッキーなことしなければなりません(筆者もこの例を作るときには何度も実験しなければなりませんでした)。
zoom-inアニメーションを使って絞り込んだ要素を順次表示させた例:
rotate-rightアニメーションを使って絞り込んだ要素を順次表示させた例:
まとめ
今回のコンポーネントは、jQueryを使わなくても構築できます(しかも、パフォーマンスがよいかもしれません)。しかし、jQueryのfadeIn()、fadeOut()を使いこなすことで、jQueryの機能を利用したシンプルなコーディングができるようになります。
(原文:Building a Filtering Component with CSS Animations & jQuery)
[翻訳:Eri Noda]
[編集:Livit]