チュートリアル
[発展]スムーズなページ遷移を実装しよう

[発展]スムーズなページ遷移を実装しよう

negl では SPA モードでの画面遷移(画面ロードなく他のページに遷移する画面の遷移)が用意されています。 今回は SPA モードの基本的な実装方法について紹介します。

サンプルサイト

以下の2つのサイトは SPA モードでの画面遷移が行われています。 その為、ページを遷移しても画面リロードはなくより洗礼された Web サイトになっています。

SPA モードの有効化

まず、SPA モードを有効にするためには config.transition.spaMode オプションを true に設定してください。

SPAモードの有効化
const { start } = setup({ transition: { spaMode: true } });

これで、SPA モードが有効になり、以下で紹介する機能が実行される状態になります。

SPA モードの概要

まずは具体的な実装の方法を見ていく前に SPA モードの画面遷移がどのように行われるのかについて説明します。 SPA モードの画面遷移は大きく分けて次の2つの工程で構成されます。

トランジションタイプの決定

リンクやメッシュがクリックされたタイミングでトランジションの種類(トランジションタイプ)が決定されます。

画面が切り替わる際のエフェクトを画面遷移の契機(リンクやメッシュ、またはブラウザバックのアクションなど)によって切り替えることが出来るようにしたのがトランジションタイプ(画面遷移の種類)です。トランジションタイプを使い分けることで、1つのサイトに複数の画面遷移のパターンを実装することができます。

💡

トランジションタイプは次で紹介するフックに画面遷移処理を登録する際にユーザーが独自に定義することができます。

トランジションのフックの実行

トランジションタイプが決まると、トランジションタイプに合わせたフック(トランジション用のフックは(T_*)で始まるものです。)が実行されていきます。フックが実行されるタイミングはページが遷移する工程で複数用意されています。

また、トランジション用のフックの実行時は現在(current)ページと次(next)のページの DOM 情報とメッシュの情報にアクセスすることができます。 その為、このフックにDOM やメッシュを操作する画面遷移アニメーションを定義する処理を登録すれば、ユーザー独自の SPA 画面遷移が実装できることになります。

以上が SPA で画面遷移する際の概要となります。面倒な処理は negl が裏で自動的に行うため、ユーザーは画面遷移のアニメーションの実装に集中することができます。

では、それぞれの工程についてより詳しく見ていきましょう。

トランジションタイプの決定

トランジションタイプを決定する方法は以下の3つです。

  1. リンク、またはエフェクトに個別設定
  2. デフォルトトランジションタイプの設定
  3. JS からトランジションタイプを設定

1. リンク、またはエフェクトに個別設定

リンクまたはエフェクトに data-trantion 属性を付けることで、リンクやエフェクトをクリックした際のトランジションタイプを指定することができます。例えば、次のようにして data-transition を付与します。

index.html
<!-- リンクの場合 -->
<a href="/detail.html" data-transition="some-transition">リンク</a>
 
<!-- エフェクトの場合 -->
<img
  data-webgl="some-effect"
  data-transition="another-transition"
  data-interactive
  data-click-1="/detail.html"
/>

このようにすると、detail.html ページに遷移する際にクリックする要素によって some-effectanother-transition のトランジションタイプで画面遷移のアニメーションが実行されるようになります。

💡

余談ですが、エフェクトのクリックで画面遷移する際は併せて data-interactivedata-click-* が必要になりますので、忘れないように注意してください。

2. デフォルトトランジションタイプの設定

デフォルトトランジションタイプはトランジションが設定されていない要素をクリックした際に適用されるトランジションタイプです。

config.transition.defaultType で設定することができます。

デフォルトトランジションタイプの設定
const { start } = setup({
  transition: { defaultType: "default-transition" },
});

基本的にはデフォルトトランジションタイプにサイト内で一番よく使うメインのトランジションタイプを設定し、トランジションタイプが2つ以上ある場合には 1 の方法で個別にトランジションタイプを設定するのがオススメです。

3. JS からトランジションタイプを設定

もし、JavaScript でトランジションタイプを設定したい場合は transition.changeType を使用してください。

この関数が返す値が最終的なトランジションタイプとなります。 その為、動的にトランジションタイプを切り替えることができます。

