15
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SvelteKitでView Transitions APIを使う方法

Last updated at Posted at 2023-08-30

はじめに

SvelteKit v1.24.0がリリースされ、onNavigateというライフサイクル関数が追加されました。
onNavigateを使用することでURLに移動する直前に指定したコールバックを実行することができます。
onNavigateを使うことでSvelteKitでView Transitions APIを使用して、リッチなページ遷移を実現する方法を説明します。

注意

View Transitions APIはChrome 111 以上、または Edge 111 以上 で使用できます。
FirefoxやSafariでは今のところ動かないので注意してください。

SvelteKitプロジェクトのセットアップ

まず、SvelteKitプロジェクトをセットアップします。以下のコマンドで新しいプロジェクトを作成できます。
テンプレートを選択できますが、SvelteKit demo appを選択してください。

npm create svelte@latest my-app

┌  Welcome to SvelteKit!
│
◆  Which Svelte app template?
│  ● SvelteKit demo app (A demo app showcasing some of the features of SvelteKit - play a word guessing game that works without JavaScript!)
│  ○ Skeleton project
│  ○ Library project
└

次に、プロジェクトディレクトリに移動します。

cd my-app

依存関係をインストールします。

npm install

View Transitions APIを使って、リッチなページ遷移を実装する

+layout.svelteを編集する

src/routes/+layout.svelte
<script>
	import Header from './Header.svelte';
	import './styles.css';
+   import { onNavigate } from '$app/navigation';

+   onNavigate(() => {
+    if (!document.startViewTransition) return;
+
+    return new Promise((resolve) => {
+      document.startViewTransition(async () => {
+        resolve();
+        await navigation.complete;
+      });
+	 });
+	});
</script>

<div class="app">
	<Header />

	<main>
		<slot />
	</main>

	<footer>
		<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
	</footer>
</div>

<style>
	.app {
		display: flex;
		flex-direction: column;
		min-height: 100vh;
	}

	main {
		flex: 1;
		display: flex;
		flex-direction: column;
		padding: 1rem;
		width: 100%;
		max-width: 64rem;
		margin: 0 auto;
		box-sizing: border-box;
	}

	footer {
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
		padding: 12px;
	}

	footer a {
		font-weight: bold;
	}

	@media (min-width: 480px) {
		footer {
			padding: 12px 0;
		}
	}
</style>

Header.svelteを編集する

src/routes/Header.svelte
<script>
  import { page } from "$app/stores";
  import logo from "$lib/images/svelte-logo.svg";
  import github from "$lib/images/github.svg";
</script>

<header>
  <div class="corner">
    <a href="https://kit.svelte.dev">
      <img src={logo} alt="SvelteKit" />
    </a>
  </div>

  <nav>
    <svg viewBox="0 0 2 3" aria-hidden="true">
      <path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
    </svg>
    <ul>
      <li aria-current={$page.url.pathname === "/" ? "page" : undefined}>
        <a href="/">Home</a>
      </li>
      <li aria-current={$page.url.pathname === "/about" ? "page" : undefined}>
        <a href="/about">About</a>
      </li>
      <li
        aria-current={$page.url.pathname.startsWith("/sverdle")
          ? "page"
          : undefined}
      >
        <a href="/sverdle">Sverdle</a>
      </li>
    </ul>
    <svg viewBox="0 0 2 3" aria-hidden="true">
      <path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
    </svg>
  </nav>

  <div class="corner">
    <a href="https://github.com/sveltejs/kit">
      <img src={github} alt="GitHub" />
    </a>
  </div>
</header>

<style>
  header {
    display: flex;
    justify-content: space-between;
  }

  .corner {
    width: 3em;
    height: 3em;
  }

  .corner a {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
  }

  .corner img {
    width: 2em;
    height: 2em;
    object-fit: contain;
  }

  nav {
    display: flex;
    justify-content: center;
    --background: rgba(255, 255, 255, 0.7);
  }

  svg {
    width: 2em;
    height: 3em;
    display: block;
  }

  path {
    fill: var(--background);
  }

  ul {
    position: relative;
    padding: 0;
    margin: 0;
    height: 3em;
    display: flex;
    justify-content: center;
    align-items: center;
    list-style: none;
    background: var(--background);
    background-size: contain;
  }

  li {
    position: relative;
    height: 100%;
  }

  li[aria-current="page"]::before {
    --size: 6px;
    content: "";
    width: 0;
    height: 0;
    position: absolute;
    top: 0;
    left: calc(50% - var(--size));
    border: var(--size) solid transparent;
    border-top: var(--size) solid var(--color-theme-1);
  }

  nav a {
    display: flex;
    height: 100%;
    align-items: center;
    padding: 0 0.5rem;
    color: var(--color-text);
    font-weight: 700;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    text-decoration: none;
    transition: color 0.2s linear;
  }

  a:hover {
    color: var(--color-theme-1);
  }

+  li[aria-current="page"]::before {
+    view-transition-name: active-page;
+  }
</style>

app.d.tsを編集する

startViewTransitionがIDE等でうまく認識してくれなくて型エラーになる場合があります。

src/app.d.ts
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
	namespace App {
		// interface Error {}
		// interface Locals {}
		// interface PageData {}
		// interface Platform {}
	}

+	interface ViewTransition {
+        updateCallbackDone: Promise<void>;
+        ready: Promise<void>;
+        finished: Promise<void>;
+        skipTransition: () => void;
+    }

+    interface Document {
+        startViewTransition(updateCallback: () => Promise<void>): ViewTransition;
+    }
}

export {};

キャプチャ画面

オレンジのインジケーターがスムーズに動くようになりました :tada: :tada: :tada:

さいごに

今回は、SvelteKit v1.24.0で追加されたonNavigateを用いて、View Transitions APIを使ったリッチなページ遷移の実装方法を説明しました。

この実装により、ユーザのナビゲーション体験を向上させることが可能だと思うので是非試してみてください!

15
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?