実務1年目駆け出しエンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(その42)
0. 初めに
駆け出しエンジニアの僕が、ゼロからWebアプリケーション開発に挑戦する様子をお届けしているシリーズです。
ついに、長かったフロントエンド編も今日で最終回です...!!
最後までお付き合いいただきありがとうございます。
今日は、レスポンシブデザインに挑戦したいと思います。
1. ブランチ運用
例によって、developブランチを最新化させて、feature/frontend/responsiveという新規ブランを切って作業しましょう。
2. レスポンシブデザインとは
今日取り扱う、レスポンシブデザインとは何でしょうか?
MDN Web Docsの説明によれば、
レスポンシブウェブデザイン (RWD) は、ユーザビリティを確保しながら、すべての画面サイズと解像度でウェブページをうまく描画するためのウェブデザインの手法です。複数の端末に対応したウェブをデザインする方法です。
ということらしいです。
このシリーズでのこれまでのフロントエンドの実装では、PCで使うことを前提に画面を作ってきました。
しかし、今の世の中ではインターネットを利用するのはPCだけでなく、スマホやタブレットなどもメインとなっています。
そのため、どの画面サイズの端末で見ても見た目が崩れないようなレイアウトを用意することが求められることが多く、そのような対応ができているWebページをレスポンシブ対応と呼んだりします。
理想的には、すべてのページで、PC用、タブレット用、スマホ用とデザインを一つずつ用意します。
しかし...
_人人人人人人人人人_
> 面倒くさい!! <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
じゃあ、今回はどうするのかと言いますと...
タブレットはいったんおいておいて、スマホのサイズにしたときに著しく見た目が悪くなる箇所をリストアップして、その個所についてのみスマホ用のデザインを適用するという方針でいきたいと思います...!!
スマホサイズでデザインが崩れる箇所
- ヘッダーのロゴ
- ヘッダーのタイトル
- パンくずリスト
- 学部一覧
- 研究室一覧
- 研究室詳細
- マイページ
3. ヘッダー修正
3.1 ロゴ
これを、resources/js/Assets/logo/header-mobile.svgのパスで追加します。
また、Header.jsxも修正です。
resources/js/Components/Header.jsx
クリックでコードを見る
import logoPC from '../Assets/logo/header.svg';
import logoMobile from '../Assets/logo/header-mobile.svg';
{/* 左:ロゴ */}
<div className="flex items-center">
<Link href={route('home')} aria-label="トップページへ">
<img src={logoPC} alt="App Logo" className="hidden md:block h-9 w-auto" />
<img src={logoMobile} alt="App Logo" className="md:hidden h-9 w-auto" />
</Link>
</div>
こんな感じに「LabChart」という文字がなくなりました。

ちなみに、どうやってスマホサイズを確認するかというと、検証ツールの「デバイスのツールバーを切り替え」から「サイズ」で例えば「iPhone 12 Pro」とかを選択するとできます。

タイトル
タイトルの文字が大きすぎて2行、3行...となってしまっていたので小さくするように修正します。
resources/js/Components/Header.jsx
クリックでコードを見る
{/* 中央:タイトル */}
<h1 className="absolute left-1/2 -translate-x-1/2 w-[50%] text-center truncate text-sm md:text-xl font-semibold text-black">
{title}
</h1>
4. パンくずリスト修正
現在、スマホサイズだとパンくずリストが長い時は2行になってしまっています。

