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?

javascriptでおしゃれでカスタム可能なカレンダー

Last updated at Posted at 2025-01-07

初めに

javascriptでおしゃれなカレンダーを作ろう取り組んだコードを残します。

完成品

スクリーンショット 2025-01-07 201555.png

構成

構成は一般的なjavascriptの構成ですので、index.html、style.css、calendar.jsです。
カスタマイズしやすいようにclass名等を細かく決めています。
色々挑戦したので不要なコードも残っているかと思いますがお許しください。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>カレンダー</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div class="container">
      <div class="content">
        <button id="year_button"></button>
        <button id="year_prev">▲</button>
        <button id="year_next">▼</button>
        <button id="prev">◀◀</button>
        <button id="next">▶▶</button>
        <div id="calendar"></div>
        <div id="modal" style="display: none">
          <div class="modal-container">
            <select id="year-select"></select>
            <button id="modal-ok">OK</button>
            <button id="modal-cancel">Cancel</button>
          </div>
        </div>
      </div>
    </div>
    <script src="calendar.js"></script>
  </body>
</html>
style.css
/* カレンダーのテーブル全体 */
.container {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  padding: 10px;
}
.content {
  position: relative;
  width: 400px;
  height: 360px;
  padding: 10px;
  font-size: 18px;
  font-style: italic;
  margin: 10px auto;
  justify-content: center;
}

#calendar {
  margin: 0 auto 10px;
  padding: 0;
}

button#year_button {
  position: absolute;
  font-size: 30px;
  border: none;
  padding: 1px 15px 0;
  border-radius: 5px;
  background-color: #0074d0;
  color: white;
  font-style: italic;
  top: 30px;
  left: 12px;
  box-shadow: 3px 3px 3px -3px black;
  transition: background-color 0.3s ease, color 0.3s ease;
}
button#year_button:hover {
  background-color: #6df049;
}
button#prev,
button#next {
  font-size: 20px;
  font-style: italic;
  position: absolute;
  color: #0074d0;
  background-color: transparent;
  border: none;
  top: 54px;
  right: 64px;
}
button#next {
  right: 8px;
}
button#prev:hover,
button#next:hover {
  color: #6df049;
}
button#year_prev,
button#year_next {
  font-size: 12px;
  font-style: italic;
  position: absolute;
  transform: scale(5, 1);
  color: #0074d0;
  background-color: transparent;
  border: none;
  top: 15.2px;
  left: 70px;
}
button#year_next {
  top: 65.5px;
}
button#year_prev:hover,
button#year_next:hover {
  color: #6df049;
}

table {
  border-collapse: collapse;
  background-color: rgb(255, 255, 255);
  width: 100%;
  margin: 80px auto;
  position: relative;
  padding-top: 50px;
}

table tr:first-child td {
  background-color: #0074d0;
  font-size: 18px;
  color: white !important;
  font-style: italic;
}

table .buttons {
  display: flex;
  gap: 10px;
}

td {
  text-align: center;
  padding: 4px;
  font-size: 18px;
  height: 30px;
  border: 2px solid #b9c7da;
  width: 14.28%;
  position: relative;
}

td.holiday {
  color: #e41414;
}

td.clickable {
  cursor: pointer;
  transition: background-color 0.3s;
}

td.clickable:hover {
  background-color: rgb(92, 238, 131);
}

table td:first-child {
  color: #e41414;
}

table td:nth-child(7) {
  color: #1152c1;
}
/* モーダルのスタイル */
#modal {
  position: absolute;
  width: 200px;
  height: 51px;
  margin-top: 0;
  font-size: 24px;
  background-color: white;
  border: solid 4px;
  border-radius: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  top: 18.5px;
  z-index: 2;
}

select#year-select {
  font-size: 21px;
  font-style: italic;
  background-color: transparent;
  border: none;
}

button#modal-ok,
button#modal-cancel {
  border: none;
  background-color: #0074d0;
  color: white;
  font-size: 19px;
  padding: 1px 7px 1px 3px !important;
  font-style: italic;
  border-radius: 5px;
}
button#modal-cancel {
  background-color: red;
}
calendar.js
let holidays = {}; // 祝日データを格納するオブジェクト

// 国民の祝日 API から祝日データを取得
async function fetchHolidays(year) {
  const response = await fetch(
    `https://holidays-jp.github.io/api/v1/date.json`
  );
  if (response.ok) {
    const data = await response.json();
    // 指定された年の祝日を抽出
    for (const [date, name] of Object.entries(data)) {
      if (date.startsWith(year)) {
        holidays[date] = name;
      }
    }
  } else {
    console.error("祝日データの取得に失敗しました。");
  }
}

