Astro の View Transitions 徹底解説:SPA 並みの滑らかなページ遷移を実装するコツ

従来の Web サイトでは、ページ遷移時に画面全体が再読み込みされ、ユーザー体験が途切れてしまうことが課題でした。一方、Astro の View Transitions を活用すると、JavaScript フレームワークに頼らず、驚くほどスムーズなページ遷移が実現できます。
この記事では、Astro の View Transitions API を使って SPA(Single Page Application)並みの滑らかなページ遷移を実装する方法を、初心者の方にもわかりやすく徹底解説していきます。基本的な設定から高度なカスタマイズまで、実践的なコード例とともにご紹介しますので、ぜひ最後までお読みください。
背景
Web ページ遷移の進化
Web サイトのページ遷移は、長年にわたって進化を続けてきました。従来の MPA(Multi-Page Application)では、リンクをクリックするたびにブラウザが新しい HTML ドキュメント全体を読み込み、画面が一瞬白くなる現象が発生していました。
この課題を解決するため、React や Vue.js などの JavaScript フレームワークを使った SPA が登場しました。SPA では、必要な部分だけを動的に更新することで、滑らかなページ遷移を実現できます。しかし、JavaScript のバンドルサイズが大きくなり、初期表示速度が遅くなるというトレードオフがありました。
View Transitions API の登場
2023 年、Chrome をはじめとするモダンブラウザに View Transitions API が実装されました。これは、ページ遷移時にアニメーションを適用できる新しいブラウザ標準 API です。
以下の図は、従来の MPA と View Transitions を適用した場合の遷移フローを示しています。
mermaidflowchart LR
subgraph traditional["従来の MPA"]
page1["ページ A"] -->|クリック| reload["画面が白く<br/>なる"]
reload -->|再読み込み| page2["ページ B"]
end
subgraph vt["View Transitions"]
pageA["ページ A"] -->|クリック| animate["スムーズな<br/>アニメーション"]
animate -->|遷移| pageB["ページ B"]
end
従来の MPA では一度画面が白くなりますが、View Transitions では途切れることなくスムーズに遷移します。
Astro における View Transitions のサポート
Astro は、Astro 3.0 から View Transitions API を公式にサポートしています。Astro の View Transitions には、以下の特徴があります。
# | 特徴 | 説明 |
---|---|---|
1 | ゼロ JavaScript | ネイティブ API を活用し、最小限の JavaScript で動作 |
2 | フォールバック機能 | 未対応ブラウザでも動作する代替機能を提供 |
3 | カスタマイズ性 | アニメーションやタイミングを細かく制御可能 |
4 | アクセシビリティ | prefers-reduced-motion に対応 |
Astro は、静的サイトジェネレーターとしての高速性を保ちながら、SPA のような滑らかな UX を提供できる点が大きな魅力です。
近年、Core Web Vitals などのパフォーマンス指標が SEO にも影響するようになり、高速かつ滑らかな Web サイトの需要が高まっています。Astro の View Transitions は、パフォーマンスと UX の両立を実現する強力なソリューションといえるでしょう。
課題
従来のページ遷移における問題点
Web サイトを構築する際、ページ遷移の実装には複数の課題が存在します。開発者は、ユーザー体験とパフォーマンスのバランスを取ることに苦労してきました。
MPA の課題
従来の MPA では、ページ遷移のたびに以下の問題が発生します。
- 画面全体が再描画されるため、視覚的な連続性が失われる
- ヘッダーやナビゲーションなどの共通要素も再読み込みされる
- ページ読み込み中に画面が白くなり、ユーザーが不安を感じる
- CSS や JavaScript ファイルが毎回再ダウンロードされる可能性がある
SPA の課題
一方、JavaScript フレームワークを使った SPA にも課題があります。
# | 課題 | 詳細 |
---|---|---|
1 | バンドルサイズ | JavaScript のサイズが大きくなり、初期表示が遅延 |
2 | SEO の複雑化 | クライアントサイドレンダリングによる検索エンジン対応 |
3 | 複雑な状態管理 | ルーティングや状態の管理に高度な知識が必要 |
4 | 開発コスト | フレームワークの学習コストと保守コストが高い |
これらの課題により、開発者は「シンプルな静的サイト」と「リッチな SPA」のどちらを選ぶべきか、という難しい選択を迫られてきました。
ブラウザ互換性の問題
View Transitions API は比較的新しい技術のため、ブラウザのサポート状況にばらつきがあります。Chromium ベースのブラウザ(Chrome、Edge など)では完全にサポートされていますが、Safari や Firefox では段階的な対応が進められています。
以下の図は、View Transitions を実装する際のブラウザ対応フローです。
mermaidflowchart TD
start["ページ遷移開始"] --> check["ブラウザが<br/>View Transitions<br/>をサポート?"]
check -->|Yes| native["ネイティブ API<br/>でアニメーション"]
check -->|No| fallback["フォールバック<br/>処理"]
native --> done["遷移完了"]
fallback --> swap["即座に<br/>ページ切り替え"]
swap --> done
ブラウザがネイティブに対応していない場合、フォールバック処理が必要になります。
アニメーションのカスタマイズの難しさ
デフォルトのフェードアニメーションは便利ですが、ブランドイメージやデザインに合わせたカスタマイズが必要な場合もあります。
しかし、以下のような課題があります。
- CSS のアニメーション知識が必要
- 複数ページ間で一貫したアニメーションを実装する手間
- パフォーマンスへの影響を考慮した最適化
- アクセシビリティへの配慮(視覚障害やアニメーションに敏感なユーザーへの対応)
Astro の View Transitions は、これらの課題を解決するための機能を提供していますが、その使い方を理解することが重要です。
解決策
Astro View Transitions の導入
Astro の View Transitions を使えば、上記の課題を効果的に解決できます。ここでは、基本的な導入方法から高度なカスタマイズまで、段階的に解説していきます。
基本的なセットアップ
Astro で View Transitions を有効にするには、<ClientRouter />
コンポーネントを共通レイアウトの <head>
タグ内に追加するだけです。
まず、必要なコンポーネントをインポートします。
typescript---
// src/layouts/BaseLayout.astro
import { ClientRouter } from 'astro:transitions';
---
次に、レイアウトファイルの <head>
内に <ClientRouter />
を配置します。
astro<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>My Astro Site</title>
<!-- View Transitions を有効化 -->
<ClientRouter />
</head>
<body>
<slot />
</body>
</html>
このシンプルな設定だけで、サイト全体のページ遷移にフェードアニメーションが適用されます。デフォルトでは、古いページがフェードアウトしながら新しいページがフェードインする演出になります。
以下の図は、ClientRouter が有効になったときの内部動作フローです。
mermaidsequenceDiagram
participant User as ユーザー
participant Browser as ブラウザ
participant ClientRouter as ClientRouter
participant DOM as DOM
User->>Browser: リンクをクリック
Browser->>ClientRouter: 遷移をインターセプト
ClientRouter->>DOM: 現在のページの<br/>スナップショット作成
ClientRouter->>Browser: 新しいページを<br/>フェッチ
Browser->>ClientRouter: HTML を返却
ClientRouter->>DOM: View Transition<br/>API を起動
DOM->>DOM: アニメーション実行
DOM->>User: 新しいページを表示
ClientRouter がリンクのクリックを検知し、View Transitions API を呼び出すことで、スムーズなアニメーションを実現しています。
ビルトインアニメーションの活用
Astro には、以下の 3 つのビルトインアニメーションが用意されています。
# | アニメーション名 | 効果 | 推奨用途 |
---|---|---|---|
1 | fade | フェードイン・フェードアウト | 汎用的な画面遷移 |
2 | slide | スライドイン・スライドアウト | ナビゲーション、モーダル |
3 | none | アニメーションなし | 即座の切り替えが必要な場合 |
これらのアニメーションは、transition:animate
ディレクティブを使って個別の要素に適用できます。
以下は、メインコンテンツにスライドアニメーションを適用する例です。
astro---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout>
<!-- スライドアニメーションを適用 -->
<main transition:animate="slide">
<h1>About Us</h1>
<p>会社情報をご紹介します。</p>
</main>
</BaseLayout>
この設定により、ページ遷移時にメインコンテンツがスライドイン・スライドアウトします。
transition ディレクティブによる要素の対応付け
transition:name
ディレクティブを使うと、異なるページ間で同じ要素を対応付けることができます。これにより、特定の要素が滑らかに変形・移動するアニメーションを実現できます。
基本的な使い方
たとえば、ブログ記事の一覧ページと詳細ページで、同じ画像を使っている場合を考えましょう。
一覧ページ(src/pages/blog/index.astro
)では、以下のように記述します。
astro<a href="/blog/my-first-post">
<img
src="/images/post-1.jpg"
alt="記事のサムネイル"
transition:name="post-image-1"
/>
<h2>私の最初の投稿</h2>
</a>
詳細ページ(src/pages/blog/my-first-post.astro
)でも、同じ transition:name
を指定します。
astro<article>
<img
src="/images/post-1.jpg"
alt="記事のメイン画像"
transition:name="post-image-1"
/>
<h1>私の最初の投稿</h1>
<div class="content">
<!-- 記事本文 -->
</div>
</article>
このように同じ transition:name
を指定することで、一覧ページの小さいサムネイル画像が、詳細ページの大きなメイン画像へと滑らかに変形・移動します。
動的な要素への適用
ブログ記事が複数ある場合、動的に transition:name
を生成することもできます。
astro---
// src/pages/blog/index.astro
const posts = await fetchPosts(); // 記事データを取得
---
<div class="post-grid">
{posts.map((post) => (
<a href={`/blog/${post.slug}`}>
<img
src={post.image}
alt={post.title}
transition:name={`post-image-${post.id}`}
/>
<h2>{post.title}</h2>
</a>
))}
</div>
各記事の ID を使って一意な transition:name
を生成することで、どの記事をクリックしても適切なアニメーションが適用されます。
transition ディレクティブで状態を保持
transition:persist
ディレクティブを使うと、ページ遷移後も特定の要素の状態を維持できます。これは、動画プレーヤーや音楽プレーヤーなど、ページをまたいで動作を継続させたい要素に有効です。
動画プレーヤーの状態を保持
以下は、ページ遷移後も動画の再生を継続する例です。
astro---
// src/components/VideoPlayer.astro
---
<div class="video-container" transition:persist>
<video
id="main-video"
src="/videos/intro.mp4"
controls
autoplay
>
ご利用のブラウザは動画タグをサポートしていません。
</video>
</div>
<style>
.video-container {
/* スタイリング */
width: 100%;
max-width: 800px;
margin: 0 auto;
}
</style>
transition:persist
を指定した要素は、ページ遷移後も DOM から削除されず、そのまま保持されます。そのため、動画の再生位置やプレーヤーの状態がリセットされません。
オーディオプレーヤーの実装例
同様に、サイト全体で共通のバックグラウンドミュージックを再生する場合も、以下のように実装できます。
astro---
// src/components/AudioPlayer.astro
---
<audio
transition:persist
id="bg-music"
src="/audio/background.mp3"
loop
autoplay
>
ご利用のブラウザはオーディオタグをサポートしていません。
</audio>
この設定により、ユーザーがページを移動しても音楽が途切れることなく再生され続けます。
カスタムアニメーションの作成
ビルトインアニメーションだけでは物足りない場合、CSS を使って独自のアニメーションを定義できます。
カスタムアニメーションの定義
まず、グローバル CSS ファイルにキーフレームアニメーションを定義します。
css/* src/styles/transitions.css */
/* スライドアップアニメーション */
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* スライドダウンアニメーション(退出時) */
@keyframes slideDown {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(30px);
}
}
次に、::view-transition
疑似要素を使ってアニメーションを適用します。
css/* 新しいページの要素に適用 */
::view-transition-new(main) {
animation: slideUp 0.4s ease-out;
}
/* 古いページの要素に適用 */
::view-transition-old(main) {
animation: slideDown 0.4s ease-in;
}
レイアウトファイルで、この CSS を読み込みます。
astro---
// src/layouts/BaseLayout.astro
import { ClientRouter } from 'astro:transitions';
import '../styles/transitions.css';
---
<html lang="ja">
<head>
<ClientRouter />
</head>
<body>
<main transition:name="main">
<slot />
</main>
</body>
</html>
これにより、メインコンテンツがスライドアップ・スライドダウンのアニメーションで遷移します。
イージング関数のカスタマイズ
アニメーションの雰囲気を変えるには、イージング関数を調整します。
css/* src/styles/transitions.css */
/* バウンス効果のあるアニメーション */
::view-transition-new(main) {
animation: slideUp 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* スムーズな減速 */
::view-transition-old(main) {
animation: slideDown 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
イージング関数を工夫することで、ブランドイメージに合った独自のアニメーション表現が可能になります。
フォールバック戦略の実装
View Transitions API に対応していないブラウザでも、適切なフォールバック処理を実装することで、すべてのユーザーに快適な体験を提供できます。
フォールバックオプションの設定
<ClientRouter />
コンポーネントには、fallback
プロパティが用意されています。
astro---
// src/layouts/BaseLayout.astro
import { ClientRouter } from 'astro:transitions';
---
<html lang="ja">
<head>
<!-- フォールバック設定 -->
<ClientRouter fallback="swap" />
</head>
<body>
<slot />
</body>
</html>
fallback
プロパティには、以下のオプションがあります。
# | オプション | 動作 | 推奨ケース |
---|---|---|---|
1 | animate | CSS によるシンプルなアニメーション | デフォルト設定 |
2 | swap | 即座にページを切り替え | パフォーマンス重視 |
3 | none | ブラウザのデフォルト動作 | トラブルシューティング時 |
JavaScript によるブラウザ判定
より細かい制御が必要な場合、JavaScript でブラウザの対応状況を判定できます。
typescript// src/scripts/check-support.ts
/**
* View Transitions API のサポート状況をチェック
*/
export function supportsViewTransitions(): boolean {
return (
typeof document !== 'undefined' &&
'startViewTransition' in document
);
}
// 使用例
if (supportsViewTransitions()) {
console.log(
'View Transitions API がサポートされています'
);
} else {
console.log('フォールバック処理を使用します');
}
このヘルパー関数を使うことで、条件分岐や機能の段階的な提供が可能になります。
アクセシビリティへの配慮
View Transitions を実装する際は、アクセシビリティにも十分な配慮が必要です。
prefers-reduced-motion への対応
アニメーションに敏感なユーザーや、視覚障害のあるユーザーのために、prefers-reduced-motion
メディアクエリに対応します。
css/* src/styles/transitions.css */
/* デフォルトのアニメーション */
::view-transition-new(root) {
animation: fade-in 0.4s ease-out;
}
/* アニメーションを減らす設定の場合 */
@media (prefers-reduced-motion: reduce) {
::view-transition-new(root),
::view-transition-old(root) {
animation-duration: 0.01s !important;
animation-iteration-count: 1 !important;
}
}
この設定により、ユーザーの OS やブラウザで「アニメーションを減らす」設定が有効になっている場合、アニメーションがほぼ瞬時に完了します。
スクリーンリーダーへの対応
Astro の View Transitions は、自動的にルート変更をスクリーンリーダーに通知します。追加の設定は不要ですが、必要に応じて以下のような aria-live
領域を設定できます。
astro---
// src/components/RouteAnnouncer.astro
---
<div
class="sr-only"
aria-live="polite"
aria-atomic="true"
id="route-announcer"
>
<!-- ページ遷移時にここに通知が表示されます -->
</div>
<style>
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
</style>
この要素は視覚的には非表示ですが、スクリーンリーダーには読み上げられます。
具体例
ここでは、実際のプロジェクトで使える具体的な実装例を紹介します。コードをコピーして、そのまま使えるように詳しく解説していきます。
ブログサイトでの実装例
ブログサイトは、View Transitions を活用するのに最適なユースケースです。記事一覧から詳細ページへの遷移を滑らかにすることで、読者の体験が大きく向上します。
プロジェクト構成
まず、基本的なディレクトリ構成を確認しましょう。
csssrc/
├── layouts/
│ └── BaseLayout.astro
├── pages/
│ ├── index.astro
│ ├── blog/
│ │ ├── index.astro
│ │ └── [slug].astro
├── components/
│ └── BlogCard.astro
└── styles/
└── global.css
共通レイアウトの作成
すべてのページで使用する基本レイアウトを作成します。
astro---
// src/layouts/BaseLayout.astro
import { ClientRouter } from 'astro:transitions';
interface Props {
title: string;
description?: string;
}
const { title, description } = Astro.props;
---
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<meta name="description" content={description} />
<title>{title}</title>
<!-- View Transitions を有効化 -->
<ClientRouter />
<link rel="stylesheet" href="/styles/global.css" />
</head>
<body>
<!-- ヘッダーは常に表示 -->
<header transition:persist>
<nav>
<a href="/">Home</a>
<a href="/blog">Blog</a>
<a href="/about">About</a>
</nav>
</header>
<!-- メインコンテンツ -->
<main transition:animate="slide">
<slot />
</main>
<!-- フッターも常に表示 -->
<footer transition:persist>
<p>© 2025 My Blog</p>
</footer>
</body>
</html>
このレイアウトでは、ヘッダーとフッターに transition:persist
を指定しているため、ページ遷移時も固定表示されます。メインコンテンツにはスライドアニメーションを適用しています。
ブログカードコンポーネント
記事一覧で使用するカードコンポーネントを作成します。
astro---
// src/components/BlogCard.astro
interface Props {
id: number;
slug: string;
title: string;
image: string;
excerpt: string;
date: string;
}
const { id, slug, title, image, excerpt, date } = Astro.props;
---
<article class="blog-card">
<a href={`/blog/${slug}`}>
<!-- 画像に一意の transition:name を設定 -->
<img
src={image}
alt={title}
transition:name={`blog-image-${id}`}
class="card-image"
/>
<!-- タイトルにも transition:name を設定 -->
<h2 transition:name={`blog-title-${id}`}>
{title}
</h2>
<p class="excerpt">{excerpt}</p>
<time datetime={date}>{date}</time>
</a>
</article>
<style>
.blog-card {
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
transition: transform 0.2s;
}
.blog-card:hover {
transform: translateY(-4px);
}
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.excerpt {
color: #666;
line-height: 1.6;
}
</style>
画像とタイトルに一意な transition:name
を設定することで、一覧ページと詳細ページ間でスムーズな変形アニメーションが適用されます。
ブログ一覧ページ
記事一覧を表示するページを作成します。
astro---
// src/pages/blog/index.astro
import BaseLayout from '../../layouts/BaseLayout.astro';
import BlogCard from '../../components/BlogCard.astro';
// ブログ記事データを取得
const posts = [
{
id: 1,
slug: 'getting-started-with-astro',
title: 'Astro を始めよう',
image: '/images/post-1.jpg',
excerpt: 'Astro の基本的な使い方を解説します。',
date: '2025-01-15'
},
{
id: 2,
slug: 'view-transitions-guide',
title: 'View Transitions 完全ガイド',
image: '/images/post-2.jpg',
excerpt: 'View Transitions の実装方法を詳しく紹介します。',
date: '2025-02-20'
},
// その他の記事...
];
---
<BaseLayout
title="Blog - My Site"
description="技術ブログの記事一覧"
>
<div class="blog-container">
<h1>Blog</h1>
<div class="blog-grid">
{posts.map((post) => (
<BlogCard {...post} />
))}
</div>
</div>
</BaseLayout>
<style>
.blog-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.blog-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
</style>
グリッドレイアウトで記事カードを並べています。レスポンシブ対応も考慮した実装です。
ブログ詳細ページ
個別の記事ページを作成します。
astro---
// src/pages/blog/[slug].astro
import BaseLayout from '../../layouts/BaseLayout.astro';
// 実際にはデータベースや CMS から取得
const { slug } = Astro.params;
const post = {
id: 1,
title: 'Astro を始めよう',
image: '/images/post-1.jpg',
content: '<p>Astro は素晴らしいフレームワークです...</p>',
date: '2025-01-15',
author: '山田太郎'
};
---
<BaseLayout
title={post.title}
description={post.title}
>
<article class="post-detail">
<!-- 一覧ページと同じ transition:name を指定 -->
<img
src={post.image}
alt={post.title}
transition:name={`blog-image-${post.id}`}
class="hero-image"
/>
<div class="post-header">
<h1 transition:name={`blog-title-${post.id}`}>
{post.title}
</h1>
<div class="post-meta">
<time datetime={post.date}>{post.date}</time>
<span>by {post.author}</span>
</div>
</div>
<div class="post-content" set:html={post.content} />
</article>
</BaseLayout>
<style>
.post-detail {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.hero-image {
width: 100%;
height: 400px;
object-fit: cover;
border-radius: 8px;
}
.post-header {
margin: 2rem 0;
}
.post-meta {
display: flex;
gap: 1rem;
color: #666;
font-size: 0.9rem;
margin-top: 0.5rem;
}
.post-content {
line-height: 1.8;
font-size: 1.1rem;
}
</style>
一覧ページと同じ transition:name
を使用することで、カードの画像が詳細ページのヒーロー画像へと滑らかに拡大・移動します。
以下の図は、記事一覧から詳細ページへの遷移フローです。
mermaidsequenceDiagram
participant User as ユーザー
participant List as 一覧ページ
participant VT as View Transitions
participant Detail as 詳細ページ
User->>List: 記事カードをクリック
List->>VT: transition:name で<br/>要素を識別
VT->>VT: 画像とタイトルの<br/>位置・サイズを記録
VT->>Detail: 新しいページを<br/>読み込み
Detail->>VT: 同じ transition:name<br/>の要素を提供
VT->>VT: アニメーション実行<br/>(拡大・移動)
VT->>User: スムーズに詳細ページを表示
この仕組みにより、記事カードが詳細ページへと自然に変形します。
EC サイトでの商品詳細ページ遷移
EC サイトでは、商品一覧から詳細ページへの遷移を滑らかにすることで、ユーザーの購買体験を向上させることができます。
商品カードコンポーネント
astro---
// src/components/ProductCard.astro
interface Props {
id: number;
slug: string;
name: string;
image: string;
price: number;
rating: number;
}
const { id, slug, name, image, price, rating } = Astro.props;
---
<div class="product-card">
<a href={`/products/${slug}`}>
<!-- 商品画像 -->
<div class="image-wrapper">
<img
src={image}
alt={name}
transition:name={`product-${id}`}
class="product-image"
/>
</div>
<!-- 商品情報 -->
<div class="product-info">
<h3 class="product-name">{name}</h3>
<div class="rating">
{'★'.repeat(rating)}{'☆'.repeat(5 - rating)}
</div>
<p class="price">¥{price.toLocaleString()}</p>
</div>
</a>
</div>
<style>
.product-card {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: box-shadow 0.2s;
}
.product-card:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}
.image-wrapper {
aspect-ratio: 1;
overflow: hidden;
background: #f5f5f5;
}
.product-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.product-info {
padding: 1rem;
}
.price {
font-size: 1.25rem;
font-weight: bold;
color: #e63946;
}
</style>
商品画像に transition:name
を設定し、詳細ページへの遷移時に拡大アニメーションが適用されるようにしています。
商品詳細ページ
astro---
// src/pages/products/[slug].astro
import BaseLayout from '../../layouts/BaseLayout.astro';
const { slug } = Astro.params;
const product = {
id: 1,
name: 'ワイヤレスイヤホン Pro',
image: '/images/product-1.jpg',
price: 19800,
rating: 4,
description: '高音質なワイヤレスイヤホンです。',
features: [
'アクティブノイズキャンセリング',
'最大30時間再生',
'IPX4 防水'
]
};
---
<BaseLayout title={product.name}>
<div class="product-detail">
<div class="product-layout">
<!-- 商品画像(一覧ページと同じ transition:name) -->
<div class="image-section">
<img
src={product.image}
alt={product.name}
transition:name={`product-${product.id}`}
class="main-image"
/>
</div>
<!-- 商品情報 -->
<div class="info-section">
<h1>{product.name}</h1>
<div class="rating">
{'★'.repeat(product.rating)}{'☆'.repeat(5 - product.rating)}
</div>
<p class="price">¥{product.price.toLocaleString()}</p>
<p class="description">{product.description}</p>
<ul class="features">
{product.features.map((feature) => (
<li>{feature}</li>
))}
</ul>
<button class="buy-button">カートに追加</button>
</div>
</div>
</div>
</BaseLayout>
<style>
.product-detail {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.product-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
align-items: start;
}
.main-image {
width: 100%;
border-radius: 8px;
}
.price {
font-size: 2rem;
font-weight: bold;
color: #e63946;
margin: 1rem 0;
}
.buy-button {
background: #e63946;
color: white;
border: none;
padding: 1rem 2rem;
font-size: 1.1rem;
border-radius: 8px;
cursor: pointer;
width: 100%;
margin-top: 2rem;
}
@media (max-width: 768px) {
.product-layout {
grid-template-columns: 1fr;
}
}
</style>
一覧ページの小さい商品画像が、詳細ページの大きい画像へとスムーズに拡大するアニメーションが実現できました。
ポートフォリオサイトでの作品詳細表示
デザイナーや開発者のポートフォリオサイトでも、View Transitions は効果的です。
作品グリッドコンポーネント
astro---
// src/components/WorkGrid.astro
const works = [
{
id: 1,
slug: 'website-redesign',
title: 'Web サイトリニューアル',
thumbnail: '/images/work-1.jpg',
category: 'Web Design'
},
{
id: 2,
slug: 'mobile-app',
title: 'モバイルアプリ UI',
thumbnail: '/images/work-2.jpg',
category: 'UI/UX'
},
// その他の作品...
];
---
<div class="work-grid">
{works.map((work) => (
<a href={`/works/${work.slug}`} class="work-item">
<img
src={work.thumbnail}
alt={work.title}
transition:name={`work-image-${work.id}`}
class="work-thumbnail"
/>
<div class="work-overlay">
<span class="category">{work.category}</span>
<h3 transition:name={`work-title-${work.id}`}>
{work.title}
</h3>
</div>
</a>
))}
</div>
<style>
.work-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.work-item {
position: relative;
aspect-ratio: 4 / 3;
overflow: hidden;
border-radius: 8px;
display: block;
}
.work-thumbnail {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
.work-item:hover .work-thumbnail {
transform: scale(1.05);
}
.work-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 1.5rem;
background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
color: white;
}
.category {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.1em;
}
</style>
作品のサムネイル画像とタイトルに transition:name
を設定しています。
作品詳細ページ
astro---
// src/pages/works/[slug].astro
import BaseLayout from '../../layouts/BaseLayout.astro';
const { slug } = Astro.params;
const work = {
id: 1,
title: 'Web サイトリニューアル',
thumbnail: '/images/work-1.jpg',
category: 'Web Design',
client: '株式会社サンプル',
year: 2025,
description: 'クライアントの要望に応じて、Web サイトを全面的にリニューアルしました。',
images: [
'/images/work-1-detail-1.jpg',
'/images/work-1-detail-2.jpg',
'/images/work-1-detail-3.jpg'
]
};
---
<BaseLayout title={work.title}>
<article class="work-detail">
<!-- メインビジュアル -->
<div class="hero">
<img
src={work.thumbnail}
alt={work.title}
transition:name={`work-image-${work.id}`}
class="hero-image"
/>
</div>
<!-- 作品情報 -->
<div class="work-content">
<header class="work-header">
<span class="category">{work.category}</span>
<h1 transition:name={`work-title-${work.id}`}>
{work.title}
</h1>
</header>
<div class="work-meta">
<div class="meta-item">
<strong>クライアント:</strong> {work.client}
</div>
<div class="meta-item">
<strong>年度:</strong> {work.year}
</div>
</div>
<p class="description">{work.description}</p>
<!-- 追加の画像 -->
<div class="image-gallery">
{work.images.map((img) => (
<img src={img} alt="作品詳細" />
))}
</div>
</div>
</article>
</BaseLayout>
<style>
.hero {
width: 100%;
height: 60vh;
overflow: hidden;
}
.hero-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.work-content {
max-width: 800px;
margin: 0 auto;
padding: 3rem 2rem;
}
.work-meta {
display: flex;
gap: 2rem;
margin: 1.5rem 0;
padding: 1.5rem;
background: #f5f5f5;
border-radius: 8px;
}
.image-gallery {
display: grid;
gap: 2rem;
margin-top: 3rem;
}
.image-gallery img {
width: 100%;
border-radius: 8px;
}
</style>
この実装により、作品グリッドのサムネイルが詳細ページのヒーロー画像へと美しく拡大します。
パフォーマンス最適化のテクニック
View Transitions を使用する際、パフォーマンスにも注意を払う必要があります。
画像の遅延読み込み
大量の画像を扱う場合、遅延読み込みを実装します。
astro---
// src/components/LazyImage.astro
interface Props {
src: string;
alt: string;
transitionName?: string;
}
const { src, alt, transitionName } = Astro.props;
---
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
transition:name={transitionName}
class="lazy-image"
/>
<style>
.lazy-image {
background: #f0f0f0;
}
</style>
loading="lazy"
属性により、画像は画面に表示される直前に読み込まれます。
プリフェッチの活用
ユーザーがリンクにホバーした際に、次のページを事前に読み込むことで、遷移をさらに高速化できます。
astro---
// src/layouts/BaseLayout.astro
import { ClientRouter } from 'astro:transitions';
---
<html lang="ja">
<head>
<!-- プリフェッチを有効化 -->
<ClientRouter prefetch />
</head>
<body>
<slot />
</body>
</html>
prefetch
属性を追加するだけで、リンクホバー時の自動プリフェッチが有効になります。
以下の図は、プリフェッチの動作フローです。
mermaidflowchart TD
hover["ユーザーがリンクに<br/>ホバー"]
hover --> fetch["バックグラウンドで<br/>次ページを取得"]
fetch --> cache["ブラウザキャッシュに保存"]
cache --> waitNode["ユーザーのクリックを待機"]
waitNode --> clickEvt["クリック"]
clickEvt --> instant["キャッシュから即座に表示"]
instant --> transition["View Transition<br/>アニメーション実行"]
プリフェッチにより、クリック時の待機時間がほぼゼロになります。
まとめ
Astro の View Transitions は、シンプルな設定で SPA 並みの滑らかなページ遷移を実現できる強力な機能です。この記事で紹介した内容をまとめます。
View Transitions の主なメリット
View Transitions を導入することで、以下のメリットが得られます。
# | メリット | 説明 |
---|---|---|
1 | 簡単な導入 | <ClientRouter /> を追加するだけで有効化 |
2 | 軽量 | JavaScript フレームワークに比べて圧倒的に軽量 |
3 | 柔軟性 | ビルトインとカスタムアニメーションの両方に対応 |
4 | ブラウザ互換性 | フォールバック機能により幅広いブラウザで動作 |
5 | アクセシビリティ | prefers-reduced-motion など標準対応 |
実装のポイント
実装時には、以下のポイントを押さえておきましょう。
基本設定: 共通レイアウトの <head>
に <ClientRouter />
を配置するだけで、サイト全体に View Transitions が適用されます。
要素の対応付け: transition:name
ディレクティブを使って、異なるページ間で同じ要素を対応付けることで、要素が滑らかに変形・移動するアニメーションを実現できます。
状態の保持: transition:persist
を使えば、動画やオーディオプレーヤーなどの状態をページ遷移後も維持できます。
カスタマイズ: CSS の ::view-transition
疑似要素を使って、独自のアニメーションを定義できます。
フォールバック: 未対応ブラウザ向けに適切なフォールバック戦略を用意することで、すべてのユーザーに快適な体験を提供できます。
パフォーマンスとアクセシビリティ
View Transitions を実装する際は、パフォーマンスとアクセシビリティにも配慮が必要です。
画像の遅延読み込みやプリフェッチを活用することで、さらに高速な遷移が実現できます。また、prefers-reduced-motion
メディアクエリに対応することで、アニメーションに敏感なユーザーにも配慮した実装が可能です。
適用シーンの多様性
ブログ、EC サイト、ポートフォリオなど、さまざまな種類の Web サイトで View Transitions は効果を発揮します。特に、画像を多用するサイトや、ページ間で要素の連続性を保ちたいサイトには最適です。
従来の静的サイトの高速性と、SPA の滑らかな UX を両立できる Astro の View Transitions は、これからの Web 開発において重要な技術となるでしょう。
ぜひこの記事を参考に、あなたのプロジェクトでも View Transitions を導入して、ユーザー体験を向上させてみてください。
関連リンク
- article
Astro の View Transitions 徹底解説:SPA 並みの滑らかなページ遷移を実装するコツ
- article
Astro × Starlight カスタマイズ大全:ドキュメントサイトをブランド化する設計術
- article
Astro で OG/構造化データ最適化:SEO を底上げする設定大全
- article
【実測検証】Remix vs Next.js vs Astro:TTFB/LCP/開発体験を総合比較
- article
Astro × Cloudflare Workers/Pages:エッジ配信で超高速なサイトを構築
- article
Astro × SCSS/PostCSS/CSS Modules:3 つのスタイリング戦略を比較
- article
Convex で実践する CQRS/イベントソーシング:履歴・再生・集約の設計ガイド
- article
LangChain と LlamaIndex の設計比較:API 哲学・RAG 構成・運用コストを検証
- article
Jotai が再レンダリング地獄に?依存グラフの暴走を止める診断手順
- article
成果が出る GPT-5-Codex 導入ロードマップ:評価指標(ROI/品質/速度)と失敗しない運用フロー
- article
Jest expect.extend チートシート:実務で使えるカスタムマッチャー 40 連発
- article
Astro の View Transitions 徹底解説:SPA 並みの滑らかなページ遷移を実装するコツ
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来