はじめに
多くのWebサービスでは、ユーザーが編集中のページから離脱しようとする際に確認モーダルを表示します。この機能は非常に重要なUXの一部ですが、実装する際に思わぬ課題に直面することがありましたので、記事を書こうと思います。
上の画像のようなモーダルを見たことはありませんか?
編集フォームの入力中に、間違えてリロードしてしまった時に出てくる時があります。
このようなモーダルですが、自分/自社で作成してしまおうという場合があります。
特に、プロダクトのブランドコアとしてカスタムデザインのモーダルを実装している場合、ブラウザのデフォルトの離脱防止モーダルと競合してしまう問題があります。この記事では、その解決方法について詳しく解説します。
問題:ブラウザのデフォルトモーダルとの競合
発生する問題
- カスタムデザインの離脱防止モーダルを実装している
- ページ遷移時に、カスタムモーダルとブラウザのデフォルトモーダルが両方表示される
- 結果として、UXが著しく低下し、ブランドの一貫性が損なわれる
なぜこれが重要か
-
ブランドの一貫性
- カスタムデザインのモーダルは、プロダクトのブランドアイデンティティの一部
- ブラウザのデフォルトモーダルは、そのブランド体験を損なう
-
ユーザー体験
- 2つのモーダルが表示されることで、ユーザーを混乱させる
- 操作の手順が増え、フローが煩雑になる
解決策:ブラウザモーダルの制御
実装例
const historyPushState = useCallback((path: string) => {
// 1. beforeunloadイベントを一時的に無効化
const defaultLeaveEvent = window.onbeforeunload;
window.onbeforeunload = null;
try {
// 2. React Routerのnavigateを使用
navigate(path);
} finally {
// 3. 遷移後にbeforeunloadを復元
window.onbeforeunload = defaultLeaveEvent;
}
}, [navigate]);
解決策の詳細説明
-
beforeunloadイベントの一時的な無効化
- ページ遷移時に一時的にブラウザのデフォルトモーダルを無効化
- 元のイベントハンドラは保存して後で復元
-
制御された遷移の実装
- React Routerの
navigate
を使用して適切な遷移を実現 - ブラウザネイティブの遷移を避けることで、よりコントロールされた挙動を実現
- React Routerの
-
安全な状態管理
-
try-finally
ブロックで確実にイベントハンドラを復元 - エラーが発生しても、アプリケーションの他の部分は正常に動作
-
このアプローチのメリット
-
ブランドの一貫性維持
- カスタムデザインのモーダルのみが表示される
- プロダクトのデザインシステムに沿った体験を提供
-
UXの改善
- シンプルで明確な離脱フロー
- ユーザーの混乱を防止
-
保守性
- クリーンな実装で、将来的な変更にも対応しやすい
- エラーハンドリングが適切に行われている
実装時の注意点
-
イベントハンドラの管理
-
beforeunload
イベントの無効化は必要な時のみ行う - 必ず元の状態に復元する
-
-
ルーティング
- 適切なルーティングライブラリを使用する
- ブラウザネイティブの遷移は避ける
まとめ
プロダクトのブランドコアを守りながら、優れたユーザー体験を提供することは現代のWebサービスにとって重要な課題です。この記事で紹介した方法を使用することで、ブラウザのデフォルトモーダルを適切に制御し、一貫性のあるブランド体験を実現できました。
また、この解決策は単なる技術的な問題解決以上の価値があります。プロダクトのブランドアイデンティティを守り、ユーザー体験を向上させることで、サービスの品質向上に貢献します。
今後のWeb開発において、このようなブランドコアを意識した実装がますます重要になっていくでしょう。今後も精進します。ではまたっ!
補足
beforeunloadイベント
ウェブページが閉じられる直前に発生するイベントです。以下のような場合に発火します:
- ページのリロード
- ブラウザタブを閉じる
- 新しいURLへの遷移
- ブラウザを閉じる
ページ遷移の種類と特徴
location.href による遷移
最も基本的なページ遷移方法で、ページ全体をリロードします。
// - 履歴に新しいエントリーが追加される
// - ブラウザの「戻る」ボタンで前のページに戻れる
// - beforeunloadイベントが発火する
window.location.href = '/new-page';
location.replace による遷移
現在のページを履歴から完全に置き換える遷移方法です。
// - 現在のページを履歴から置き換える
// - ブラウザの「戻る」ボタンで前のページに戻れない
// - beforeunloadイベントが発火する
window.location.replace('/new-page');
history.pushState による遷移
SPAで一般的な、ページリロードなしでの遷移を実現します。
// - 履歴に新しいエントリーを追加
// - ページのリロードは発生しない
// - beforeunloadイベントは発火しない
history.pushState({}, '', '/new-page');
React Router の navigate
React アプリケーションで推奨される、宣言的な遷移方法です。
// - SPAの文脈での遷移
// - デフォルトではページリロードなし
// - アプリケーションの状態を保持したまま遷移
navigate('/new-page');
これらの遷移方法は、それぞれ異なる特性を持っています。SPAでは主にReact RouterのnavigateやhistoryPushStateを使用し、必要に応じてlocation.replaceやlocation.hrefを使い分けることになります。