SolidJS 用アニメーションライブラリ、Solid Transition Group を使ってみた
この記事は「株式会社メンバーズ Jamstack研究会主催 Advent Calendar
2023」の20日目の記事です。
はじめに
この記事では、SolidJS 用のアニメーションライブラリである Solid Transition Group を使ってみましたので、その感想と、使い方のざっくりとした説明などをまとめました。
設計や機能は、React 用の React Transition Group と、Vue の Transition に影響を受けている、とのことです。
触ってみた感想としては、それらと大部分は似通っていますが、更にシンプルに扱えるよう、調整・洗練されている箇所が多いと感じました。
本記事は、Solid Transition Groupの v0.2.3 を利用した場合の内容となっています。
インストール
SolidJS がインストール済みのプロジェクトが既にある前提で話を進めていきます。
インストール作業は、solid-transition-group の package をお好みの方法でインストールするだけです。
npm install solid-transition-group
# or
yarn add solid-transition-group
# or
pnpm add solid-transition-group
インストールが完了したら、早速アプリ内で利用してみましょう。
Transition コンポーネント を使ったシンプルな例
Solid Transition Group では、<Transition> と <TransitionGroup> の2種類のアニメーション制御を扱えます。今回は <Transition> に絞ってご紹介したいと思います。
<Transition> は、要素やコンポーネントが DOM 内に enter (挿入) 、exit (削除) されるとき、アニメーションを適用するために利用します。
例として、以下の画像とソースコードで示したページで考えてみましょう。
Show と Hide の2つのボタンがあり、それぞれを押すことで、表示状態を管理する isVisible の true / false が切り替わります。
その下には、p 要素で包まれた "Hello transition!" というテキストがあり、SolidJS 組み込みの <Show> コンポーネントが isVisible をもとに DOM への挿入、削除を制御しています。
import { Show, createSignal } from 'solid-js';
import { Transition } from 'solid-transition-group';
import './App.css'
function App() {
const [isVisible, setVisible] = createSignal(true)
return (
<>
<button onClick={() => setVisible(true)}>Show</button>
<button onClick={() => setVisible(false)}>Hide</button>
<Show when={isVisible()}>
<div>Hello transition!</div>
</Show>
</>
)
}
export default App
今回は、上記のページに対し、以下のアニメーションを実装することを目標に進めてみます。
初期状態
Show ボタンと Hide ボタンだけが表示され、”Hello transition!” は非表示
Show ボタンをクリックしたとき
"Hello transition!" がフェードイン
透明状態から 1.5s かけて不透明になる
Hide ボタンをクリックしたとき
"Hello transition!" がフェードアウト
不透明状態から 1s かけて透明になる
Transition コンポーネント に要素を渡す
<Transition> を import して JSX 内に配置し、アニメーションさせたい要素・コンポーネントを children として渡すことで、アニメーションの対象とすることができます。
今回は、<Show> で制御されている p 要素のアニメーションを制御するため、以下のように記述します。
import { Transition } from "solid-transition-group"
(中略)
<Transition name="fade">
<Show when={isVisible()}>
<p>Hello transition!</p>
</Show>
</Transition>
ライフサイクル
<Transition> は、アニメーションのライフサイクル内の各段階ごとに class を生成し、children として渡した要素・コンポーネントに対し適用し、その段階が終われば自動で class を削除します。
ライフサイクルは以下の通りです。
enter(要素の追加時)
enter : enter 開始の瞬間
enter-to : enter の開始後、遷移が続いている間
enter-active : enter と enter-to を含めた、遷移が続いている間
exit(要素の削除時)
exit : exit 開始の瞬間
exit-to : exit が開始後、遷移が続いている間
exit-active : exit と exit-to を含めた、遷移が続いている間
要素・コンポーネントに適用される class 名は、上記のコード内で <Transition> の props として指定した name (今回は "fade" としました。省略した場合のデフォルトは "s" になります) が先頭につき、ライフサイクル内のステップ名が続きます。
例えば、enter の遷移中は、同時に `enter-to` と `enter-active` の2つの状態になるので、実際にレンダリングされる p 要素は以下のようになります。
<p class="fade-enter-to fade-enter-active">Hello transition!</p>
適用される class 名は既に分かっているので、CSS ファイル内に、上記の class 名の通りに selector を指定し、ライフサイクルの段階ごとにどの style が適用されてほしいか、を記述します。
今回は、以下のような CSS が出来上がりました。
/* enter, exit のそれぞれで transition プロパティを指定することができる */
.fade-enter-active {
transition: opacity 1.5s;
}
.fade-exit-active {
transition: opacity 1s;
}
/* enter の開始時は opacity: 0(透明) */
.fade-enter {
opacity: 0;
}
/* enter の終了時に opacity: 1(不透明) になるよう遷移させる */
.fade-enter-to {
opacity: 1;
}
/*
enter 後(isVisible = true)の間は opacity: 1(不透明) になっているので、
.fade-exit で opacity: 1 を指定する必要はな
*/
/* exit の終了時に opacity: 0(透明) になるよう遷移させる */
.fade-exit-to {
opacity: 0;
}
上の例では、.fade-enter と .fade-exit で指定している内容は同じなのでまとめてもいいんですが、分かりやすくするため別々に分けています。
動かしてみる
目指していた動作が実現できました!
適用されているクラスが分かるよう、DevTools も同時に表示しています。(.fade-enter と .fade-exit は一瞬すぎて見えませんが……)
同系統のライブラリでは、class を付与してから削除するまでの時間を timeout という props として指定しなければならない場合もあるんですが、
Solid Transition Group では、デフォルトで CSS の transitionend, animationend イベントを検知して自動で class を削除してくれるため、とてもシンプルに使えると感じました。
今回は CSS での適用だけでしたが、<Transition> に渡す props によって、アニメーションのライフサイクルの各段階で JavaScript の関数呼び出しをさせることができるため、
遷移完了を検出して何かのトリガーにするなど、活用の幅も広がります。
まとめ
この記事の執筆時点では、Solid Transition Group について書かれた日本語の記事が見当たらなかったため、Solid Transition Group をはじめとした関連ライブラリをもっと広め、導入の敷居を下げたいという思いで記事を書いてみました。
Jamstack での文脈上では、SolidJS は Astro 上でも利用できたり、まだベータ版ですがメタフレームワークである SolidStart があったりと、SolidJS はまだまだ盛り上がりそうな様子です。これから更に利用者が増えるよう、情報発信を続けていきたいです。
最後までご覧いただき、ありがとうございました!