HMTL
<section class="gallery">
<div class="container">
<input type="radio" id="All" name="categories" value="All" checked>
<input type="radio" id="cat-a" name="categories" value="cat-a">
<input type="radio" id="cat-b" name="categories" value="cat-b">
<input type="radio" id="cat-c" name="categories" value="cat-c">
<input type="radio" id="cat-d" name="categories" value="cat-d">
<input type="radio" id="cat-e" name="categories" value="cat-e">
<input type="radio" id="cat-f" name="categories" value="cat-f">
<ol class="filters">
<li><label for="All">All</label></li>
<li><label for="cat-a">カテゴリA</label></li>
<li><label for="cat-b">カテゴリB</label></li>
<li><label for="cat-c">カテゴリC</label></li>
<li><label for="cat-d">カテゴリD</label></li>
<li><label for="cat-e">カテゴリE</label></li>
<li><label for="cat-f">カテゴリF</label></li>
</ol>
<ol class="targets">
<li class="target" data-category="cat-a cat-b">
<figure> <a href="#"> <img src="images/001.jpg" alt=""> </a>
<h2 class="target-title"> <a href="#">Title 1</a> </h2>
<ol class="target-categories">
<li> <a href="#">カテゴリA</a> </li>
<li> <a href="#">カテゴリB</a> </li>
</ol>
<figcaption>キャプション1</figcaption>
</figure>
</li>
<li class="target" data-category="cat-a cat-c">
<figure> <a href="#"> <img src="images/002.jpg" alt=""> </a>
<h2 class="target-title"> <a href="#">Title 2</a> </h2>
<ol class="target-categories">
<li> <a href="#">カテゴリA</a> </li>
<li> <a href="#">カテゴリC</a> </li>
</ol>
<figcaption>キャプション2</figcaption>
</figure>
</li>
・・・中略・・・
<li class="target" data-category="cat-e cat-a">
<figure> <a href="#"> <img src="images/012.jpg" alt=""> </a>
<h2 class="target-title"> <a href="#">Title 12</a> </h2>
<ol class="target-categories">
<li> <a href="#">カテゴリE</a> </li>
<li> <a href="#">カテゴリA</a> </li>
</ol>
<figcaption>キャプション12</figcaption>
</figure>
</li>
</ol>
</div>
</section>
CSS
* {
margin: 0 auto;
padding: 0;
}
img {
display: block;
max-width: 100%;
height: auto;
}
ol {
list-style: none;
}
a {
text-decoration: none;
color: inherit;
}
body {
margin: 50px auto;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
input[type="radio"] {
position: absolute;
left: -9999px;
}
.filters {
text-align: center;
margin-bottom: 2rem;
}
.filters * {
display: inline-block;
}
.filters label {
text-align: center;
padding: 0.25rem 0.5rem;
margin-bottom: 0.25rem;
min-width: 50px;
line-height: normal;
cursor: pointer;
transition: all 0.2s;
color: #999;
}
.filters label:hover {
background: #994B8A;
color: #fff;
}
.targets {
display: grid;
grid-gap: 1.5rem;
grid-template-columns: repeat(4, 1fr);
}
.targets .target {
background: #fafafa;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.targets .target-title {
font-size: 1.2rem;
margin: 1rem 0 1rem 0.25rem;
color: #999;
}
.targets .target-title:hover {
text-decoration: underline;
}
.targets figcaption {
padding: 0.5rem;
font-size: 0.75rem;
}
.targets .target-categories {
margin-bottom: 0.75rem;
font-size: 0.75rem;
}
.targets .target-categories * {
display: inline-block;
}
.targets .target-categories li {
margin-bottom: 0.2rem;
}
.targets .target-categories a {
padding: 0.2rem 0.5rem;
transition: all 0.1s;
color: #994B8A;
}
.targets .target-categories a:hover {
background: #994B8A;
color: #fff;
}
[value="All"]:checked ~ .filters [for="All"],
[value="cat-a"]:checked ~ .filters [for="cat-a"],
[value="cat-b"]:checked ~ .filters [for="cat-b"],
[value="cat-c"]:checked ~ .filters [for="cat-c"],
[value="cat-d"]:checked ~ .filters [for="cat-d"],
[value="cat-e"]:checked ~ .filters [for="cat-e"],
[value="cat-f"]:checked ~ .filters [for="cat-f"] {
background: #994B8A;
color: #fff;
}
/* メディアクエリ */
@media screen and (max-width: 900px) {
.targets {
grid-template-columns: repeat(3, 1fr);
}
}
@media screen and (max-width: 650px) {
html {
font-size: 14px;
}
.targets {
grid-template-columns: repeat(2, 1fr);
}
}
/* アニメーションは JavaScript で head 内の style 要素に出力 */
JavaScript
<script>
window.addEventListener('DOMContentLoaded', function(){
const input_categories = document.querySelectorAll("input[name=categories]");
//全ての .target の要素(target クラスを指定された div 要素)を取得
const targets = document.querySelectorAll('.target');
// NodeList から配列を作成
targets_array = Array.prototype.slice.call( targets ) ;
//または targets_array = [].slice.call( targets ) ;
//アニメーションのクラス名の固定部分の文字列
const animationClassNamePrefix = 'checked_animation-';
for(let input_category of input_categories) {
input_category.addEventListener('change',function(){
for(let i=0; i<targets.length; i++) {
targets[i].style.setProperty('display', 'block');
//全ての要素からアニメーションのクラスを削除
targets[i].classList.remove(animationClassNamePrefix + i);
}
if( this.checked ) {
if(this.value !== 'All') {
const not_checked_categories = document.querySelectorAll('.target:not([data-category~=' + '"' + this.value + '"])');
for(let not_checked_category of not_checked_categories) {
not_checked_category.style.setProperty('display', 'none');
}
//data-category に選択されたラジオボタンの value 属性の値が含まれる .target の要素にアニメーションのクラスを追加
const checked_categories = document.querySelectorAll('.target[data-category~=' + '"' + this.value + '"]');
for(let checked_category of checked_categories) {
//この要素が全ての.target の要素の中の何番目かを取得
const index = targets_array.indexOf( checked_category );
//取得したインデックスを使ってアニメーション用のクラスを追加
checked_category.classList.add(animationClassNamePrefix + index);
}
}else{
//選択されたラジオボタンの value 属性の値が All の場合は全ての .target の要素にアニメーションのクラスを追加
for(let i=0; i<targets.length; i++) {
//全ての要素からアニメーションのクラスを追加
targets[i].classList.add(animationClassNamePrefix + i);
}
}
}
});
}
//アニメーション
//キーフレームアニメーションのルールを生成して返す関数
const returnAnimation = (animation_class, animation_name, animation_delay, translate_from_x, translate_from_y) => {
const animation_rules = `
.${animation_class} {
animation: ${animation_name} 0.2s ease-in-out ${animation_delay} both;
}
@keyframes ${animation_name} {
0% {
transform: translate(${translate_from_x}, ${translate_from_y});
opacity: 0;
}
100% {
transform: translate(0, 0);
opacity: 1;
}
}`
return animation_rules;
};
//CSS ルールの文字列の初期化
let rules = '';
//ウィンドウ幅
const ww = window.innerWidth;
//アニメーションの開始遅延を調整する値(大きくすれば遅延は小さくなる)
const start_delay_control = 30;
//transform: translate() の水平方向の初期値
const x_init_value = 100;
//transform: translate() の垂直方向の初期値
const y_init_value = 100;
//transform: translate() 水平方向を調整する値
const x_multiple = 10;
//transform: translate() の垂直方向を調整する値
const y_multiple = 10;
//全ての target クラスを指定した div 要素(targets の各要素)にアニメーションを設定
for(let i = 0; i < targets.length; i++ ) {
//要素に指定するアニメーションを用のクラス名
const animation_class = animationClassNamePrefix + i;
//アニメーションの名前(クラス名と同じにしているが、異なる名前でも問題なし)
const animation_name = animationClassNamePrefix + i;
//アニメーションの開始遅延(i の値を元に調整。)
const animation_delay = i/start_delay_control + 's';
//ウィンドウ幅をその要素の要素の水平方向のオフセット座標で割った値
const x = ww/targets[i].offsetLeft;
//translate に指定する x 座標の値の初期値
let translate_from_x = '0px';
//上記で取得した x の値が2より大きければに x 座標の値を負の値に
if(x < 2) {
translate_from_x = (x_init_value + i * x_multiple) + 'px';
}else{
translate_from_x = '-' + (x_init_value + i * x_multiple) + 'px';
}
//translate に指定する y 座標の値
const translate_from_y = (y_init_value + i * y_multiple) + 'px';
//アニメーションのクラスを追加
targets[i].classList.add(animation_class);
//animationend イベントを設定
targets[i].addEventListener('animationend', () => {
// アニメーション終了後にクラスを削除
targets[i].classList.remove(animation_class);
});
//関数 returnAnimation() を使ってルールの文字列を作成(追加)
rules += `${returnAnimation (animation_class, animation_name, animation_delay, translate_from_x, translate_from_y)}`;
}
//style 要素を作成
const s = document.createElement( 'style' );
//style 要素にルールを設定
s.innerHTML = rules;
//head 要素の末尾に上記で作成した style 要素を追加
document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
});
</script>