未分類
コーディング規約ビューアページのバグ分析と修正計画
報告されたバグ
1. キーボードショートカットが動作しない
- 症状:
Ctrl+](次へ) とCtrl+[(前へ) のキーボードショートカットが動作しない - 影響範囲:
/coding-standards/viewer#rule-2-1などの全体ビューページ
2. スクロールができない
- 症状: ページのスクロールが正常に動作しない
- 影響範囲: 全体ビューページ
コード分析結果
ファイル: apps/web/app/pages/coding-standards/viewer/index.vue
キーボードショートカットの実装状況
定義箇所 (Line 248-256):
function handleKeydown(e) {
if (e.ctrlKey && e.key === '[') {
e.preventDefault();
goToPrevStep();
} else if (e.ctrlKey && e.key === ']') {
e.preventDefault();
goToNextStep();
}
}
イベントリスナー登録 (Line 370):
window.addEventListener('keydown', handleKeydown);
イベントリスナー解除 (Line 402):
window.removeEventListener('keydown', handleKeydown);
実装状態: ✅ コード上は正しく実装されている
スクロール機能の実装状況
スクロール可能な高さの設定 (Line 555):
.step {
min-height: 150vh;
position: relative;
}
右側コードエリアの固定 (Line 595-603):
.viz {
position: sticky;
top: 8vh;
height: 84vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
実装状態: ✅ コード上は正しく実装されている
問題の原因仮説
1. ClientOnlyコンポーネントの影響
Line 2-90: 全体が<ClientOnly>でラップされている
<template>
<ClientOnly>
<div class="breadcrumb-nav">
...
</div>
...
</ClientOnly>
</template>
仮説:
ClientOnlyコンポーネントによるレンダリングのタイミングの問題- SSR(サーバーサイドレンダリング)とCSR(クライアントサイドレンダリング)の切り替え時にイベントリスナーが正しく登録されない可能性
- ハイドレーションミスマッチが発生している可能性
2. z-indexの定義不足
Line 567-577: 11個のステップまでしかz-indexが定義されていない
.step[data-step="0"] .step-content { z-index: 1; }
.step[data-step="1"] .step-content { z-index: 2; }
...
.step[data-step="10"] .step-content { z-index: 11; }
問題点:
- JSONデータには100以上のルールが存在する
- 12番目以降のステップにはz-indexが設定されない
- ステップ要素が重なって表示される可能性がある
- これによりクリックイベントやフォーカスが正しく機能しない可能性
3. イベントリスナーの競合
可能性のある競合:
- IntersectionObserver (Line 349-364)
- Scroll イベント (Line 368)
- Keydown イベント (Line 370)
これらのイベントが同時に動作することで、競合が発生している可能性がある。
4. ハッシュナビゲーションの副作用
Line 373-392: ハッシュによるナビゲーション処理
if (window.location.hash) {
const hash = window.location.hash.slice(1);
const targetElement = document.getElementById(hash);
if (targetElement) {
// ...スクロール処理
}
}
問題の可能性:
- ハッシュナビゲーション後にフォーカスが失われる
- キーボードイベントが要素ではなくwindowに登録されているため、フォーカスの影響を受けるべきではないが、何らかの副作用がある可能性
修正計画
Phase 1: 緊急修正(即座に実施)
1-1. z-index問題の解決
方法A: 動的z-index生成
<style>
/* 静的なz-index定義を削除 */
</style>
<script setup>
// 動的にz-indexを設定
const getStepZIndex = (index) => index + 1;
</script>
<template>
<div class="step-content" :style="{ zIndex: getStepZIndex(index) }">
...
</div>
</template>
方法B: CSSカスタムプロパティを使用
<template>
<div class="step-content" :style="{ '--step-index': index }">
...
</div>
</template>
<style>
.step-content {
z-index: calc(var(--step-index) + 1);
}
</style>
推奨: 方法B(CSSカスタムプロパティ)- よりパフォーマンスが良い
1-2. イベントリスナーの確実な登録
onMounted(async () => {
await loadSteps();
// 他の初期化処理...
await nextTick();
// イベントリスナーを最後に登録(確実に要素がレンダリングされた後)
await nextTick(); // 2回目のnextTickで確実に
window.addEventListener('keydown', handleKeydown);
console.log('Keydown listener registered'); // デバッグ用
});
Phase 2: 根本原因の調査(Chrome DevToolsで確認)
2-1. イベントリスナーの確認
Chrome DevToolsで以下を確認:
- Elements > Event Listeners タブで
keydownイベントが登録されているか - Console で以下を実行:
getEventListeners(window).keydown
2-2. DOM要素の状態確認
.step要素のz-indexが正しく適用されているか- スクロール可能な高さが十分にあるか
overflowプロパティが正しく設定されているか
2-3. ハイドレーションエラーの確認
Console で以下のエラーをチェック:
- "Hydration node mismatch"
- "Hydration completed but contains mismatches"
Phase 3: 構造的な改善(長期対応)
3-1. ClientOnlyの削除検討
<template>
<!-- ClientOnlyを削除 -->
<div class="breadcrumb-nav">
...
</div>
...
</template>
<script setup>
// onMountedの処理を調整
onMounted(() => {
// ブラウザ環境でのみ実行される処理
if (process.client) {
// ...
}
});
</script>
3-2. スクロール処理の最適化
// Passive scrollイベントの活用
window.addEventListener('scroll', updateProgress, {
passive: true,
capture: false
});
// IntersectionObserverのオプション最適化
io = new IntersectionObserver(
(entries) => {
// ...
},
{
root: null,
threshold: [0, 0.25, 0.5, 0.75, 1], // より細かいしきい値
rootMargin: '0px'
}
);
3-3. Composableへの分離
// composables/useCodingStandardsViewer.ts
export function useCodingStandardsViewer() {
const steps = ref([]);
const activeIndex = ref(0);
function handleKeydown(e: KeyboardEvent) {
// ...
}
onMounted(() => {
window.addEventListener('keydown', handleKeydown);
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', handleKeydown);
});
return {
steps,
activeIndex,
// ...
};
}
実装優先順位
最優先(今すぐ実施)
- z-indexの動的生成 - 方法Bを採用
- イベントリスナー登録の確実化 - nextTick を2回使用
- デバッグログの追加 - 問題箇所の特定
次に実施(動作確認後)
- Chrome DevToolsでの詳細調査 - イベントリスナー、DOM状態、エラーの確認
- スクロール処理の最適化 - passiveオプション、IntersectionObserverの調整
長期的に検討
- ClientOnlyの削除 - SSR/CSRの切り替えを適切に処理
- Composableへの分離 - コードの可読性と保守性向上
期待される結果
修正後の動作
- キーボードショートカット:
Ctrl+]: 次のルールにスムーズに移動Ctrl+[: 前のルールにスムーズに移動
- スクロール:
- マウスホイールでのスクロールが正常に動作
- タッチパッドでのスクロールが正常に動作
- スクロールバーが表示され操作可能
- 全体的な改善:
- すべてのルール(100以上)で正しくz-indexが適用される
- ハイドレーションエラーが発生しない
- パフォーマンスが向上する
次のステップ
- Phase 1の修正を実装
- 動作確認(ローカル環境)
- Phase 2のデバッグ実施
- 必要に応じてPhase 3の改善を実施
- 本ドキュメントを更新(修正結果を記録)
関連ファイル
apps/web/app/pages/coding-standards/viewer/index.vue- ビューアページ(全体)apps/web/app/pages/coding-standards/viewer/[ruleId].vue- 個別ルールページapps/web/app/pages/coding-standards/index.vue- 一覧ページapps/web/public/data/coding-standards.json- ルールデータ