LESSON 9課題あり!

ポートフォリオを作ろう③
JavaScriptで動きを付ける

いよいよ最終回。JavaScript で「〜したら、〜する」という仕掛けを付けて、ポートフォリオを完成させます。 今回追加するのは、次の4つの動きです。

① 時間帯のあいさつ

開いた時刻に合わせて「おはようございます」「こんにちは」「こんばんは」を切り替える。

② 鉄棒アニメーション

鉄棒の人が、くるくると回り続ける。クリックで止めたり動かしたりできる。

③ コントローラー

コントローラーのボタンが、ポチポチと押されたように光る。

④ ひとことメッセージ

ボタンを押すと、メッセージがランダムに切り替わる。

1. 完成イメージ

鉄棒とコントローラーが動いているのを見たら、イラストや「ひとことを見る」ボタンもクリックしてみてください。

portfolio/index.html(完成版)

2. JavaScriptの考え方

HTML が「骨組み」、CSS が「見た目」なら、JavaScript は「仕掛け」です。 JavaScript のプログラムは、ほとんどが次の形の組み合わせでできています。

ボタンが押されたらきっかけ(イベント)
→ 
ひとことを変えるやること(処理)
家でいえば「人が近づいたら → ドアを開ける」自動ドアや、「スイッチを押したら → 電気をつける」照明と同じです。きっかけは「時間がたったら」でもOK。これを使うとアニメーションが作れます。

JavaScript がページを書き換えるときは、まず「どの部分を書き換えるか」をHTMLの中から探し、次に中身を書き換えます。 そのため、HTML のタグに id や class という名札を付けておき、それを目印に探します。

3. 準備:HTMLとCSSを書き足す

index.html を書き換える

イラストを動かすには、「動かない部分」と「動く部分」を別々の画像に分けて重ねる必要があります。 紙芝居ではなく、パペット人形劇のイメージです。そこで、画像を次の3つに差し替えます。

フォルダの構成
portfolio/
├── index.html
├── style.css
├── script.js          ← 新しく作る
└── images/
    ├── tetsubo-bar.svg  ← 鉄棒だけ(動かない)
    ├── person.svg       ← 鉄棒の人だけ(動く)
    └── game-base.svg    ← ボタンなしのコントローラー(動かない)

練習用のイラスト(tetsubo-bar.svg・person.svg・game-base.svg)は、 右クリック →「名前を付けてリンク先を保存」で images フォルダに保存してください。

あいさつを表示する場所を、ヘッダーに追加します。

index.html(header の中)
  <header>
    <p id="greeting">こんにちは!</p>
    <h1>Mayu Takahashi Portfolio</h1>

鉄棒の <img> を、2枚の画像を重ねる「舞台(stage)」に置き換えます。

index.html(特技の section の中)
      <p>子どものころから鉄棒が大好きです。</p>
      <div class="stage" id="tetsubo">
        <img src="images/tetsubo-bar.svg" alt="鉄棒のイラスト">
        <img src="images/person.svg" alt="" class="person" id="person">
      </div>
      <p class="hint">イラストをクリックすると、止まったり動いたりします。</p>
    </section>

コントローラーも同じように、本体の画像の上に4つのボタンを重ねます。ボタンは画像ではなく、<span> の箱を CSS で丸く塗って作ります。

index.html(ハマっているものの section の中)
      <p>5人対5人で戦う、チーム対戦型のシューティングゲームです。</p>
      <div class="stage">
        <img src="images/game-base.svg" alt="ゲームのコントローラーのイラスト">
        <span class="pad-button red"></span>
        <span class="pad-button yellow"></span>
        <span class="pad-button green"></span>
        <span class="pad-button blue"></span>
      </div>
    </section>

最後に、ひとことのカードと、JavaScript ファイルの読み込みを追加します。

index.html(main の最後に追加)
    <section class="card">
      <h2>ひとこと</h2>
      <p id="message">下のボタンを押してみてね。</p>
      <button id="message-button">ひとことを見る</button>
    </section>
  </main>
index.html(body の終わりの直前)
  </footer>

  <script src="script.js"></script>
</body>

style.css に書き足す

ボタンの見た目と、画像を重ねるための設定を、style.css の一番下に追加します。少し長いですが、コピーしてOKです。

style.css(一番下に追加)
/* ===== ここから Lesson 9 で追加 ===== */

/* ボタン */
button {
  display: block;
  margin: 16px auto 0;
  padding: 8px 24px;
  border: none;
  border-radius: 999px;
  background-color: #4cc38a;
  color: #17201b;
  font-size: 16px;
  cursor: pointer;
  transition: transform 0.1s;
}