例えば、以下のリンクをクリックした際には some-transition のタイプでトランジションタイプが設定されますが、transition.changeType を使うことで異なる another-transition に変更しています。

<a href="/detail.html" data-transition="some-transition"></a>
transition.changeType((transitionType, state) => {
  console.log(transitionType);
  // > some-transition
  return "another-transition"; // another-transition にトランジションタイプを変更
});

詳細は transition.changeType をご覧ください。

以上がトランジションタイプを決定する方法です。
では次にトランジションのフックの実行方法について見ていきましょう。

トランジションのフックの実行

画面遷移時に実行したい処理はトランジション用のフックに対して行います。トランジション用のフックの利用も Hook オブジェクトを通して行います。トランジション用のフックは次の通りです。

トランジション用のフック

  1. T_INIT(ページトランジションの初期化時)
  2. T_BEGIN(ページトランジションの開始時)
  3. T_NEXT_DOM_BEFORE(次のページの DOM が挿入される前)
  4. T_BOTH_DOM_EXIST(次のページの DOM にアクセス可能な状態)
  5. T_BOTH_OB_EXIST(次のページの Ob オブジェクトがアクセス可能な状態)
  6. T_END(ページトランジション終了時)
  7. T_ERROR(ページトランジションでエラーが出た時)

これらのフックに登録したコールバック関数が画面遷移時に実行されます。また、フックを登録する際はトランジションタイプ(type)を第三引数のオブジェクトに設定します。仮に type を省略した際は base というタイプが自動的に振られます。

💡

base のトランジションタイプのコールバック関数は全てのトランジションタイプで実行されます。

例えば、T_BEGIN に対して次の関数を登録するとリンク、またはメッシュをクリックしたタイミングでログが出力されます。

トランジションの登録
hook.on(hook.T_BEGIN, () => console.log("base が実行されました。"), {
  type: "base",
  priority: -1, // フックオプションの priority を -1 に設定
});
 
hook.on(hook.T_BEGIN, () => console.log("some-transition が実行されました。"), {
  type: "some-transition",
  priority: 0, // フックオプションの priority を 0 に設定。(-1のフックよりも先に実行されます。)
});
 
hook.on(
  hook.T_BEGIN,
  () => console.log("another-transition が実行されました。"),
  { type: "another-transition", priority: 0 }
);
index.html
<a href="/detail.html" data-transition="some-transition">some-transition</a>
<a href="/detail.html" data-transition="another-transition"
  >another-transition</a
>
クリック結果
// それぞれ以下のログが表示されます。
 
// data-transition="some-transition" リンクをクリックした時
// > some-transition が実行されました。
// > base が実行されました。
 
// data-transition="another-transition" リンクをクリックした時
// > another-transition が実行されました。
// > base が実行されました。

some-transitionanother-transitionどちらのリンクをクリックしても base が出力されます。 上記の例では T_BEGIN のフックに対して、コールバック関数を登録しましたが、他のトランジション用のフックでも登録の仕方は同じです。

各フックの特徴

各フックについての留意事項について紹介します。

T_INIT(ページトランジションの初期化時)

引数:{} 空のオブジェクト

このフックはユーザーがページにランディングした際に一度だけ呼ばれます。(SPA による画面遷移時には呼ばれません。)

T_BEGIN(ページトランジションの開始時)

引数:state

このフックはトランジション開始直後に呼ばれます。また、引数には state が渡ります。
現在ページのメッシュのフェードアウトや three.js のパスによって画面全体に効果を付ける開始点として最適のタイミングとなります。

💡

state とは?
state はトランジションの状態を管理する TransitionState 型のオブジェクトです。 このオブジェクトは現在ページと次のページの DOM 情報とメッシュの情報等を保持しています。

現在ページの情報は current プロパティに格納され、次のページの情報は next プロパティに格納されます。

例えば、現在のページで表示されている .page-container 要素の情報は state.current.el で取得することができます。 また、現在ページで表示されているメッシュを取得するためには state.current.os で取得することができます。

その為、現在ページのメッシュを全て非表示にしたい場合は次のように記述することができます。