resources/js/Components/Common/Breadcrumb.jsx
クリックでコードを見る
// スマホ用:現在のページの一つ上の階層を返す
const getPreviousItem = () => {
if (lab) {
if (faculty) return { label: faculty.name, href: route('labs.index', buildParams({ faculty: faculty.id })) };
if (university) return { label: university.name, href: route('faculties.index', buildParams({ university: university.id })) };
if (query) return { label: `「${query}」の検索結果`, href: route('universities.index', { query }) };
}
if (faculty) {
if (university) return { label: university.name, href: route('faculties.index', buildParams({ university: university.id })) };
if (query) return { label: `「${query}」の検索結果`, href: route('universities.index', { query }) };
}
if (university) {
if (query) return { label: `「${query}」の検索結果`, href: route('universities.index', { query }) };
}
return null;
};
const previousItem = getPreviousItem();
return (
<nav className="text-sm text-[#747D8C] mb-4">
{/* スマホ:一つ前だけ表示 */}
{previousItem && (
<div className="md:hidden">
<Link href={previousItem.href} className="flex items-center gap-1 hover:text-black hover:underline">
<span>←</span>
<span className="truncate">{previousItem.label}</span>
</Link>
</div>
)}
{/* PC:全項目表示 */}
<ol className="hidden md:flex items-center gap-2 flex-wrap">
この書き方が可読性が高いのかは少し自信がありませんが...
こんな感じに一つ上の階層(研究室詳細画面なら研究室一覧画面)のみ表示させることで戻れるようにしています。

5. 学部一覧画面修正
現在の学部一覧画面は、このように学部名が全然収まりきっていません。

resources/js/Pages/Faculty/Index.jsx
クリックでコードを見る
{/* コンテンツ部分 */}
{hasResults ? (
<div className="w-full grid grid-cols-1 md:grid-cols-3 gap-6 mt-8 justify-items-center">
{faculties.map(faculty => (
<FacultyCard key={faculty.id} faculty={faculty} query={query} />
))}
</div>
) : (
<div className="flex-1 flex items-center justify-center">
<p className="text-[#747D8C]">まだ学部が登録されていません。</p>
</div>
)}
6. 研究室一覧画面修正
カードタイトル
タイトルの文字を少し小さくして改行されないようにします。
resources/js/Components/Lab/LabCard.jsx
クリックでコードを見る
<div className="ml-6 min-w-0">
<span className="block text-xl md:text-2xl font-bold text-[#747D8C] truncate">
画面上部の配置
スマホの時は、絞り込みメニューを一つ下に表示させます。
resources/js/Pages/Lab/Index.jsx
クリックでコードを見る
<div className="flex flex-col items-center min-h-full">
<div className="w-full flex flex-row items-center justify-between">
{/* パンくずリスト 左寄せ */}
<div>
<Breadcrumb university={faculty.university} faculty={faculty} query={query} />
</div>
{/* 研究室件数 + ケバブメニュー 右寄せ+ソート */}
<div className="flex items-center gap-2">
<p className="text-[#747D8C]">{labs.total}件の研究室</p>
<select
value={sort}
onChange={handleSortChange}
className="hidden md:block text-sm text-[#747D8C] bg-[#EEF5F9] border border-[#747D8C] rounded px-3 py-1 pr-8 outline-none focus:outline-none focus:ring-0 focus:border-[#747D8C]"
>
{sortOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
{/* ケバブメニュー */}
<div className="relative" ref={menuRef}>
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="p-2 rounded-full"
>
<KebabIcon />
</button>
{isMenuOpen && <MenuPopover addLabel="研究室を追加する" onAddClick={handleAddLabClick} onEditClick={handleEditClick} onViewHistoryClick={handleViewHistoryClick} onDeletionRequestClick={handleDeletionRequestClick} />}
</div>
</div>
</div>
{/* スマホ用ソート:左寄せで下に配置 */}
<div className="w-full md:hidden mt-2">
<select
value={sort}
onChange={handleSortChange}
className="text-sm text-[#747D8C] bg-[#EEF5F9] border border-[#747D8C] rounded px-3 py-1 pr-8 outline-none focus:outline-none focus:ring-0 focus:border-[#747D8C]"
>
{sortOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
プルダウンメニュー
...と思ったら、別のFireFoxっていうブラウザで開くとそんなことはありませんでした。
なので、これについては修正は必要ありません。
なお、今後はFireFoxでサイズ感を確認したいと思います。
7. 研究室詳細画面修正
...あと、ヘッダーのタイトルが全然収まっていないので再度修正が必要そうですね。
男女比バー
resources/js/Pages/Lab/Show.jsx
クリックでコードを見る
{lab.gender_ratio_male != null && lab.gender_ratio_female != null ? (
<div className="flex w-full h-6 rounded overflow-hidden text-sm text-white font-medium md:ml-4">
{lab.gender_ratio_male > 0 && (
今更ですけど、Tailwind CSSを使うとレスポンシブ対応もかなり楽に書けますね。
ヘッダーのタイトル
研究室名の前についていた大学名と学部名を、学部名の前についていた大学名を取って、裸の状態で表示させるように変えます。
resources/js/Components/Header.jsx
クリックでコードを見る
const Header = ({ title, mobileTitle, onOpenSidebar, headerRight }) => {
{/* 中央:タイトル */}
<h1 className="absolute left-1/2 -translate-x-1/2 w-[50%] text-center truncate text-sm md:text-xl font-semibold text-black">
{mobileTitle && (
<span className="md:hidden">{mobileTitle}</span>
)}
<span className={mobileTitle ? 'hidden md:inline' : ''}>{title}</span>
</h1>
resources/js/Layouts/AppLayout.jsx
クリックでコードを見る
const AppLayout = ({ children, title, mobileTitle, mode='default', headerRight }) => {
<Header title={title} mobileTitle={mobileTitle} onOpenSidebar={() => setIsSidebarOpen(true)} headerRight={headerRight} />
resources/js/Pages/Lab/Index.jsx
クリックでコードを見る
<AppLayout title={`${faculty.university.name} ${faculty.name}`} mobileTitle={faculty.name}>
resources/js/Pages/Lab/Show.jsx
クリックでコードを見る
<AppLayout title={`${lab.faculty.university.name} ${lab.faculty.name} ${lab.name}`} mobileTitle={lab.name}>
8. マイページ修正
resources/js/Pages/MyPage/Index.jsx
クリックでコードを見る
{/* 基本情報 */}
<div className="flex items-center justify-between border-b border-black pb-2 w-full">
<h2 className="text-xl font-bold text-black">基本情報</h2>
<span className="text-sm text-[#747D8C]">
利用開始日: {user?.created_at ? new Date(user.created_at).toLocaleDateString('ja-JP') : ''}
</span>
</div>
{/* ニックネーム */}
<div className="mt-6 mb-4 flex flex-col md:flex-row md:items-center">
<h3 className="text-base md:text-lg font-semibold text-[#747D8C] md:w-40 md:shrink-0">ニックネーム</h3>
<div className="mt-2 md:mt-0 w-full md:w-96">
<UserInfoBar value={user?.name} onOpenEditDialog={() => setUserEditModalOpen(true)} />
</div>
</div>
{/* e-Mailアドレス */}
<div className="mt-6 mb-4 flex flex-col md:flex-row md:items-center">
<h3 className="text-base md:text-lg font-semibold text-[#747D8C] md:w-40 md:shrink-0">e-Mailアドレス</h3>
<div className="mt-2 md:mt-0 w-full md:w-96">
<UserInfoBar value={user?.email} onOpenEditDialog={() => setUserEditModalOpen(true)} />
</div>
</div>
{/* パスワード */}
<div className="mt-6 mb-4 flex flex-col md:flex-row md:items-center">
<h3 className="text-base md:text-lg font-semibold text-[#747D8C] md:w-40 md:shrink-0">パスワード</h3>
<div className="mt-2 md:mt-0 w-full md:w-96">
<UserInfoBar value="••••••••" onOpenEditDialog={() => setUserEditModalOpen(true)} />
</div>
</div>
ここまでできたら、コミット・プッシュ、PR作成・マージをお忘れなく!
9.(おまけその17)リファクタリング: 不要なファイルを削除
最後はおまけコーナーでもやって閉めますか。
9.1 ブランチ運用
現在、使い終わったブランチをそのままにしていたのでとんでもないことになっていると思います。
不要なブランチはすべて消しましょうw
ローカルのブランチ整理
$ git branch -D △△
※△△はブランチ名です。
※今チェックアウトしているブランチを削除することはできません。
リモートのブランチ整理
GitHubの「Branches」のゴミ箱アイコンから削除できます。

両方ともmainブランチとdevelopブランチのみを残して、それ以外をすべて削除してください。
全部で、59ブランチもあってめちゃくちゃ大変かもしれませんがw
(本当は使わなくなった時点で削除しましょう。僕は、本業で同じことをして同僚に笑われてしまいました)
新規ブランチ
ここまで整理出来たら、developブランチをpullして新しいブランチを切って作業をしましょう。
ブランチ名は、refactor/frontend/unnecessary-componentsとかにします。
9.2 不要なコンポーネント削除
使われなくなったコンポーネントを削除します。
resources/js/Components/ApplicationLogo.jsxresources/js/Components/Checkbox.jsxresources/js/Components/DangerButton.jsxresources/js/Components/Dropdown.jsxresources/js/Components/InputError.jsxresources/js/Components/InputLabel.jsxresources/js/Components/Modal.jsxresources/js/Components/NavLink.jsxresources/js/Components/PrimaryButton.jsxresources/js/Components/ResponsiveNavLink.jsxresources/js/Components/SecondaryButton.jsxresources/js/Layouts/AuthenticatedLayout.jsxresources/js/Layouts/GuestLayout.jsxresources/js/Pages/Auth/ConfirmPassword.jsxresources/js/Pages/Auth/ForgotPassword.jsxresources/js/Pages/Auth/ResetPassword.jsxresources/js/Pages/Auth/VerifyEmail.jsxresources/js/Pages/Lab/Home.jsxresources/js/Pages/MyPage/Bookmarks.jsxresources/js/Pages/Profile/Edit.jsxresources/js/Pages/Profile/Partials/DeleteUserForm.jsxresources/js/Pages/Profile/Partials/UpdatePasswordForm.jsxresources/js/Pages/Profile/Partials/UpdateProfileInformationForm.jsx
結構たくさん削除してしまったので後でエラーが起きそうで怖いですが...
その時はその時で直しましょうw
コミット・プッシュ、PR作成・マージ、ブランチの削除をしましょう。
10. まとめ・次回予告
本日を持ちまして、かなり長くなってしまっていたフロントエンド実装編もおしまいです!
バックエンド編、ひいては設計編時の考慮が貧弱だったために、その部分の修正をフロントエンド編でしりぬぐいしていた感は否めませんでしたw
改めて、設計の大切さを実感しつつ、自分の成長も感じている昨今です。
次回からは、テスト編が始まります...!!
今の職場では、ほとんどテストケースを書かないので、これまた勉強しながらになるとは思いますが、もう少しだけお付き合いくださればと思います。
これまでの記事一覧
☆要件定義・設計編
☆環境構築編
☆バックエンド実装編
- その7: バックエンド実装編① ~認証機能作成~
- その8: バックエンド実装編②前編 ~レビュー投稿機能作成~
- その8.5: バックエンド実装編②後編 ~レビュー投稿機能作成~
- その9: バックエンド実装編③ ~レビューCRUD機能作成~
- その10: バックエンド実装編④ ~レビューCRUD機能作成その2~
- その11: バックエンド実装編⑤ ~新規大学・学部・研究室作成機能作成~
- その12: バックエンド実装編⑥ ~大学検索機能作成~
- その13: バックエンド実装編⑦ ~大学・学部・研究室編集機能作成~
- その14: バックエンド実装編⑧ ~コメント投稿機能~
- その15: バックエンド実装編⑨ ~コメント編集・削除機能~
- その16: バックエンド実装編⑩ ~ブックマーク機能~
- その17: バックエンド実装編⑪ ~排他制御・トランザクション処理~
- その18: バックエンド実装編⑫ ~マイページ機能作成~
- その19: バックエンド実装編⑬ ~管理者アカウント機能作成~
- その20: バックエンド実装編⑭ ~通知機能作成~
- その21: バックエンド実装編⑮ ~ソーシャルログイン機能作成~
☆フロントエンド実装編
- その22: フロントエンド実装編① ~メインコンテンツ領域作成~
- その23: フロントエンド実装編② ~ヘッダー作成~
- その24: フロントエンド実装編③ ~サイドバー作成~
- その25: フロントエンド実装編④ ~ホームページ作成~
- その26: フロントエンド実装編⑤ ~大学検索結果画面作成~
- その27: フロントエンド実装編⑥ ~大学詳細・学部一覧画面作成~
- その28: フロントエンド実装編⑦ ~学部詳細・研究室一覧画面作成~
- その29: フロントエンド実装編⑧前編 ~研究室詳細・レビュー画面作成前編~
- その29.5: フロントエンド実装編⑧後編 ~研究室詳細・レビュー画面作成後編~
- その30: フロントエンド実装編⑨ ~マイページ作成~
- その31: フロントエンド実装編⑩ ~パンくずリスト作成~
- その32: フロントエンド実装編⑪ ~ログイン・新規登録モーダル作成~
- その33: フロントエンド実装編⑫ ~大学作成・編集モーダル作成~
- その34: フロントエンド実装編⑬ ~学部作成・編集モーダル作成~
- その35: フロントエンド実装編⑭ ~研究室作成・編集モーダル作成~
- その36: フロントエンド実装編⑮ ~レビュー作成・編集モーダル作成~
- その37: フロントエンド実装編⑯ ~コメント一覧表示モーダル作成~
- その38: フロントエンド実装編⑰ ~編集履歴ページ・削除依頼ページ・通知ドロップダウン作成~
- その39: フロントエンド実装編⑱ ~トースト作成~
- その40: フロントエンド実装編⑲ ~退会ページ作成~
- その41: フロントエンド実装編⑳ ~ファビコン作成~
軽く宣伝
YouTubeを始めました(というか始めてました)。
内容としては、Webエンジニアの生活や稼げるようになるまでの成長記録などを発信していく予定です。
現在、まったく再生されておらず、落ち込みそうなので、見てくださる方はぜひ高評価を教えもらえると励みになると思います。"(-""-)"









