😎

【ポートフォリオ 04】JavaScriptで海外旅行スポットサイト風ページを作成 ~DOM操作とクラス設計を実践

に公開

はじめに

現在、未経験からバックエンドエンジニアを目指し、RecursionCSというコンピュータサイエンスの基礎をプロジェクトを通じて学習できるサイトを使用して、学習しています。
そのプロジェクトの一環で、JavaScriptを使ってDOM操作を行なうプロジェクトを自分専用にカスタマイズし、実際に訪れたことがある海外の観光スポットをまとめました。

この記事では、実際にDOM操作・連想配列・オブジェクトを使って作成したポートフォリオについて紹介していきます。

※ なお、RecursionCSのプロジェクトでは、『Emotion Onomatopoeia Dictionary』というプロジェクトがこのプロジェクトに該当します。このプロジェクトでは、感情カテゴリをクリックすることで、それに関連する感情の擬音語のカードが表示されるセクションにジャンプするアプリケーションの作成を目指しています。

GitHubはこちら
https://github.com/Mavo39/frontend-portfolio/tree/main/01_travel_spot

目的

1. DOM操作により動的にHTMLを生成すること
HTMLの構造を理解し、要素を動的に変更します

2. 連想配列の仕組みを理解し、使用できるようにすること
事前にリストとして使用するデータを作成しておくことで、if-else文による処理コストを削減します

3. 実際に使用するクラスを定義すること
オブジェクト指向を使って、実際にクラスを定義して使用してみること

4. ページ内リンクを実現すること
国名カードをクリックすることで、具体的な観光スポットを表示します

完成イメージ

Image 2025-08-27 at 15.58.jpeg

設計段階で考えたこと

実装方針を立てるあたって、はじめに繰り返し処理になる部分を探しました。
その中で、以下の3点が繰り返し処理になると考えました。

① 国別のカード

※実際に図式化したイメージ

Scan Aug 27, 2025 at 18.03.jpeg
※ 筆者による手書きメモ

「写真」「国名」「一言紹介」の構造は同じであり、この部分が国ごとに必要になるとわかりました。

② 国別カードのリンク先

「国名」「一言紹介」「具体的なスポット」が同じ構造です。

③ ②の中における具体的なスポットの説明・写真

※実際に図式化したイメージ

Scan Aug 27, 2025 at 18.04.jpeg

※ 筆者による手書きメモ

「スポット名」「紹介文」「写真」という構造が同じです。

①〜③を受けてわかったこと

  • 国の中に複数のスポットのデータを保持する必要があること
  • 黄色で囲った部分がループ処理によって繰り返し処理されて生成される部分であること

構造が同じであるため、繰り返し処理によって以下の2つを作成できると判断しました。
(1) 国別カードを作成する構造
(2) 国ごとのスポットを作成する構造

これらを実現するために、以下の実装方針を立てました。

実装方針

1. 静的HTMLの作成

DOMにより動的HTMLを生成する前に、骨組みを作り、どの部分を共通化・動的変更を加えるかを確認するために、静的HTMLを作成します。

2. Spotクラスを定義

データ構造
String name        # スポット名
String description # 紹介文
String image       # 写真

3. Countryクラスを定義

データ構造
# プロパティ
String country     # 国名
String description # 説明・紹介
String color       # 背景色
Spot[] spots       # スポット

# メソッド
getSpots()     # スポット名(文字列)配列をスポットオブジェクトの配列に変換
getCardHtml()  # 国別カードのHTMLで使用する文字列を取得
getSpotsHtml() # 各観光スポットをHTMLで使用する文字列を取得

spots では、Spotクラスのインスタンスを配列として保持します。
これにより、国がスポットを保持する構造を実現します。

4. 連想配列でデータ管理

  • 国の写真
    Countryオブジェクト生成時に渡した国名の文字列に応じて、写真が自動的に選択され、表示されるようにします。

  • スポットの紹介文
    Spotオブジェクト生成時に渡したスポット名に応じて、自動的に紹介文が選択され、表示されるようにします。

  • スポットの写真
    Spotオブジェクト生成時に渡したスポット名に応じて、自動的にそのスポットの写真が選択され、表示されるようにします。

5. DOM操作により動的にHTMLを生成

  • 繰り返し処理
    (1) Countryクラス外でCountryオブジェクトをループ
    (2) Countryクラス内でSpotオブジェクトをループ

  • 「1.」で作成した静的HTMLに変更を加えていく
    最後、div要素に設定したid属性を取得し、innerHTMLメソッドにより連結します。

直面したエラー