button:hover {
  opacity: 0.85;
}

button:active {
  transform: scale(0.92);
}

#greeting {
  margin: 0 0 8px;
  font-size: 20px;
  font-weight: bold;
}

#message {
  text-align: center;
  font-size: 20px;
}

/* 画像を重ねるための舞台 */
.stage {
  position: relative;
  max-width: 300px;
  margin: 16px auto 0;
}

.stage img {
  margin: 0;
}

/* 鉄棒の人(にぎっている手を中心に回る) */
.person {
  position: absolute;
  left: 40%;
  top: 37.5%;
  width: 20%;
  transform-origin: 50% 11.8%;
}

#tetsubo {
  cursor: pointer;
}

.hint {
  text-align: center;
  font-size: 14px;
  color: #93a399;
}

/* コントローラーのボタン */
.pad-button {
  position: absolute;
  width: 4.4%;
  aspect-ratio: 1;
  border-radius: 50%;
  opacity: 0.35;
  transition: opacity 0.1s, transform 0.1s;
}

.pad-button.red    { left: 62.8%; top: 48%;    background-color: #ff5a5f; }
.pad-button.yellow { left: 67.8%; top: 54.8%;  background-color: #ffd25e; }
.pad-button.green  { left: 57.8%; top: 54.8%;  background-color: #5fd3a3; }
.pad-button.blue   { left: 62.8%; top: 61.5%;  background-color: #5fb2ff; }

/* 押されている間の見た目 */
.pad-button.pressed {
  opacity: 1;
  transform: scale(0.8);
  box-shadow: 0 0 12px #ffffff;
}
ポイント意味
position: relative と absolute舞台(.stage)を基準にして、人やボタンを好きな位置に重ねて置くための組み合わせ。left・top で位置を決めます
transform-origin回転の中心。ここでは「鉄棒をにぎっている手」の位置にしているので、手を中心にくるっと回ります
.pad-button.pressed「pad-button と pressed の両方の名札が付いているとき」の見た目。JavaScript でこの名札を付けたり外したりして、光らせます
:hover / :active「マウスが乗っているとき」/「押している間」の見た目

id を CSS で指定するときは、名前の前に # を付けます。

script.js を作る

index.html と同じフォルダに、新しいファイル script.js を作ります。ここから先は、このファイルに書いていきます。

4. ① 時間帯のあいさつ

script.js
// ① 時間帯に合わせて、あいさつを変える
const greeting = document.getElementById("greeting");
const hour = new Date().getHours();

if (hour < 10) {
  greeting.textContent = "おはようございます!";
} else if (hour < 18) {
  greeting.textContent = "こんにちは!";
} else {
  greeting.textContent = "こんばんは!";
}

1行ずつ、日本語に訳してみましょう。

コード日本語にすると
// …コメント(メモ書き)。プログラムとしては無視される
const greeting = document.getElementById("greeting");ページの中から、名札(id)が greeting の部分を探して、greeting という箱(変数)に入れておく
const hour = new Date().getHours();今の時刻の「時」(0〜23)を調べて、hour という箱に入れておく
if (hour < 10) { … }もし 10時より前なら、「おはようございます!」にする
else if (hour < 18) { … }そうではなく 18時より前なら、「こんにちは!」にする
else { … }どれでもなければ、「こんばんは!」にする
greeting.textContent = "…";greeting の部分の文字を書き換える
変数は「名前付きの箱」: const 名前 = 中身; と書くと、中身に名前を付けて取っておけます。何度も使うものに名前を付けておくと、プログラムが読みやすくなります。 if 〜 else は条件分岐といって、「場合によってやることを変える」ための書き方です。

5. ② 鉄棒アニメーション

あいさつの下に、空行を1行あけて続けて書きます。

script.js(続き)
// ② 鉄棒の人が、くるくる回り続ける
const person = document.getElementById("person");
const tetsubo = document.getElementById("tetsubo");
let angle = 0;
let moving = true;

setInterval(function () {
  if (moving) {
    angle = angle + 5;
    person.style.transform = "rotate(" + angle + "deg)";
  }
}, 30);

tetsubo.addEventListener("click", function () {
  moving = !moving;
});
コード日本語にすると
let angle = 0;今の角度を入れておく箱を、0度で用意する(let は、あとで中身を変えられる箱)
let moving = true;「今動いているか」を入れておく箱。true は「はい」、false は「いいえ」
setInterval(function () { … }, 30);30ミリ秒(0.03秒)ごとに、{ } の中をくり返し実行する
angle = angle + 5;角度を5度増やす
person.style.transform = "rotate(" + angle + "deg)";人の画像を、その角度だけ回転させる(例:rotate(45deg))
tetsubo.addEventListener("click", …)鉄棒のイラストがクリックされたら、{ } の中を実行する
moving = !moving;! は「反対にする」。動いていれば止める、止まっていれば動かす
アニメーションの正体: 0.03秒ごとに5度ずつ回転させているだけです。パラパラ漫画と同じで、少しずつ変えた絵を素早く切り替えると、人間の目には「動いている」ように見えます。 30 や 5 の数字を変えると、回る速さが変わります。いろいろ試してみましょう。

6. ③ コントローラー

script.js(続き)
// ③ コントローラーのボタンが、ポチポチ光る
const padButtons = document.querySelectorAll(".pad-button");

setInterval(function () {
  const index = Math.floor(Math.random() * padButtons.length);
  const padButton = padButtons[index];
  padButton.classList.add("pressed");
  setTimeout(function () {
    padButton.classList.remove("pressed");
  }, 200);
}, 350);
コード日本語にすると
document.querySelectorAll(".pad-button")名札(class)が pad-button の部分を全部探す(4つのボタンが、0番〜3番の番号付きで手に入る)
setInterval(function () { … }, 350);0.35秒ごとに、{ } の中をくり返す
Math.random() * padButtons.length0 以上 4 未満の、でたらめな数(例:2.71…)を作る
Math.floor( … )小数点以下を切り捨てる(2.71… → 2)。これで 0〜3 のどれかになる
padButtons[index]4つのボタンのうち、index 番目を取り出す
classList.add("pressed")そのボタンに pressed の名札を付ける(CSS の指定で光る)
setTimeout(function () { … }, 200);0.2秒たったら1回だけ、{ } の中を実行する。ここで名札を外して、元の暗い色に戻す

setInterval は「〇秒ごとにくり返す」、setTimeout は「〇秒たったら1回だけ」。 「光らせる → 0.2秒後に消す」を、0.35秒ごとにランダムなボタンでくり返すことで、だれかがポチポチ押しているように見えます。

プログラムの世界では、番号を 0 から数えるのが一般的です。最初は戸惑いますが、すぐに慣れます。

7. ④ ひとことメッセージ

script.js(続き)
// ④ ボタンを押すと、ひとことがランダムに変わる
const messages = [
  "最近はエアロピクルスも好きだよ",
  "スーの歌も好きだよ",
  "ねずみなんだけど丑年なんでスー"
];
const message = document.getElementById("message");
const messageButton = document.getElementById("message-button");

messageButton.addEventListener("click", function () {
  const index = Math.floor(Math.random() * messages.length);
  message.textContent = messages[index];
});
コード日本語にすると
const messages = [ … ];メッセージを3つ並べたリスト(配列)を作る。順番に 0番・1番・2番 と番号が付く
messageButton.addEventListener("click", …)「ひとことを見る」ボタンがクリックされたら、{ } の中を実行する
Math.floor(Math.random() * messages.length)③ と同じ方法で、0〜2 のどれかの番号をランダムに選ぶ
message.textContent = messages[index];リストの index 番目のメッセージに書き換える

8. 全体のコードと完成

完成した3つのファイルの全体はこちらです。

script.js
// ① 時間帯に合わせて、あいさつを変える
const greeting = document.getElementById("greeting");
const hour = new Date().getHours();

if (hour < 10) {
  greeting.textContent = "おはようございます!";
} else if (hour < 18) {
  greeting.textContent = "こんにちは!";
} else {
  greeting.textContent = "こんばんは!";
}

// ② 鉄棒の人が、くるくる回り続ける
const person = document.getElementById("person");
const tetsubo = document.getElementById("tetsubo");
let angle = 0;
let moving = true;

setInterval(function () {
  if (moving) {
    angle = angle + 5;
    person.style.transform = "rotate(" + angle + "deg)";
  }
}, 30);

tetsubo.addEventListener("click", function () {
  moving = !moving;
});

// ③ コントローラーのボタンが、ポチポチ光る
const padButtons = document.querySelectorAll(".pad-button");

setInterval(function () {
  const index = Math.floor(Math.random() * padButtons.length);
  const padButton = padButtons[index];
  padButton.classList.add("pressed");
  setTimeout(function () {
    padButton.classList.remove("pressed");
  }, 200);
}, 350);

// ④ ボタンを押すと、ひとことがランダムに変わる
const messages = [
  "最近はエアロピクルスも好きだよ",
  "スーの歌も好きだよ",
  "ねずみなんだけど丑年なんでスー"
];
const message = document.getElementById("message");
const messageButton = document.getElementById("message-button");

messageButton.addEventListener("click", function () {
  const index = Math.floor(Math.random() * messages.length);
  message.textContent = messages[index];
});
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Mayu Takahashi Portfolio</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header>
    <p id="greeting">こんにちは!</p>
    <h1>Mayu Takahashi Portfolio</h1>
    <p>はじめまして! 私の好きなものを紹介します。</p>
  </header>

  <main>
    <section class="card">
      <h2>特技:鉄棒</h2>
      <p>子どものころから鉄棒が大好きです。</p>
      <div class="stage" id="tetsubo">
        <img src="images/tetsubo-bar.svg" alt="鉄棒のイラスト">
        <img src="images/person.svg" alt="" class="person" id="person">
      </div>
      <p class="hint">イラストをクリックすると、止まったり動いたりします。</p>
    </section>

    <section class="card">
      <h2>今いちばんハマっているもの:Valorant</h2>
      <p>5人対5人で戦う、チーム対戦型のシューティングゲームです。</p>
      <div class="stage">
        <img src="images/game-base.svg" alt="ゲームのコントローラーのイラスト">
        <span class="pad-button red"></span>
        <span class="pad-button yellow"></span>
        <span class="pad-button green"></span>
        <span class="pad-button blue"></span>
      </div>
    </section>

    <section class="card">
      <h2>ひとこと</h2>
      <p id="message">下のボタンを押してみてね。</p>
      <button id="message-button">ひとことを見る</button>
    </section>
  </main>

  <footer>
    <p>&copy; 2026 Mayu Takahashi</p>
  </footer>

  <script src="script.js"></script>
</body>
</html>
style.css
/* ページ全体 */
body {
  margin: 0;
  background-color: #17201b;
  color: #f4f1e6;
  font-family: "Yu Gothic UI", "Meiryo", "Hiragino Sans", sans-serif;
  line-height: 1.8;
}

/* 一番上のタイトル部分 */
header {
  background: linear-gradient(135deg, #f9d84a, #4cc38a);
  padding: 48px 16px;
  text-align: center;
  color: #17201b;
}

header h1 {
  margin: 0;
  font-size: 36px;
}

/* 中身を中央に寄せる */
main {
  max-width: 720px;
  margin: 0 auto;
  padding: 32px 16px;
}

/* 紹介カード */
.card {
  background-color: #22302a;
  border-radius: 16px;
  padding: 24px;
  margin-bottom: 24px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}

.card h2 {
  margin-top: 0;
  color: #f9d84a;
  border-left: 6px solid #4cc38a;
  padding-left: 12px;
}

.card img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 16px auto 0;
  border-radius: 12px;
}

/* 一番下の部分 */
footer {
  text-align: center;
  padding: 24px;
  color: #93a399;
  font-size: 14px;
}

/* ===== ここから Lesson 9 で追加 ===== */

/* ボタン */
button {
  display: block;
  margin: 16px auto 0;
  padding: 8px 24px;
  border: none;
  border-radius: 999px;
  background-color: #4cc38a;
  color: #17201b;
  font-size: 16px;
  cursor: pointer;
  transition: transform 0.1s;
}

button:hover {
  opacity: 0.85;
}

button:active {
  transform: scale(0.92);
}

#greeting {
  margin: 0 0 8px;
  font-size: 20px;
  font-weight: bold;
}

#message {
  text-align: center;
  font-size: 20px;
}

/* 画像を重ねるための舞台 */
.stage {
  position: relative;
  max-width: 300px;
  margin: 16px auto 0;
}

.stage img {
  margin: 0;
}

/* 鉄棒の人(にぎっている手を中心に回る) */
.person {
  position: absolute;
  left: 40%;
  top: 37.5%;
  width: 20%;
  transform-origin: 50% 11.8%;
}

#tetsubo {
  cursor: pointer;
}

.hint {
  text-align: center;
  font-size: 14px;
  color: #93a399;
}

/* コントローラーのボタン */
.pad-button {
  position: absolute;
  width: 4.4%;
  aspect-ratio: 1;
  border-radius: 50%;
  opacity: 0.35;
  transition: opacity 0.1s, transform 0.1s;
}

.pad-button.red    { left: 62.8%; top: 48%;    background-color: #ff5a5f; }
.pad-button.yellow { left: 67.8%; top: 54.8%;  background-color: #ffd25e; }
.pad-button.green  { left: 57.8%; top: 54.8%;  background-color: #5fd3a3; }
.pad-button.blue   { left: 62.8%; top: 61.5%;  background-color: #5fb2ff; }

/* 押されている間の見た目 */
.pad-button.pressed {
  opacity: 1;
  transform: scale(0.8);
  box-shadow: 0 0 12px #ffffff;
}

保存して、Chrome で開いてみましょう。鉄棒の人が回り、コントローラーが光り、ボタンでひとことが変われば……

9. 動かないときは「コンソール」を見る

JavaScript は、1文字のミスでもまったく動かなくなることがあります。そんなときは、Chrome で F12 を押し、 デベロッパーツールの「コンソール(Console)」タブを開いてみましょう。エラーがあれば、赤い文字で原因と場所(ファイル名と行番号)が表示されます。

よくあるミス確認すること
何も動かない、どのボタンも反応しない<script src="script.js"> のファイル名が合っているか。script.js が同じフォルダにあるか。
Cannot read properties of null と出るHTML の id や class の名前と、JavaScript で探している名前が一致しているか(person と persan など)。
人が変な場所を中心に回るCSS の .person の transform-origin や left・top が、上のコードと同じになっているか。
Unexpected token と出るカッコ ( ) { } の閉じ忘れや、" の閉じ忘れがないか。全角の記号が混ざっていないか。

エラーの意味がわからなかったら、そのメッセージをそのまま Claude Code に見せて聞いてみましょう。

Claude Code への質問の例
Chrome のコンソールに次のエラーが出ました。原因と、どこを直せばいいかを教えてください。
(ここにエラーメッセージを貼り付ける)

さらにステップアップするなら

確認クイズ

選択肢をクリックしてください。

Q1. 「ボタンを押したらメッセージが変わる」のような動きを作る言語は?

Q2. button.addEventListener("〇〇", …) で「クリックされたら」にするには、〇〇に何を書く?

Q3. 「0.03秒ごとに少しずつ回転させる」のように、決まった間隔で処理をくり返すときに使うのは?

Q4. const messages = ["A", "B", "C"]; で、"A" は何番?

Q5. JavaScript が動かないとき、エラーを確認できるデベロッパーツールのタブは?

まとめ

HTML で骨組みを作り、CSS で見た目を整え、JavaScript で動きを付ける。 これは、世界中の Webサイトが作られているのとまったく同じ流れです。今日作ったポートフォリオは、その立派な第一歩です。

課題:自分のポートフォリオを提出しよう課題あり!

Lesson 7〜9 で作ったポートフォリオを、自分の内容に作り変えて提出してください。

課題の内容

  • 名前・特技・ハマっているものを、自分のものにする
  • 画像を、自分で用意したものに差し替える(自分で撮った写真・描いた絵など、権利に問題のないもの)
  • 「ひとこと」のメッセージを、自分の言葉で3つ以上にする
  • (チャレンジ)カードを1枚以上増やしたり、色を自分好みに変えたりしてみる

鉄棒やコントローラーのアニメーションは、自分の画像に合わなければ外してもかまいません。そのときは、Chrome のコンソールにエラーが出ていないか確認しましょう。

提出前のチェックリスト

  • index.html を Chrome で開くと、画像・色・動きが正しく表示される
  • Chrome のデベロッパーツール(F12)のコンソールに、赤いエラーが出ていない
  • index.html・style.css・script.js・images フォルダがそろっている
  • 使っている画像は、自分で用意した(または利用が許可された)ものだけ

提出のしかた

  1. 提出用のフォルダを作る

    デスクトップなどに、Lesson09_氏名 という名前のフォルダを作ります(例:Lesson09_YamadaTaro)。

  2. プロジェクトのフォルダをコピーして入れる

    portfolio フォルダを、まるごと提出用のフォルダにコピーします。

  3. zip ファイルにまとめる

    提出用のフォルダを右クリック →「圧縮先」→「ZIP ファイル」を選びます(Windows 10 の場合は「送る」→「圧縮(zip 形式)フォルダー」)。 同じ場所に Lesson09_氏名.zip ができます。

  4. 共有する

    できた zip ファイルを、講師・担当者から案内された共有先にアップロードして提出します。

提出する前に、zip ファイルをダブルクリックして中身を開き、必要なファイルがそろっているか確認しておくと安心です。