0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

八百屋さんの動く売上グラフを作ろう!HTML・CSS・JavaScriptのつながりを解説

0
Last updated at Posted at 2026-09-14

先日、私が講師をしているBINGOの生徒の50代男性・Sさんから相談を受けました。この前、売り上げを折れ線グラフにしたけど次はアニメーションを付けたい!との思いがあるそうです。
以前の「手書きのデータ、どうやってWebアプリのグラフにする?」50代Sさんと学んだ初級者向けAIプロンプト実践術はこちら

今回は、JavaScriptを使って「ボタンを押すとぐぐーんと伸びる折れ線グラフ」を作るコードを紹介します。

HTML、CSS、JavaScriptがそれぞれどんな役割をしていて、どう連携しているのか、特に「グラフが伸びるアニメーション」の裏側をじっくり紐解いていきましょう!

Gemini_Generated_Image_qqv3c7qqv3c7qqv3.jpg

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は、その画用紙やボタンがかっこいい形や色になるように、周りから優しく包み込んでいるんですGemini_Generated_Image_qqv3c7qqv3c7qqv3.jpg
。

3. なんでグラフが「ぐぐーん」って綺麗に伸びるの?

Sさん:ボタンを押したときに、線が「ぐぐーん」って伸びてるのはどうやってるの?

先生A:実はあれは「超高速のパラパラマンガ」なんです!

Sさん:パラパラマンガ!?

先生A:そう!線を一発でバシッと引いているんじゃなくて、ほんの少しずつ伸びた絵を1秒間に何十回も描き直しているんだ。

Sさん:描き直してる?消しゴムで消しながら?

先生A:そうなんです。コードの中にこういう命令があります。

// 画面を一度真っ白に拭き取る
ctx.clearRect(0, 0, canvas.width, canvas.height);

先生A:ボタンを押すと、JavaScriptは次のような順番で大忙しで作業をしているんです。

  1. 画用紙を一瞬で雑巾がけして真っ白にする(clearRect)
  2. 背景の目盛りや、先月までの確定した線をサッと引く
  3. 先月と今月の間の「0.1ミリだけ伸びた線」を描く
  4. また画用紙を真っ白にする
  5. 今度は「0.2ミリ伸びた線」を描く……

これを1秒間の間に60回近く繰り返しているんだ。

Sさん:目が追いつかない速さでパラパラマンガを描き直してるから、つながって伸びているみたいに見えるんだね!

先生A:そのパラパラマンガの合図を出しているのが、この requestAnimationFrame という命令なんです。

if (progress < 1) {
  // まだゴールに届いていないから、次のパラパラマンガのコマを描いてね!
  requestAnimationFrame(animate);
}

Sさん:だからあんなに滑らかに「ぐぐーん」って動くんだね!すっきりした!

4. 実際の動作

※GIF化したので少しもっさりしてます
レコーディング 2026-09-16 190952.gif

5. まとめ

今回のポイントを整理してみましょう。

  • HTML:画用紙(<canvas>)やボタン(<button>)を配置して仕組みの土台を作る。
  • CSS:ボタンの押し心地や、全体のデザインを綺麗に整える。
  • JavaScript:ボタンが押された合図を受け取って、超高速パラパラマンガ(requestAnimationFrame)を実行し、線が伸びるアニメーションを実現する。

Webアプリケーションで動的なグラフを描くときは、このように「画面を一度リセットして、少し変化した状態を再描画する」という仕組みが基本になっています。

一見難しそうに見えるアニメーションも、分解してみるとシンプルなパラパラマンガの原理なんですね。

ぜひ手元の環境でコードを動かして、数値を変えたりアニメーション速度をいじったりして遊んでみてください!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?