教育コンテンツUIを抜本改善 - Theater型・Scrollytelling・ミラーカラムレイアウト
背景
教育コンテンツ用のSlideViewerが幅860pxに押し込められていて、テキストと画像が窮屈に並んでいた。教科書のページをそのまま画面に貼り付けたような見た目で、スクロールしても「どこを読んでいるか」を見失う。レイアウトを根本から見直すことにした。
やったこと
3パターンのレイアウトを実装して、実際に教材コンテンツを流し込んで比較した。
1. Theater型(採用)
テキストを上、画像を下に配置するスライドデッキ風UI。左右の矢印オーバーレイでスライドを送る。
<template>
<div class="theater-viewer">
<!-- キャプション: 3行分の高さを固定して画像のズレを防止 -->
<div class="caption" :style="{ minHeight: '4.5em' }">
{{ currentSlide.text }}
</div>
<!-- 画像エリア + 左右矢印オーバーレイ -->
<div class="image-area">
<button class="nav-arrow left" @click="prev">‹</button>
<img :src="currentSlide.image" />
<button class="nav-arrow right" @click="next">›</button>
</div>
<!-- ページインジケーター(1枚スライドでは非表示) -->
<div v-if="slides.length > 1" class="indicator">
{{ currentIndex + 1 }} / {{ slides.length }}
</div>
</div>
</template>
設計で気をつけた点:
- キャプション高さの固定: テキスト量がスライドごとに異なるため、高さを
minHeight: '4.5em'(3行分)で固定した。これを入れないと、テキストの行数が変わるたびに画像がガクガク上下に動く - 白ベースのデザイン: 教材コンテンツなので、背景白・影なし・余白広めにして、教科書を読んでいる感覚に近づけた
- 1枚スライドの処理: スライドが1枚しかない場合、ページ番号・矢印・コントロール一式を
v-ifで非表示にする。1/1と表示されても意味がない
2. Scrollytelling型(実装して比較用に残す)
左にテキスト、右に画像の2カラム構成。スクロールに連動して右側の画像が切り替わる。
// IntersectionObserverで「今どのテキストカードが画面中央にいるか」を検知
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
activeCard.value = Number(entry.target.dataset.index)
}
})
},
{ rootMargin: '-40% 0px -40% 0px' }
)
2つのバグを潰した:
- activeCardが0→2に飛ぶ問題:
rootMarginの設定が広すぎて、複数のカードが同時にisIntersectingになっていた。-40% 0px -40% 0pxに絞ることで、画面中央付近のカードだけを検知するようにした - セクション末尾の余白問題: 最後のテキストカードをスクロールしきると、右の画像が宙に浮く。最後のカードに
margin-bottomを追加して、画像がビューポート内に留まるよう調整した
レスポンシブ対応
モバイル幅(768px以下)では2カラムが収まらないため、インターリーブ表示に切り替えた。テキスト→画像→テキスト→画像の順に1カラムで積む。IntersectionObserverはモバイルでは無効にして、単純なスクロールに戻す。
3. Stepper型(作成後に不採用で削除)
ステップバーを上部に置いて、クリックで切り替える方式。作ってみたが、教材コンテンツの「流れ」を表現するには向いていなかった。ステップ数が多いと横幅を食い過ぎるのと、前後の文脈が見えなくなる。実装後すぐ削除した。
Theater型の採用とコンポーネント化
3パターンを並べて教材を流し込んだ結果、Theater型を採用した。
- テキストと画像の対応関係が一番わかりやすい
- 矢印操作が直感的で、教材を「めくる」感覚に近い
- キャプション固定により、画像位置がぶれない
コンポーネント TheaterViewer.vue として切り出し、ページ本体を書き換えた。
743行 → 140行に削減。3パターンの比較用コードとインラインのスタイル定義を丸ごと消して、TheaterViewerにpropsでスライドデータを渡すだけの構成にした。
<!-- ページ側: TheaterViewerを呼ぶだけ -->
<TheaterViewer :slides="categorySlides" />
ミラーカラムレイアウト
大カテゴリ・中カテゴリ・小カテゴリの3カラム構造とTheater型を組み合わせた。左カラムでカテゴリを選択すると、中央カラムにサブカテゴリが表示され、右カラムにTheaterViewerが展開する。
教科書の「目次→章→節→本文」という階層を画面上で再現する構造。3カラムの幅比率は 1:1.5:3 にして、右のTheaterViewerに十分な表示領域を確保した。
振り返り
3パターン作って比較したことで、「なぜTheater型がいいのか」を自分の手で確認できた。Scrollytellingは見た目のインパクトがあるが、IntersectionObserverのチューニングに時間を取られる。教材コンテンツでは「前後のスライドに自分のペースで移動できる」ことのほうが重要で、Theater型の矢印操作がそれに合っていた。
743行が140行に縮んだとき、画面を二度見した。3パターン分の実験コードが全部消えて、TheaterViewerのpropsだけが残っている。コンポーネント化の効果を数字で体感した瞬間だった。
IntersectionObserverの rootMargin 調整は地味だが学びがあった。「-40% 0px -40% 0px」で画面中央20%だけを検知領域にするテクニックは、スクロール連動UIでまた使う場面がありそうだ。