Countryクラス(Type error)
...
    getSpotsHtml() {
        return `
                    ...
                    <div class="row justify-content-between flex-wrap">
                        ${this.getSpots.map(spot => `  // ここに発生
                            <div class="col-12 col-md-5 d-flex bg-white px-0 g-4">
                                <div class="col-8 p-3">
                                    <h4>${spot.name}</h4>
                                    <p>${spot.description}</p>
                                </div>
                                <div class="col-4 d-flex justify-content-center align-items-center">
                                    <img src="${spot.image}" alt="${spot.name}" class="col-12 p-1 img-fluid img-aspect">
                                </div>
                            </div>            
                        `).join('')}
                    </div>
                    ...

Image 2025-08-27 at 15.38.jpeg

内容

this.getSpots.mapを関数として呼び出したが、関数ではなかった

原因

() のつけ忘れ

特定した流れ

  1. エラーメッセージから「対象が配列じゃない」と分かる

  2. スポットの配列([スポットオブジェクト, スポットオブジェクト, スポットオブジェクト...])が理想だが、実際にはそうなっていない

  3. スポットの配列を返す場所
    getSpots() メソッドがこれを返す場所

  4. map()は配列に対して適用されるが、そうなっていない
    実行してないから配列になってない、という結論に至る
    → メソッドを呼び出しているのに、() を使っていないと気づく

工夫した点

  • 課題を元にいきなりコードを書かず、全体像を把握し、どの方向性で実装していくかを決めてから実装したこと

  • そのために、どの部分が構造的に同じで繰り返し処理によって実現できるか、共通化できるかを探したこと

  • 配列.map()を使用することで、可読性を考慮したこと
    最初は for ループを使用していましたが、もっと簡単にできる方法を探した結果、map()を使った方法があることを知り、実際に使いました。

  • いきなり動的HTMLを生成するためのHTMLの原型が分からなかったため、最初に静的HTMLを作ってから、JavaScriptコードを書き始めたこと(静的HTML → 動的HTMLへ段階的に書き換え)

  • レスポンシブ対応するために、静的HTMLを作成する時に、いろいろ試して最適な配置を決めてから動的HTMLへ置き換えたこと

学び

ブランチの運用方法について

失敗点 : main ブランチをクリーンに保つための手順を最初に考慮していなかったこと

本来であれば、mainブランチから派生ブランチを作成し、その中に機能別に作業ブランチを作成して、作業ブランチ経由で mainブランチにマージする必要がありました。
しかし、mainブランチから派生ブランチを作成し、その派生ブランチ feat/image-assets を直接 main ブランチにマージしてしまいました。

途中からの軌道修正

feat/01_travel_spot ブランチを作成し、機能ごとにこのブランチから派生させて開発し、
完成後にfeat/01_travel_spot ブランチにマージしていき、main ブランチをクリーンに保ちました。

ここからの学び

  • main ブランチをクリーンに保つための戦略を最初に決める必要があること
  • その他の点として、ブランチ名はハイフンを使用することが一般的だということ(アンダースコアを使っていた)

map()メソッドについて

forループでも以下のように実装できましたが、可読性の点で課題を感じていました。

forループを使ってhtml(文字列)を生成
for (let i = 0; i < spots.length; i++) {
  html += `<div>${spots[i].name}</div>`;
}

map()を選んだ理由は、配列の各要素をHTML文字列に変換して結合する処理に最適だと判断したからです。
forループに比べ、副作用を持たない純粋な変換処理として書けるため、コードの意図が明確になり、保守性を高めることが期待できます。

今後の課題

  • コードの再利用性向上
    国やスポットの表示ロジックを関数化することでコンポーネント化し、別プロジェクトでも流用できる形にする。

  • エラーハンドリングの強化
    画像が読み込めない場合やデータが存在しない場合に、代替表示を行う仕組みを追加する。

まとめ

今回のプロジェクトでは、「DOM操作」「連想配列」「クラス」といったJavaScriptの基本概念を組み合わせ、実際に動的なWebページを作成することで、理解を深めることができました。

特に、以下の3点が大きな学びでした。

  • 設計を先に考えることの重要性
    いきなり実装せず、繰り返し構造や共通部分を洗い出してから着手したことで、コードの見通しが良くなったこと

  • 配列操作のメソッド活用
    map()を使うことで、コードが簡潔になり、意図が明確になったこと

  • Gitのブランチ運用の重要性
    mainブランチをクリーンに保つための運用ルールをあらかじめ決めておくことの大切さを学んだこと

今後は、今回の学びを踏まえ、コードの再利用性を向上させることやエラーハンドリング、UIを改善していきたいと思います。

最後までお読みいただき、ありがとうございました。

参考URL

https://recursionist.io/dashboard/course/15/lesson/564

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Discussion