ポートフォリオを作ろう③
JavaScriptで動きを付ける
いよいよ最終回。JavaScript で「〜したら、〜する」という仕掛けを付けて、ポートフォリオを完成させます。 今回追加するのは、次の4つの動きです。
① 時間帯のあいさつ
開いた時刻に合わせて「おはようございます」「こんにちは」「こんばんは」を切り替える。
② 鉄棒アニメーション
鉄棒の人が、くるくると回り続ける。クリックで止めたり動かしたりできる。
③ コントローラー
コントローラーのボタンが、ポチポチと押されたように光る。
1. 完成イメージ
鉄棒とコントローラーが動いているのを見たら、イラストや「ひとことを見る」ボタンもクリックしてみてください。
2. JavaScriptの考え方
HTML が「骨組み」、CSS が「見た目」なら、JavaScript は「仕掛け」です。 JavaScript のプログラムは、ほとんどが次の形の組み合わせでできています。
ボタンが押されたらきっかけ(イベント)→ ひとことを変えるやること(処理)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 フォルダに保存してください。
あいさつを表示する場所を、ヘッダーに追加します。
<header>
<p id="greeting">こんにちは!</p>
<h1>Mayu Takahashi Portfolio</h1>鉄棒の <img> を、2枚の画像を重ねる「舞台(stage)」に置き換えます。
<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 で丸く塗って作ります。
<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 ファイルの読み込みを追加します。
<section class="card">
<h2>ひとこと</h2>
<p id="message">下のボタンを押してみてね。</p>
<button id="message-button">ひとことを見る</button>
</section>
</main> </footer>
<script src="script.js"></script>
</body><div>:特に意味を持たない、汎用の箱。ここでは画像を重ねる「舞台」として使います<span>:小さな箱。ここではコントローラーのボタン1つ1つになります<button>:押せるボタンを作るタグid="person":ページの中で1つだけのものに付ける名札。class と違い、同じ id は1ページに1つまで<script src="script.js">:JavaScript ファイルを読み込む。HTML を全部読み込んでから動かしたいので、</body>の直前に書きます
style.css に書き足す
ボタンの見た目と、画像を重ねるための設定を、style.css の一番下に追加します。少し長いですが、コピーしてOKです。
/* ===== ここから 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. ① 時間帯のあいさつ
// ① 時間帯に合わせて、あいさつを変える
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行あけて続けて書きます。
// ② 鉄棒の人が、くるくる回り続ける
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; | ! は「反対にする」。動いていれば止める、止まっていれば動かす |
30 や 5 の数字を変えると、回る速さが変わります。いろいろ試してみましょう。
6. ③ コントローラー
// ③ コントローラーのボタンが、ポチポチ光る
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.length | 0 以上 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. ④ ひとことメッセージ
// ④ ボタンを押すと、ひとことがランダムに変わる
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つのファイルの全体はこちらです。
// ① 時間帯に合わせて、あいさつを変える
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];
});<!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>© 2026 Mayu Takahashi</p>
</footer>
<script src="script.js"></script>
</body>
</html>/* ページ全体 */
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 に見せて聞いてみましょう。
Chrome のコンソールに次のエラーが出ました。原因と、どこを直せばいいかを教えてください。
(ここにエラーメッセージを貼り付ける)さらにステップアップするなら
- メッセージを自分らしい言葉に変える、数を増やす
setIntervalの数字を変えて、逆上がりやボタンの速さを変えてみる- Claude Code に「画像をクリックしたら大きく表示する機能を付けたい。やり方を教えて」と相談して、新しい動きに挑戦する
確認クイズ
選択肢をクリックしてください。
Q1. 「ボタンを押したらメッセージが変わる」のような動きを作る言語は?
Q2. button.addEventListener("〇〇", …) で「クリックされたら」にするには、〇〇に何を書く?
Q3. 「0.03秒ごとに少しずつ回転させる」のように、決まった間隔で処理をくり返すときに使うのは?
Q4. const messages = ["A", "B", "C"]; で、"A" は何番?
Q5. JavaScript が動かないとき、エラーを確認できるデベロッパーツールのタブは?
まとめ
- JavaScript は「きっかけ → やること」の組み合わせで動きを作る。
getElementById・querySelectorAllで、HTML の中から書き換えたい部分を名札(id / class)を目印に探す。- 変数(
const/let)は名前付きの箱、if 〜 else は条件分岐、配列は番号付きのリスト。 addEventListener("click", …)で「クリックされたら」、setIntervalで「〇秒ごとに」の処理を書く。- アニメーションは、少しずつ変えた状態を素早く切り替えるパラパラ漫画。
- 動かないときは F12 →「コンソール」で赤いエラーを確認する。
HTML で骨組みを作り、CSS で見た目を整え、JavaScript で動きを付ける。 これは、世界中の Webサイトが作られているのとまったく同じ流れです。今日作ったポートフォリオは、その立派な第一歩です。
課題:自分のポートフォリオを提出しよう課題あり!
Lesson 7〜9 で作ったポートフォリオを、自分の内容に作り変えて提出してください。
課題の内容
- 名前・特技・ハマっているものを、自分のものにする
- 画像を、自分で用意したものに差し替える(自分で撮った写真・描いた絵など、権利に問題のないもの)
- 「ひとこと」のメッセージを、自分の言葉で3つ以上にする
- (チャレンジ)カードを1枚以上増やしたり、色を自分好みに変えたりしてみる
鉄棒やコントローラーのアニメーションは、自分の画像に合わなければ外してもかまいません。そのときは、Chrome のコンソールにエラーが出ていないか確認しましょう。
提出前のチェックリスト
index.htmlを Chrome で開くと、画像・色・動きが正しく表示される- Chrome のデベロッパーツール(F12)のコンソールに、赤いエラーが出ていない
index.html・style.css・script.js・imagesフォルダがそろっている- 使っている画像は、自分で用意した(または利用が許可された)ものだけ
提出のしかた
-
提出用のフォルダを作る
デスクトップなどに、
Lesson09_氏名という名前のフォルダを作ります(例:Lesson09_YamadaTaro)。 -
プロジェクトのフォルダをコピーして入れる
portfolioフォルダを、まるごと提出用のフォルダにコピーします。 -
zip ファイルにまとめる
提出用のフォルダを右クリック →「圧縮先」→「ZIP ファイル」を選びます(Windows 10 の場合は「送る」→「圧縮(zip 形式)フォルダー」)。 同じ場所に
Lesson09_氏名.zipができます。 -
共有する
できた zip ファイルを、講師・担当者から案内された共有先にアップロードして提出します。
提出する前に、zip ファイルをダブルクリックして中身を開き、必要なファイルがそろっているか確認しておくと安心です。