// カレンダーを生成する関数
async function renderCalendar(year, month) {
  // 祝日データを取得(まだ未取得の場合のみ)
  if (Object.keys(holidays).length === 0) {
    await fetchHolidays(year);
  }

  const startDate = new Date(year, month - 1, 1); // 月の最初の日
  const endDate = new Date(year, month, 0); // 月の末日
  const endDayCount = endDate.getDate(); // 月の日数
  const startDay = startDate.getDay(); // 月の最初の曜日
  let dayCount = 1; // 日にちカウンター
  let calendarHtml = ""; // カレンダーHTML

  // calendarHtml += "<h1>" + year + "/" + month + "</h1>";
  calendarHtml += "<table>";
  calendarHtml += "<tr>";

  const weeks = ["Sun", "Mon", "The", "Wed", "Thu", "Fri", "Sat"];
  for (let i = 0; i < weeks.length; i++) {
    calendarHtml += "<td>" + weeks[i] + "</td>";
  }
  calendarHtml += "</tr>";

  for (let w = 0; w < 6; w++) {
    calendarHtml += "<tr>";
    for (let d = 0; d < 7; d++) {
      if (w == 0 && d < startDay) {
        calendarHtml += "<td></td>"; // 月初まで空白を埋める
      } else if (dayCount > endDayCount) {
        calendarHtml += "<td></td>"; // 月末を超えた場合
      } else {
        const currentDate = `${year}-${String(month).padStart(2, "0")}-${String(
          dayCount
        ).padStart(2, "0")}`;
        let className = "clickable";
        if (d === 0) className += " sunday"; // 日曜日
        if (d === 6) className += " saturday"; // 土曜日
        if (holidays[currentDate]) className += " holiday"; // 祝日

        calendarHtml += `<td class="${className}" data-date="${currentDate}"><span class="date">${dayCount}</span></td>`;
        dayCount++;
      }
    }
    calendarHtml += "</tr>";
  }
  calendarHtml += "</table>";

  calendarHtml += "<tr class='calendar-header'>";
  // ここでボタンに年と月を設定
  const yearButton = document.getElementById("year_button");
  yearButton.textContent = `${year} / ${month}`; // 年と月をボタンに表示
  document.querySelector("#calendar").innerHTML = calendarHtml;

  // 最終行が空であれば非表示にする
  const rows = document.querySelectorAll("#calendar table tr");
  const lastRow = rows[rows.length - 1];
  let isEmpty = true;
  lastRow.querySelectorAll("td").forEach((cell) => {
    if (cell.textContent.trim() == "") {
      isEmpty = true;
    }
  });

  if (isEmpty) {
    lastRow.style.display = "none";
  }

  const firstRow = rows[1];
  const firstCell = firstRow.querySelector("td");
  const lastRow2 = rows[rows.length - 2];
  if (firstCell.textContent.trim() !== "" && endDayCount === 28) {
    console.log(firstCell.textContent.trim());
    lastRow2.style.display = "none";
  }

  // 日付をクリックしたときのイベント
  document.querySelectorAll(".clickable").forEach((cell) => {
    cell.addEventListener("click", (e) => {
      const targetCell = e.target.closest("td");
      const selectedDate = targetCell?.dataset.date;
      if (holidays[selectedDate]) {
        alert(
          `選択された日付: ${selectedDate}\n祝日: ${holidays[selectedDate]}`
        );
      } else {
        alert(`選択された日付: ${selectedDate}`);
      }
    });
  });
}

// 初期化
document.addEventListener("DOMContentLoaded", async () => {
  const today = new Date();
  await renderCalendar(today.getFullYear(), today.getMonth() + 1);

  document.getElementById("prev").addEventListener("click", async () => {
    today.setMonth(today.getMonth() - 1);
    await renderCalendar(today.getFullYear(), today.getMonth() + 1);
  });

  document.getElementById("next").addEventListener("click", async () => {
    today.setMonth(today.getMonth() + 1);
    await renderCalendar(today.getFullYear(), today.getMonth() + 1);
  });

  //一年移動
  document.getElementById("year_prev").addEventListener("click", async () => {
    today.setFullYear(today.getFullYear() - 1);
    await renderCalendar(today.getFullYear(), today.getMonth() + 1);
  });

  document.getElementById("year_next").addEventListener("click", async () => {
    today.setFullYear(today.getFullYear() + 1);
    await renderCalendar(today.getFullYear(), today.getMonth() + 1);
  });

  // モーダル内のイベントを設定
  document.getElementById("year_button").addEventListener("click", openModal);
  console.log(document.getElementById("year_button"));

  // 年選択肢を生成する関数
  function populateYearOptions() {
    const yearSelect = document.getElementById("year-select");
    const currentYear = new Date().getFullYear();
    if (!yearSelect) {
      console.error("Year select element not found.");
      return;
    }
    console.log(yearSelect);
    yearSelect.innerHTML = ""; // 初期化
    for (let year = 2100; year >= 1924; year--) {
      const option = document.createElement("option");
      option.value = year;
      option.textContent = year;

      // 現在の年を初期値として選択
      if (year === currentYear) {
        option.selected = true;
      }
      yearSelect.appendChild(option);
    }
  }

  // ページロード時に年選択肢を生成
  document.addEventListener("DOMContentLoaded", () => {
    populateYearOptions();
  });

  populateYearOptions();

  document.getElementById("modal-ok").addEventListener("click", async () => {
    const yearSelect = document.getElementById("year-select");
    const selectedYear = parseInt(yearSelect.value, 10);

    if (!isNaN(selectedYear)) {
      today.setFullYear(selectedYear); // 選択した年に移動
      await renderCalendar(today.getFullYear(), today.getMonth() + 1);
      closeModal(); // モーダルを閉じる
    } else {
      alert("有効な年を選択してください");
    }
  });

  document.getElementById("modal-cancel").addEventListener("click", closeModal);

  // モーダルを開く関数
  function openModal() {
    document.getElementById("modal").style.display = "flex";
  }

  // モーダルを閉じる関数
  function closeModal() {
    document.getElementById("modal").style.display = "none";
  }
});
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?