はじめに
今回は、Alpine.jsの実装アイデア集とのことで実際に自分が使いそうなパターンのコードをまとめてみました。生のJavaScriptを書くの面倒くさいな〜って思うときにAlpine.jsを使おうかと思います。
案件で他のパターンでAlpine.jsを使った時は随時この記事で追加していく予定です。
Alpine.jsとは?
Alpine.jsは、簡単に言うと「軽くてお手軽なJavaScriptフレームワーク」です。HTMLにちょっとしたコードを追加するだけで、クリックでメニューを開いたり、条件によって要素を表示・非表示にしたりと、動的なUIがサクッと作れちゃいます。
「ReactとかVueって名前は聞いたことあるけど、なんか難しそう…」って感じたことありませんか?
Alpine.jsなら大丈夫!特別な設定や面倒な準備はいりません。
以下のスクリプトを読み込むだけで使えるから、誰でも気軽にスタートできます。
<script src="//unpkg.com/alpinejs" defer></script>
動的なUIの例では、フォームのバリデーションやアコーディオンメニュー、モーダルウィンドウなんかもパパッと作れます。
詳しい使い方などは公式のドキュメントを見てみてください!

それではデモを見ていきましょう!
Note
今回はcssは詳しく解説しないので気になる方はCodePen上で確認してみてください!
【デモ01】カテゴリーフィルターの表現
最初はAlpine.jsでのカテゴリーフィルターの実装を見ていきましょう。
カードコンポーネントがありタブをクリックすると特定のカテゴリーが表示されます。要件としてはクリックするとカテゴリーのidをURLに付与させるようにします。
この実装で、ブラウザの戻るボタンを押したりリロードをしてもタブの切り替えを維持できます。
(おそらくCodePenではURLの確認はできないと思うのでぜひ手元で確かめてみてください!)
HTML
HTMLは以下のようになっています。
<div class="tab-content" x-data="{ open: 'all' }"
x-init="if (window.location.search) {
const urlParams = new URLSearchParams(window.location.search);
const category = urlParams.get('category');
if (category) open = category;
}"
x-cloak
>
<div class="tab-list" role="tablist">
<a class="tab" href="javascript:void(0)" role="tab" id="all"
@click="open = 'all'; window.history.pushState({}, '', '?category=all')"
aria-controls="all" :aria-selected="open === 'all'"
:class="{ '-active': open === 'all' }"
>すべて</a>
<a class="tab" href="javascript:void(0)" role="tab" id="html"
@click="open = 'html'; window.history.pushState({}, '', '?category=html')"
aria-controls="html" :aria-selected="open === 'html'"
:class="{ '-active': open === 'html' }"
>HTML</a>
// 以降CSS,JavaScriptタグが続く
</div>
<div class="card-wrap">
<div class="card" x-show="open === 'all' || open === 'html'" x-transition>
<div class="card-img">
<img src="https://picsum.photos/600/400?random=0" alt="">
</div>
<div class="card-body">
<div class="card-category" id="html">HTML</div>
<div class="card-title">HTMLの記事だよ01</div>
</div>
</div>
<div class="card" x-show="open === 'all' || open === 'css'" x-transition>
<div class="card-img">
<img src="https://picsum.photos/600/400?random=1" alt="">
</div>
<div class="card-body">
<div class="card-category" id="css">CSS</div>
<div class="card-title">CSSの記事だよ01</div>
</div>
</div>
// 以降カードコンポーネント続く
</div>
</div>
コード量が多く分かりづらいですが、コードを1つずつ見ていきましょう。
全体のデータ管理(x-data)と初期化の状態(x-init)
<div class="tab-content" x-data="{ open: 'all' }"
x-init="if (window.location.search) {
const urlParams = new URLSearchParams(window.location.search);
const category = urlParams.get('category');
if (category) open = category;
}"
x-cloak
>
</div>
x-data="{ open: 'all' }"
で現在選択中のカテゴリーを管理するopenという変数を定義します。
初期状態ではall
にし、すべてのカードが表示されるようにしています。
ページロード後もカテゴリーが選択されている状態にしたいため、x-init
でURLのクエリーパラメータを取得して、その値をopenに代入しています。
タブ部分(.tab-list)
<div class="tab-list" role="tablist">
<a class="tab" href="javascript:void(0)" role="tab" id="all"
@click="open = 'all'; window.history.pushState({}, '', '?category=all')"
aria-controls="all" :aria-selected="open === 'all'"
:class="{ '-active': open === 'all' }"
>すべて</a>
<a class="tab" href="javascript:void(0)" role="tab" id="html"
@click="open = 'html'; window.history.pushState({}, '', '?category=html')"
aria-controls="html" :aria-selected="open === 'html'"
:class="{ '-active': open === 'html' }"
>HTML</a>
// 以降CSS,JavaScriptタグが続く
</div>
各タブはaタグで実装しており、ページ遷移をさせないためhref="javascript:void(0)"
としています。
タブをクリックすると以下の処理が行われます。
- oepnの値をクリックされたカテゴリーに更新。
window.history.pushState
でURLのクエリーパラメータを更新。- クリックされたタブには
-active
クラスを付与され(openの値と一致しているので)、ARIA属性も適切に更新されます。
カード表示部分(.card-wrap)
最後にカード表示部分になります。
<div class="card-wrap">
<div class="card" x-show="open === 'all' || open === 'html'" x-transition>
<div class="card-img">
<img src="https://picsum.photos/600/400?random=0" alt="">
</div>
<div class="card-body">
<div class="card-category" id="html">HTML</div>
<div class="card-title">HTMLの記事だよ01</div>
</div>
</div>
// ...
</div>
カードはx-show
でopenがallの時か、特定のカテゴリー(htmlなど)の場合に表示されます。
x-transition
でアニメーションを付与させることができます!
Alpine.jsを使うことでJSファイルを用意せずに実装できますが、その分htmlファイルが複雑になりますね💦
ですが、ARIA属性なども適切に設定し使い回すことができるので慣れていきたいですね!
【デモ02】モバイルの時にアコーディオンメニューになる
デモ02は画面サイズに応じて異なる挙動をするアコーディオンUIのデモになります。具体的には、モバイル画面サイズ(768px未満)の場合にアコーディオン機能が有効になり、デスクトップでは通常のリスト表示になります。
HTML
<div class="accordion-content"
x-data="{ isMobile: window.innerWidth < 768 }"
x-init="window.addEventListener('resize', () => isMobile = window.innerWidth < 768)"
>
<details class="accordion" :open="!isMobile">
<summary class="accordion-summary" :tabindex="isMobile ? '0' : '-1'">目次</summary>
<div class="accordion-body">
<ul class="accordion-list">
<li class="accordion-item">目次1</li>
// ...
</ul>
</div>
</details>
</div>
アコーディオンにはdetailsタグを使っています。detailsタグはopen属性で開閉を制御でき、openがtrueの場合は開いた状態になります。なのでx-dataで画面幅がモバイル画面サイズ(768px未満)かを判定する変数isMobileを定義してモバイル画面ではないとき(!isMobile)はアコーディオンが開いている状態になります!
detailsタグのデフォルトだとTab操作が可能になるので、アコーディオンが開いているときはtabindexを-1にしてTab操作を無効にしています。
また、デスクトップ画面だとアコーディオンが開いていますが、クリックするとdetailsタグなので閉じてしまうので、cssでpointer-events: none
でクリックイベントを無効にしましょう!
.accordion {
pointer-events: none;
@media (max-width: 768px) {
pointer-events: all;
}
}
このデモみたいにわざわざJavaScript書くのが地味に面倒くさい時にAlpine.jsは便利ですね!
【デモ03】ドロップダウンメニュー
デモ03はドロップダウンメニューのデモになります。
HTML
<div class="dropdown"
x-data="{ open: false }"
@click.outside="open = false"
>
<button class="dropdown-btn" @click="open = !open">
ドロップダウンメニュー
</button>
<div class="dropdown-menu"
x-show="open"
@click.outside="open = false"
x-transition
x-clock
>
<ul>
<li class="dropdown-item">アイテム1</li>
<li class="dropdown-item">アイテム2</li>
<li class="dropdown-item">アイテム3</li>
</ul>
</div>
</div>
今まで同様なので特に解説することはないですが、ドロップダウンメニューの場合は@click.outside
でクリックされた要素以外の場所をクリックされたらメニューを閉じるようになります!
【デモ04】TOPへ戻るボタン
デモ04はTOPへ戻るボタンのデモになります。
キービジュアル(KV)を超えてからTOPへ戻るボタンが表示されるようにしています。
また、ボタンをposition: fixedで指定するとフッターに被るので、ボタンにはposition: stickyを指定しフッターに被らないようにしています。
HTML
全体のHTMLは以下のようになります。
<div class="kv top-btn-target">KV</div>
<div class="container">
<div class="content"></div>
<div class="top-btn"
x-data="{ showButton: false }"
:class="{ 'is-show': showButton }"
@click="window.scrollTo({ top: 0, behavior: 'smooth' })"
x-init="
const target = document.querySelector('.top-btn-target');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
showButton = !entry.isIntersecting;
});
});
observer.observe(target);
"
x-clock></div>
<footer class="footer">Footer</footer>
</div>
↓cssコードの全文は折りたたみを見てください!
cssコードの全文
[x-cloak] {
display: none !important;
}
.kv {
background: #222;
height: 70vh;
display: grid;
place-items: center;
color: #fff;
font-size: 8vw;
}
.content {
width: 100%;
height: 250vh;
position: relative;
}
.footer {
margin-top: 40px;
background: #000;
height: 200px;
display: grid;
place-items: center;
color: #fff;
font-size: 8vw;
}
.top-btn {
width: 52px;
height: 52px;
border: 1px solid #000;
background: #fff;
border-radius: 50%;
display: grid;
place-items: center;
position: sticky;
left: 90%;
bottom: 40px;
z-index: 8;
transition: opacity 0.3s;
opacity: 0;
@media (max-width: 768px) {
width: 40px;
height: 40px;
right: 16px;
bottom: 16px;
}
&.is-show {
opacity: 1;
}
&::before {
content: '';
width: 12px;
height: 12px;
border-top: 2px solid #000;
border-right: 2px solid #000;
rotate: -45deg;
translate: 0 2px;
transition: border-color 0.3s;
@media (max-width: 768px) {
width: 9px;
height: 9px;
border-width: 1px;
translate: 0 1px;
}
}
&:hover {
background: #000;
&::before {
border-color: #fff;
}
}
}
TOPへ戻るボダンをクリックすると、window.scrollTo({ top: 0, behavior: 'smooth' })
でページがスムーズにトップまでスクロールします。
キービジュアル(KV)を超えたらの監視はIntersectionObserverを使っています。
対象は.top-btn-target
になり、画面外に出たらshowButton
をtrueにしてis-show
クラスを付与してボタンを表示させています!
わざわざJS書かなくていいので楽ですね!
まとめ
Alpine.jsの実装アイデア集を何種類かまとめました🏔️
簡単に動的なUIを実装できるので、生のJavaScriptを書くの面倒くさいな〜って思うときにAlpine.jsを使っていこうと思います!
この記事が参考になれば幸いです。