JavaScript | 1 日 60 分 × 7 日アプリ学習:JavaScript基礎・DOM操作 おみくじ編

Web APP JavaScript
スポンサーリンク
スポンサーリンク

6日目のゴールと全体像

おみくじアプリ 6日目のテーマは 「履歴カードを“編集・色分け・アニメーション付き削除”できるようにし、DOM操作の応用力を一段階引き上げること」 です。

ここまでで、みなさんは

  • ランダム結果の表示
  • 結果ごとのメッセージ・背景色・ラッキーアイテム
  • 履歴保存(ローカルストレージ)
  • 履歴一覧表示・削除
  • 並び替え・詳細表示

という「小さな占いサービス」のようなアプリを作ってきました。

6日目ではここに 編集機能・色分け・アニメーション を加えて、 より“使いやすく・楽しい”おみくじアプリへ育てます。

履歴カードに「メモ編集」機能を追加する

履歴に「メモ」を追加できると、 自分だけの占い記録帳 として使えるようになります。

履歴オブジェクトに memo を追加する

4日目の履歴オブジェクトに memo を追加します。

const historyItem = {
  dateTime: dateTimeText,
  time: now.getTime(),
  result: randomResult,
  rank: data.rank,
  luckyItem: item,
  memo: "" // 新しく追加
};
JavaScript

メモ編集ボタンをカードに追加する

履歴カードの HTML を拡張します。

card.innerHTML = `
  <p>日時:${item.dateTime}</p>
  <p>結果:${item.result}(ランク:${item.rank})</p>
  <p>ラッキーアイテム:${item.luckyItem}</p>
  <p>メモ:${item.memo || "なし"}</p>
  <button class="history-detail-btn">詳細を見る</button>
  <button class="history-edit-btn">メモ編集</button>
  <button class="history-delete-btn">削除</button>
`;
JavaScript

メモ編集処理を作る

function editMemo(index) {
  const newMemo = prompt("メモを入力してください:");

  if (newMemo === null) return; // キャンセル時

  historyArray[index].memo = newMemo; // メモ更新
  saveHistory(historyArray);          // 保存
  renderHistory(historyArray);        // 再描画
}
JavaScript

深掘りポイント

  • prompt() は簡易入力ダイアログ
  • キャンセル時は null が返る
  • 履歴配列の該当 index の memo を書き換えるだけでOK
  • 「データ → 保存 → 再描画」の流れが重要

履歴カードを「結果のランク」で色分けする

視覚的にわかりやすくするため、 カードの背景色をランクに応じて変えます。

ランク → 色の例

  • rank 5(大吉) → 金色
  • rank 4(中吉) → 青
  • rank 3(小吉) → 緑
  • rank 2(吉) → 紫
  • rank 1(末吉) → オレンジ
  • rank 0(凶) → 赤

色を返す関数

function getCardColor(rank) {
  if (rank === 5) return "#fff8b3";
  if (rank === 4) return "#cce5ff";
  if (rank === 3) return "#d4edda";
  if (rank === 2) return "#e2d4ff";
  if (rank === 1) return "#ffe5b4";
  return "#f8d7da";
}
JavaScript

カード生成時に色を適用

card.style.backgroundColor = getCardColor(item.rank);
JavaScript

削除アニメーションを追加する

削除時にカードがふわっと消えると、 アプリの使い心地が大きく向上します。

CSS(削除アニメーション)

.fade-out {
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.4s, transform 0.4s;
}

JavaScript(削除処理)

function deleteHistory(index, cardElement) {
  cardElement.classList.add("fade-out");

  setTimeout(() => {
    historyArray.splice(index, 1);
    saveHistory(historyArray);
    renderHistory(historyArray);
  }, 400);
}
JavaScript

深掘りポイント

  • .classList.add() でCSSアニメーションを適用
  • setTimeout() でアニメーション後に削除
  • UIが滑らかになるとアプリの満足度が大きく上がる

6日目の完成コード(script.js)

const omikujiBtn = document.getElementById("omikujiBtn");
const result = document.getElementById("result");
const message = document.getElementById("message");
const luckyItem = document.getElementById("luckyItem");
const clearHistoryBtn = document.getElementById("clearHistoryBtn");
const sortNewBtn = document.getElementById("sortNewBtn");
const sortOldBtn = document.getElementById("sortOldBtn");

let historyArray = loadHistory();

// 結果候補
const results = ["大吉", "中吉", "小吉", "吉", "末吉", "凶"];

// 結果データ
const omikujiData = {
  "大吉": { rank: 5, message: "最高の一日になりそうです。" },
  "中吉": { rank: 4, message: "良い流れが来ています。" },
  "小吉": { rank: 3, message: "少しずつ前に進める日。" },
  "吉":   { rank: 2, message: "穏やかな一日。" },
  "末吉": { rank: 1, message: "準備をしておくと後で役に立ちます。" },
  "凶":   { rank: 0, message: "慎重に行動してみましょう。" }
};

// ラッキーアイテム
const luckyItems = [
  "赤いペン", "お気に入りの本", "コーヒー", "新しいノート",
  "青い服", "散歩", "音楽プレイリスト", "ストレッチ", "おいしいお菓子"
];

// ランダム結果
function getRandomResult() {
  const index = Math.floor(Math.random() * results.length);
  return results[index];
}

