先日、私が講師をしているBINGOの生徒の50代男性・Sさんから相談を受けました。この前、売り上げを折れ線グラフにしたけど次はアニメーションを付けたい!との思いがあるそうです。
以前の「手書きのデータ、どうやってWebアプリのグラフにする?」50代Sさんと学んだ初級者向けAIプロンプト実践術はこちら
今回は、JavaScriptを使って「ボタンを押すとぐぐーんと伸びる折れ線グラフ」を作るコードを紹介します。
HTML、CSS、JavaScriptがそれぞれどんな役割をしていて、どう連携しているのか、特に「グラフが伸びるアニメーション」の裏側をじっくり紐解いていきましょう!
1. コメント付きの全体コード
まずはコードの全体像です。どこで何をしているかわかりやすいように、日本語のコメントを入れました。
クリックして全体コードを表示する
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>八百屋さんのうりあげグラフ</title>
<style>
/* --
CSS:見た目やお洋服を整える領域
-- */
/* 画面全体の設定:背景を少しグレーにして、文字を真ん中に寄せる */
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
text-align: center;
background-color: #f9f9f9;
padding: 20px;
}
/* タイトルの色 */
h1 {
color: #2c3e50;
}
/* 白い大きなカード(枠)の設定 */
.container {
width: 80%;
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
/* お絵描きする透明なキャンバスの設定 */
canvas {
width: 100%;
height: 400px;
display: block;
}
/* ボタンの見た目:緑色で丸っこく、立体的にする */
button {
margin-top: 20px;
padding: 12px 24px;
font-size: 18px;
font-weight: bold;
color: white;
background-color: #2ecc71;
border: none;
border-radius: 25px;
cursor: pointer;
box-shadow: 0 4px #27ae60;
transition: all 0.1s ease;
}
/* マウスをのせたとき:少し濃い緑にする */
button:hover {
background-color: #27ae60;
}
/* ボタンを押した瞬間:凹む動きをつける */
button:active {
box-shadow: 0 0 #27ae60;
transform: translateY(4px);
}
/* ボタンが押せなくなったら:グレーにして固くする */
button:disabled {
background-color: #bdc3c7;
box-shadow: none;
cursor: not-allowed;
transform: none;
}
</style>
</head>
<body>
<!-- --
HTML:画用紙やボタンを置いて、つなぎ役を作る領域
-- -->
<div class="container">
<h1>🍎 八百屋さんの1年間の売上 🍌</h1>
<!-- JSが後で自由に絵を描くための「魔法の透明画用紙」 -->
<canvas id="chartCanvas" width="700" height="350"></canvas>
<!-- 押すとJSの「showNextMonth()」という命令が飛び出すボタン -->
<button id="nextBtn" onclick="showNextMonth()">翌月を表示する</button>
</div>
<!-- --
JavaScript:アニメーションや計算を行う領域
-- -->
<script>
// 1. データ設定:お店の売上データと、画用紙の準備
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
const salesData = [120000, 150000, 180000, 220000, 200000, 250000, 300000, 280000, 240000, 260000, 210000, 350000];
// HTMLから「画用紙(canvas)」を探してきて、絵を描く「筆(ctx)」を取り出す
const canvas = document.getElementById('chartCanvas');
const ctx = canvas.getContext('2d');
// レイアウト設定(余白や一番高い売上の目標値)
const padding = { top: 40, right: 40, bottom: 50, left: 80 };
const graphWidth = canvas.width - padding.left - padding.right;
const graphHeight = canvas.height - padding.top - padding.bottom;
const maxSales = 400000; // Y軸の最大値
// 各月の数字を、画用紙の上の「縦と横の位置(X座標・Y座標)」に変換して計算しておく
const points = months.map((month, i) => {
const x = padding.left + (graphWidth / (months.length - 1)) * i;
const y = padding.top + graphHeight - (salesData[i] / maxSales) * graphHeight;
return { x, y, month, value: salesData[i] };
});
// 今どこまで描いたか覚えておく「記録ノート」
let displayedCount = 0; // 確定して描画済みのデータ点数
let isAnimating = false; // アニメーション中かどうか
// 2. 背景(マス目や「1月〜12月」の文字)を描く関数
function drawBackground() {
// 一度画用紙をまっさらに拭き取る
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 横線(金額の目盛り)を描く
const step = 5;
ctx.strokeStyle = '#e0e0e0';
ctx.lineWidth = 1;
ctx.fillStyle = '#666';
ctx.font = '12px sans-serif';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
for (let i = 0; i <= step; i++) {
const val = (maxSales / step) * i;
const y = padding.top + graphHeight - (i / step) * graphHeight;
ctx.beginPath();
ctx.moveTo(padding.left, y);
ctx.lineTo(canvas.width - padding.right, y);
ctx.stroke();
ctx.fillText(val.toLocaleString() + '円', padding.left - 10, y);
}
// 縦線とX軸のラベル(1月〜12月)を描く
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
points.forEach(p => {
ctx.fillText(p.month, p.x, canvas.height - padding.bottom + 10);
});
// 軸の黒い太枠を描く
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(padding.left, padding.top);
ctx.lineTo(padding.left, canvas.height - padding.bottom);
ctx.lineTo(canvas.width - padding.right, canvas.height - padding.bottom);
ctx.stroke();
}
// 3. グラフを描画する関数(確定した線 + 今伸びている途中の線)
function render(currentLineProgress = 0) {
// 背景をきれいに塗り直す
drawBackground();
// まだ1ヶ月目も表示していないときはここで終わり
if (displayedCount === 0) return;
// 既に完成している過去の線をオレンジ色で一気に引く
ctx.beginPath();
ctx.strokeStyle = '#e67e22';
ctx.lineWidth = 4;
ctx.moveTo(points[0].x, points[0].y);
for (let i = 0; i < displayedCount; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
// 新しく「ぐぐーん」と伸びている最中の線を描く
if (isAnimating && displayedCount > 0) {
const prev = points[displayedCount - 1];
const next = points[displayedCount];
// 先月(prev)から今月(next)の間の「途中経過の場所」を計算する
const currentX = prev.x + (next.x - prev.x) * currentLineProgress;
const currentY = prev.y + (next.y - prev.y) * currentLineProgress;
ctx.lineTo(currentX, currentY);
}
ctx.stroke();
// 確定した場所にオレンジ色の丸印を描く
for (let i = 0; i < displayedCount; i++) {
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, 6, 0, Math.PI * 2);
ctx.fillStyle = '#d35400';
ctx.fill();
}
}
// 4. 「翌月を表示する」ボタンを押したときの処理
function showNextMonth() {
// アニメーション中か、もう12月まで全部出していたら何もしない
if (isAnimating || displayedCount >= months.length) return;
const btn = document.getElementById('nextBtn');
// 最初の1月目を出すときは、アニメーションなしでぽんと点を置く
if (displayedCount === 0) {
displayedCount = 1;
render();
return;
}
// 2月目以降:伸びるパラパラマンガの開始!
isAnimating = true;
btn.disabled = true; // 連打できないようにボタンを固くする
const duration = 600; // 0.6秒かけて伸びる設定
const startTime = performance.now(); // スタート時間を記録
// コマ送り(アニメーション)を実行するお仕事
function animate(time) {
// どれくらい時間が進んだか(0.0 から 1.0 までの割合)
let progress = (time - startTime) / duration;
if (progress > 1) progress = 1;
// 途中の線の位置で描き直す
render(progress);
if (progress < 1) {
// まだ時間が残っていれば、次のコマへ(画面が更新される直前にまた呼ぶ)
requestAnimationFrame(animate);
} else {
// ゴールにたどり着いたときの処理
displayedCount++;
isAnimating = false;
render();
if (displayedCount < months.length) {
btn.disabled = false; // ボタンを元通り押せるようにする
} else {
btn.innerText = '12か月分 表示完了!'; // 全部終わったら文字を変える
}
}
}
// アニメーションの最初の1コマ目を呼び出す
requestAnimationFrame(animate);
}
// 最初にページを開いたとき:枠線と月だけを描いて待機しておく
drawBackground();
</script>
</body>
</html>
2. HTMLとCSSとJavaScriptはどうつながっているの?
Sさん:コードがいっぱい!HTMLとCSSとJavaScriptって、どうやって連携を取り合っているの?
先生A:すばらしい質問です。例えばこのボタンを見てください。
<button id="nextBtn" onclick="showNextMonth()">翌月を表示する</button>
先生A:ここにある onclick="showNextMonth()" という記述が「ボタンが押されたら、JavaScriptの中にある showNextMonth という名前のお仕事を呼び出してね!」という合図になっています。
Sさん:なるほど!HTMLのボタンを押すと、JavaScriptに「出番だよ!」って合図が行くんだね!
先生A:そう!そして、JavaScriptはこうやってHTMLのお部屋(canvas)を探し出して、直接絵を描きこんでいるんです。
const canvas = document.getElementById('chartCanvas');
const ctx = canvas.getContext('2d');
Sさん:id="chartCanvas" っていう名前を目印にして、画用紙を見つけてるんだね!
先生A:その通り。CSSは、その画用紙やボタンがかっこいい形や色になるように、周りから優しく包み込んでいるんです
。
3. なんでグラフが「ぐぐーん」って綺麗に伸びるの?
Sさん:ボタンを押したときに、線が「ぐぐーん」って伸びてるのはどうやってるの?
先生A:実はあれは「超高速のパラパラマンガ」なんです!
Sさん:パラパラマンガ!?
先生A:そう!線を一発でバシッと引いているんじゃなくて、ほんの少しずつ伸びた絵を1秒間に何十回も描き直しているんだ。
Sさん:描き直してる?消しゴムで消しながら?
先生A:そうなんです。コードの中にこういう命令があります。
// 画面を一度真っ白に拭き取る
ctx.clearRect(0, 0, canvas.width, canvas.height);
先生A:ボタンを押すと、JavaScriptは次のような順番で大忙しで作業をしているんです。
- 画用紙を一瞬で雑巾がけして真っ白にする(
clearRect) - 背景の目盛りや、先月までの確定した線をサッと引く
- 先月と今月の間の「0.1ミリだけ伸びた線」を描く
- また画用紙を真っ白にする
- 今度は「0.2ミリ伸びた線」を描く……
これを1秒間の間に60回近く繰り返しているんだ。
Sさん:目が追いつかない速さでパラパラマンガを描き直してるから、つながって伸びているみたいに見えるんだね!
先生A:そのパラパラマンガの合図を出しているのが、この requestAnimationFrame という命令なんです。
if (progress < 1) {
// まだゴールに届いていないから、次のパラパラマンガのコマを描いてね!
requestAnimationFrame(animate);
}
Sさん:だからあんなに滑らかに「ぐぐーん」って動くんだね!すっきりした!
4. 実際の動作
5. まとめ
今回のポイントを整理してみましょう。
-
HTML:画用紙(
<canvas>)やボタン(<button>)を配置して仕組みの土台を作る。 - CSS:ボタンの押し心地や、全体のデザインを綺麗に整える。
-
JavaScript:ボタンが押された合図を受け取って、超高速パラパラマンガ(
requestAnimationFrame)を実行し、線が伸びるアニメーションを実現する。
Webアプリケーションで動的なグラフを描くときは、このように「画面を一度リセットして、少し変化した状態を再描画する」という仕組みが基本になっています。
一見難しそうに見えるアニメーションも、分解してみるとシンプルなパラパラマンガの原理なんですね。
ぜひ手元の環境でコードを動かして、数値を変えたりアニメーション速度をいじったりして遊んでみてください!