現在ページのメッシュを全て非表示
hook.on(hook.T_BEGIN, (state) => {
  // 現在ページの情報を取得
  const { current } = state;
  // 現在ページに存在するエフェクトをループして、メッシュを非表示に変更
  current.os.forEach((ob) => (ob.mesh.visible = false));
 
  /* 画面上に表示されていたメッシュが非表示となります。 */
});
T_BEGINの裏で次のHTMLを取得しています。

current が保持する情報の詳細はこちらをご覧ください。

T_NEXT_DOM_BEFORE(次のページの DOM が挿入される前)

引数:state

このフックは次のページが現在のページに挿入される前に発火します。すなわち次のページの HTML データを DOM に変換し、JS 上には保持していますが、DOM ツリーにはまだ挿入していない状況です。
次のページの HTML を DOM に挿入する前に何らかの前処理を行いたい場合にはこちらのフックを利用してください。

// 次のページの DOM が挿入される前に実行する関数を登録します。
// priority が大きいフックから実行されます。
hook.on(hook.T_NEXT_DOM_BEFORE, fixCurrentPage, { priority: 1000 });
hook.on(hook.T_NEXT_DOM_BEFORE, transparentNextHTML, { priority: 900 });
 
// 古いページを position: fixed にし、新しいページを重ねて表示できるようにする
async function fixCurrentPage({ current: { el } }) {
  const currentRect = el.getBoundingClientRect();
  el.style.top = currentRect.y + "px";
  el.style.width = `100%`;
  el.style.position = "fixed";
  el.style.left = 0;
}
 
// 次のページの.page-container要素を opacity: 0 とする
function transparentNextHTML({ next: { el } }) {
  // next.el: 次のページの.page-container要素
  el.style.opacity = 0;
}
💡

このフックからは次のページの next.os(次のページのエフェクトの配列) 以外の情報にアクセスすることができます。 詳細はこちら をご覧ください。

T_BOTH_DOM_EXIST(次のページの DOM にアクセス可能な状態)

引数:state

このフックは次のページが DOM ツリーに追加された直後(現在のページに HTML が追加された直後)に発火します。 その為、このフックの状態では 現在ページの .page-container(state.current.el) と次のページの .page-container(state.next.el) の両方が DOM ツリーに存在する状態です。

T_BOTH_OB_EXIST(次のページの Ob オブジェクトがアクセス可能な状態)

引数:state

このフックでは現在ページと次のページの全てのエフェクトにアクセス可能な状態です。 その為、基本的にはこのフックでページ遷移のメインのアニメーションを記述します。

以下のコードは、ジブリサイトの遷移先のページを表示するアニメーションの実装(transition-ghibli.js)です。

💡ジブリサイトのフェードインアニメーションを見てみよう
⚠️

このフックの実行が終わった時点で state.current.os に含まれるエフェクト(Ob オブジェクト)が three.js のシーンから自動的に除外される対象となります。その為、もし特定のエフェクトを画面に残したままにしたい場合はこのフックが完了するまでに state.current.os から対象の Ob オブジェクトを除外してください。

画面に残したいフックは T_BOTH_OB_EXIST の完了より前に current.os から除外
hook.on(hook.T_BEGIN, keepOb, { type, priority: 100 });
function keepOb({ current }) {
  // 次のページでも保持し続けるObのセレクター
  const keepObs = [".ocean", ".sky"];
  keepObs.forEach((selector) => {
    const ob = world.getObByEl(selector);
    const idx = current.os.indexOf(ob);
    // current.osから除外
    current.os.splice(idx, 1);
  });
}

T_END(ページトランジション終了時)

引数:state

このフックはトランジション終了時に呼ばれます。この時点でページトランジションに関する処理は全て終了している状態ですので、何か後処理などがある場合はこちらのフックを利用してください。

T_ERROR(ページトランジションでエラーが出た時)

引数:state

このフックはページトランジション中に何かエラーが発生した際に呼ばれます。 なお、ページトランジション中にブラウザバックなどの操作が呼ばれた場合は画面をリロードする仕様となっています。

以上が transition の制御となります。 ダウンロード可能なサンプルコード内ではより実践的なコードを核にすることができます。 トランジションの制御を行っているファイルは transition-*.js というファイル名にしています。

実際に動かしながらコードを確認し、何かご質問がありましたらコミュニティで聞いてください。