// ラッキーアイテム
function getRandomItem() {
  const index = Math.floor(Math.random() * luckyItems.length);
  return luckyItems[index];
}

// 背景色変更
function updateBackgroundByRank(rank) {
  if (rank === 5) document.body.style.backgroundColor = "#fff8b3";
  else if (rank === 4) document.body.style.backgroundColor = "#cce5ff";
  else if (rank === 3) document.body.style.backgroundColor = "#d4edda";
  else if (rank === 2) document.body.style.backgroundColor = "#e2d4ff";
  else if (rank === 1) document.body.style.backgroundColor = "#ffe5b4";
  else document.body.style.backgroundColor = "#f8d7da";
}

// カード色分け
function getCardColor(rank) {
  if (rank === 5) return "#fff8b3";
  if (rank === 4) return "#cce5ff";
  if (rank === 3) return "#d4edda";
  if (rank === 2) return "#e2d4ff";
  if (rank === 1) return "#ffe5b4";
  return "#f8d7da";
}

// 履歴保存
function saveHistory(arr) {
  localStorage.setItem("omikujiHistory", JSON.stringify(arr));
}

// 履歴読み込み
function loadHistory() {
  const data = localStorage.getItem("omikujiHistory");
  return data ? JSON.parse(data) : [];
}

// メモ編集
function editMemo(index) {
  const newMemo = prompt("メモを入力してください:");
  if (newMemo === null) return;

  historyArray[index].memo = newMemo;
  saveHistory(historyArray);
  renderHistory(historyArray);
}

// 詳細表示
function showDetail(item) {
  const detailBox = document.getElementById("detailBox");

  detailBox.innerHTML = `
    <div class="detail-card">
      <p>日時:${item.dateTime}</p>
      <p>結果:${item.result}</p>
      <p>ランク:${item.rank}</p>
      <p>ラッキーアイテム:${item.luckyItem}</p>
      <p>メモ:${item.memo || "なし"}</p>
    </div>
  `;
}

// 履歴描画
function renderHistory(historyArray) {
  const list = document.getElementById("historyList");
  list.innerHTML = "";

  historyArray.forEach((item, index) => {
    const card = document.createElement("div");
    card.className = "history-card";
    card.style.backgroundColor = getCardColor(item.rank);

    card.innerHTML = `
      <p>日時:${item.dateTime}</p>
      <p>結果:${item.result}(ランク:${item.rank})</p>
      <p>ラッキーアイテム:${item.luckyItem}</p>
      <p>メモ:${item.memo || "なし"}</p>
      <button class="history-detail-btn">詳細</button>
      <button class="history-edit-btn">メモ編集</button>
      <button class="history-delete-btn">削除</button>
    `;

    card.querySelector(".history-detail-btn").addEventListener("click", function() {
      showDetail(item);
    });

    card.querySelector(".history-edit-btn").addEventListener("click", function() {
      editMemo(index);
    });

    card.querySelector(".history-delete-btn").addEventListener("click", function() {
      deleteHistory(index, card);
    });

    list.appendChild(card);
  });
}

// 削除(アニメーション付き)
function deleteHistory(index, cardElement) {
  cardElement.classList.add("fade-out");

  setTimeout(() => {
    historyArray.splice(index, 1);
    saveHistory(historyArray);
    renderHistory(historyArray);
  }, 400);
}

// おみくじボタン
omikujiBtn.addEventListener("click", function() {
  const randomResult = getRandomResult();
  const data = omikujiData[randomResult];

  result.textContent = `今日の運勢は「${randomResult}」です`;
  message.textContent = data.message;

  updateBackgroundByRank(data.rank);

  const item = getRandomItem();
  luckyItem.textContent = `今日のラッキーアイテム:${item}`;

  const now = new Date();
  const dateTimeText = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${now.getHours()}:${now.getMinutes()}`;

  const historyItem = {
    dateTime: dateTimeText,
    time: now.getTime(),
    result: randomResult,
    rank: data.rank,
    luckyItem: item,
    memo: ""
  };

  historyArray.push(historyItem);
  saveHistory(historyArray);
  renderHistory(historyArray);
});

// 全履歴削除
clearHistoryBtn.addEventListener("click", function() {
  localStorage.removeItem("omikujiHistory");
  historyArray = [];
  renderHistory(historyArray);
});

// 並び替え
sortNewBtn.addEventListener("click", function() {
  historyArray.sort((a, b) => b.time - a.time);
  renderHistory(historyArray);
});

sortOldBtn.addEventListener("click", function() {
  historyArray.sort((a, b) => a.time - b.time);
  renderHistory(historyArray);
});

// 初期表示
renderHistory(historyArray);
JavaScript

6日目で本当に掴んでほしいこと

● DOM操作は「画面を作り変える力」

カードUI、編集、削除、アニメーションなど、 DOMを自在に扱えるとアプリが一気に本格的になる。

● ローカルストレージ+編集機能で“占い記録帳”になる

履歴が残り、編集できることで、 おみくじアプリは「自分の運勢ログ」に進化する。

● イベント処理は複数組み合わせると強力

  • click
  • DOM追加
  • ローカルストレージ保存
  • アニメーション

これらが組み合わさると、 ユーザーが気持ちよく使えるアプリになる。

次のステップ

7日目では、 アプリ全体を完成版として整理し、構造を最終調整する というステップへ進みます。

タイトルとURLをコピーしました