ปฏิทินเคลื่อนไหวโดยใช้ HTML + CSS + JS

Oct 17 2020

ฉันเป็นนักพัฒนามือใหม่และฉันกำลังสร้างปฏิทินสำหรับสมาร์ทโฟน ฉันใช้HTML, CSS และ JS ฉันยังไม่ได้ทำโปรเจ็กต์ทั้งหมด แต่ฉันมีความรู้สึกว่ากำลังสร้างรหัสที่ยุ่งเหยิง ฉันจะโพสต์ลิงก์ไปยัง git + รหัสที่นี่เพื่อให้คุณสามารถอ่าน / โคลนได้ดีขึ้นและช่วยฉันในการออกแบบรูปแบบ ฯลฯ ...

PS: โครงการนี้ได้รับการพัฒนาโดยใช้ความละเอียดของ iPhoneX (375 x 812) ยังไม่ตอบสนองดังนั้นฉันขอแนะนำให้รันโค้ดโดยใช้ความละเอียดด้านบน

ลิงค์ Git: https://github.com/LucasBiazi/Mobile_Calendar_HTML_CSS_JS

HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Calendar</title>
    <script src="../script/script.js"></script>
    <link rel="stylesheet" href="../css/style.css" />
  </head>
  <!-- As soon as the page loads, fulfills the table. -->
  <body onload="load_DB(new Date().getFullYear(), new Date().getMonth())">
    <div class="main">
      <div class="title">
        <span class="year_title" id="year_title"></span>
        <span class="month_title" id="month_title"></span>
      </div>
      <div class="calendar">
        <div id="month_days" class="month_days">
          <table id="days">
            <tr>
              <th style="color: lightsalmon">Sun</th>
              <th class="even">Mon</th>
              <th>Tue</th>
              <th class="even">Wed</th>
              <th>Thu</th>
              <th class="even">Fri</th>
              <th style="color: lightsalmon">Sat</th>
            </tr>
          </table>
        </div>
        <!-- Future implementations. -->
        <div class="data">
          <div class="data_content">
            <p style="color: black; font-size: 20px">No content.</p>
          </div>
        </div>
      </div>
      <div class="buttons">
        <!-- Reteats a month. -->
        <button
          style="
            animation: display_button_back 1s ease;
            position: relative;
          "
          onclick="retreat_month(parseInt(document.getElementById('year_title').textContent), 
        identify_month(document.getElementById('month_title').textContent))"
        >
          <img
            src="../../media/left-arrow-line-symbol.svg"
            style="width: 35px; height: 35px"
            alt="back"
          />
        </button>
        <!-- Shows current month. -->
        <button
          style="animation: display_opacity_zoom 1s ease-out;"
          onclick="advance_month(new Date().getFullYear(), new Date().getMonth() - 1)"
        >
          T
        </button>
        <!-- Advances a month. -->
        <button
          style="
            animation: display_button_next 1s ease;
            position: relative;
          "
          onclick="advance_month(parseInt(document.getElementById('year_title').textContent), 
          identify_month(document.getElementById('month_title').textContent))"
        >
          <img
            src="../../media/right-arrow-angle.svg"
            style="width: 35px; height: 35px"
            alt="next"
          />
        </button>
      </div>
    </div>
  </body>
</html>

CSS:

* {
  padding: 0px;
  margin: 0px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;
  font-weight: lighter;
}

body {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background-image: url(../../media/grass.jpg);
  /* Blurring the background. Applies behind the element... */
  backdrop-filter: blur(9px);
  background-size: cover;
}

@keyframes display_data {
  0% {
    transform: scale3d(0.25, 0, 1);
  }
  25% {
    transform: scale3d(0.5, 0, 1);
  }
  50% {
    transform: scale3d(0.1, 0, 1);
  }
  75% {
    transform: scale3d(0.1, 1.2, 1);
  }
  100% {
    transform: scale3d(1, 1, 1);
  }
}
@keyframes display_month_days {
  from {
    opacity: 0%;
  }
  to {
    opacity: 100%;
  }
}
@keyframes display_button_back {
  0% {
    right: 25px;
    transform: scale3d(0.75, 0.75, 1);
  }
  100% {
    right: 0px;
    transform: scale3d(1, 1, 1);
  }
}
@keyframes display_button_next {
  0% {
    left: 25px;
    transform: scale3d(0.75, 0.75, 1);
  }
  100% {
    left: 0px;
    transform: scale3d(1, 1, 1);
  }
}
@keyframes display_opacity_zoom {
  from {
    opacity: 0%;
    transform: scale3d(0.5, 0.5, 1);
  }
  to {
    opacity: 100%;
    transform: scale3d(1, 1, 1);
  }
}
.main {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  color: white;
  background-color: rgba(0, 0, 0, 0.65);
}

.title {
  margin-top: 7%;
  height: 80px;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  animation: display_opacity_zoom 1s ease-out;
}

.year_title {
  margin-left: 5px;
  font-size: 40px;
  letter-spacing: 5px;
  color: lightsalmon;
  text-align: center;
}

.month_title {
  margin-left: 15px;
  font-size: 25px;
  letter-spacing: 15px;
  text-align: center;
}

.calendar {
  height: 75%;
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}

.month_days {
  margin-top: 10px;
  width: 100%;
  height: 50%;
  animation: display_month_days 1s ease-in-out;
}

table {
  margin-top: 20px;
  width: 100%;
  font-size: 22px;
}

tr,
th,
td {
  background-color: none;
}

th {
  width: 14%;
  text-align: center;
  color: white;
}

td {
  width: 2.38em;
  height: 2.38em;
  color: white;
  text-align: center;
  border-radius: 50%;
}

td:hover {
  background-color: black;
}

.data {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 95%;
  height: 30%;
  background-color: rgba(255, 255, 255, 0.9);
  border: none;
  border-radius: 5px;
  animation: display_data 0.8s ease;
}

.data_content {
  width: 95%;
  height: 95%;
}

.other_month {
  background: none;
  color: rgba(175, 175, 175, 0.45);
}

.buttons {
  width: 100vw;
  height: 70px;
  display: flex;
  justify-content: space-around;
  align-items: flex-start;
}

button {
  width: 60px;
  height: 60px;
  display: flex;
  justify-content: center;
  align-items: center;
  background: none;
  border: none;
  font-size: 35px;
  font-weight: 400;
  color: white;
}

JS:

// Returns the day of the week in which the month starts.
starting_day = (year, month) => new Date(year, month, 1).getDay();

// Returns the amount of days in a month.
total_month_days = (year, month) => new Date(year, month + 1, 0).getDate();

// Set the background color of the cell that contains today's day.
function is_today(months, cell) {
  if (
    new Date().getFullYear() ==
      parseInt(document.getElementById("year_title").textContent) &&
    months[new Date().getMonth()].month ==
      document.getElementById("month_title").textContent &&
    cell.textContent == new Date().getDate()
  ) {
    cell.style.background = "rgba(255, 160, 122, 0.8)";
  }
}

// Changes color if it is a weekend.
color_weekend = (let_value, cell) => {
  if (let_value == 6 || let_value == 0) cell.style.color = "lightsalmon";
};

// Populates the DB.
function load_DB(year, month) {
  let counter = 1;
  const table = document.getElementById("days");
  const date_object = new Date(year, month);

  // Array containing months names, starting_day()_+ total_month_days().
  const months = [
    {
      // Month 0
      month: "January",
      first_day: starting_day(date_object.getFullYear(), 0),
      days: Array(total_month_days(date_object.getFullYear(), 0)),
    },
    {
      // Month 1
      month: "February",
      first_day: starting_day(date_object.getFullYear(), 1),
      days: Array(total_month_days(date_object.getFullYear(), 1)),
    },
    {
      // Month 2
      month: "March",
      first_day: starting_day(date_object.getFullYear(), 2),
      days: Array(total_month_days(date_object.getFullYear(), 2)),
    },
    {
      // Month 3
      month: "April",
      first_day: starting_day(date_object.getFullYear(), 3),
      days: Array(total_month_days(date_object.getFullYear(), 3)),
    },
    {
      // Month 4
      month: "May",
      first_day: starting_day(date_object.getFullYear(), 4),
      days: Array(total_month_days(date_object.getFullYear(), 4)),
    },
    {
      // Month 5
      month: "June",
      first_day: starting_day(date_object.getFullYear(), 5),
      days: Array(total_month_days(date_object.getFullYear(), 5)),
    },
    {
      // Month 6
      month: "July",
      first_day: starting_day(date_object.getFullYear(), 6),
      days: Array(total_month_days(date_object.getFullYear(), 6)),
    },
    {
      // Month 7
      month: "August",
      first_day: starting_day(date_object.getFullYear(), 7),
      days: Array(total_month_days(date_object.getFullYear(), 7)),
    },
    {
      // Month 8
      month: "September",
      first_day: starting_day(date_object.getFullYear(), 8),
      days: Array(total_month_days(date_object.getFullYear(), 8)),
    },
    {
      // Month 9
      month: "October",
      first_day: starting_day(date_object.getFullYear(), 9),
      days: Array(total_month_days(date_object.getFullYear(), 9)),
    },
    {
      // Month 10
      month: "November",
      first_day: starting_day(date_object.getFullYear(), 10),
      days: Array(total_month_days(date_object.getFullYear(), 10)),
    },
    {
      // Month 11
      month: "December",
      first_day: starting_day(date_object.getFullYear(), 11),
      days: Array(total_month_days(date_object.getFullYear(), 11)),
    },
  ];

  // Prints the name of the current month and year.
  document.getElementById("year_title").textContent = date_object.getFullYear();
  document.getElementById("month_title").textContent =
    months[date_object.getMonth()].month;

  // Month days that will appear in the first row.
  const number_of_cells_in_the_first_row =
    7 - months[date_object.getMonth()].first_day;

  // Month days that will appear after the first row.
  let normal_days = number_of_cells_in_the_first_row + 1;

  // Creates + populates the 5 last rows.
  for (let r = 0; r < 5; r++) {
    let row = table.insertRow(r + 1);
    let cell;
    // Creates + populates 7 cells in each row.
    for (let x = 0; x < 7; x++) {
      cell = row.insertCell(x);
      cell.textContent = normal_days;
      is_today(months, cell);
      color_weekend(x, cell);
      normal_days++;
      // Filling the rest of the table with the days of the next month(gray days).
      if (normal_days > months[date_object.getMonth()].days.length + 1) {
        // Next month days are always lowlighted.
        if (x == 6 || x == 0) cell.style.color = "rgba(175, 175, 175, 0.45)";
        cell.textContent = counter++;
        cell.className = "other_month";
      }
    }
  }

  // Creates + populates the 1 row.
  for (let i = 0; i < 1; i++) {
    let row = table.insertRow(i + 1);
    let cell;
    let number_of_blank_cells = 7 - number_of_cells_in_the_first_row;
    // Populates it.
    for (let y = 0; y < number_of_cells_in_the_first_row; y++) {
      cell = row.insertCell(0);
      cell.textContent = number_of_cells_in_the_first_row - y;
      is_today(months, cell);
      color_weekend(y, cell);
    }
    // Filling the rest of the table (next month days).
    for (let w = 0; w < number_of_blank_cells; w++) {
      cell = row.insertCell(0);
      if (months[date_object.getMonth() - 1]) {
        cell.textContent = months[date_object.getMonth() - 1].days.length--;
        cell.className = "other_month";
      } else {
        cell.textContent = months[date_object.getMonth() + 11].days.length--;
        cell.className = "other_month";
      }
    }
  }
}

// Converts the month name to the month number (January = 0).
function identify_month(string) {
  const all_months = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
  ];
  let i = 0;
  while (string != all_months[i]) {
    i++;
  }
  return i;
}

// Advancecs a month.
function advance_month(year, month) {
  // Cleaning table.
  const table = document.getElementById("days");
  for (let i = 0; i < 6; i++) {
    table.deleteRow(1);
  }
  // Add new data.
  month++;
  load_DB(year, month);
}

// Retreats a month.
function retreat_month(year, month) {
  // Cleaning table.
  const table = document.getElementById("days");
  for (let i = 0; i < 6; i++) {
    table.deleteRow(1);
  }
  month--; // Add new data.
  load_DB(year, month);
}

คำตอบ

6 SᴀᴍOnᴇᴌᴀ Oct 17 2020 at 12:28

ยินดีต้อนรับสู่ Code Review CSS ดูดีทีเดียว ฉันไม่เห็นสไตล์ชีตที่มีสีมากเกินไปlightsalmon😃 ด้านล่างนี้คือคำแนะนำบางประการ

การอ้างอิง Cache DOM

"... การเข้าถึง DOM มีค่าใช้จ่ายค่อนข้างสูง - ฉันคิดว่าถ้าฉันมีสะพานเช่นที่ดินสองชิ้นที่มีสะพานเก็บค่าผ่านทางและเครื่องมือ JavaScript อยู่ที่ด้านหนึ่งและ DOM อยู่อีกด้านหนึ่งและ ทุกครั้งที่ฉันต้องการเข้าถึง DOM จาก
    เอ็นจิ้นJavaScript ฉันต้องจ่ายค่าผ่านทางนั้น” - John Hrvatin, Microsoft, MIX09 ในการพูดคุยเกี่ยวกับการสร้างเว็บแอปพลิเคชั่นและไซต์ประสิทธิภาพสูงเมื่อเวลา 29:38 น. ซึ่งอ้างถึงในO'Reilly หนังสือJavascriptโดย Nicholas C Zakas หน้า 36เช่นเดียวกับที่กล่าวถึงในโพสต์นี้

โปรดจำไว้ว่ามีการระบุไว้เมื่อ 10 ปีก่อนและเบราว์เซอร์มีแนวโน้มที่จะมาพร้อมกันตั้งแต่นั้นมาก็ยังควรระวัง อ้างอิง Store เพื่อองค์ประกอบ DOM - เช่นdocument.getElementById("year_title")ใน variables- นี้อาจเกิดขึ้นในการเรียกกลับไปยังเหตุการณ์DOMContentLoaded

let titleEl;
window.addEventListener('DOMContentLoaded', (event) => {
    titleEl = document.getElementById("year_title");
});

ดังที่โจชัวกล่าวไว้ตัวจัดการเหตุการณ์ใน HTML ถูกกำหนดให้เป็นไปตามมาตรฐานของวันนี้และการเรียกกลับนั้นจะเป็นสถานที่ที่เหมาะสมที่จะให้โค้ดอยู่ในonloadคุณลักษณะขององค์ประกอบของร่างกาย

ชื่อเดือน

ดังที่ Joshua ได้กล่าวถึงรหัสสำหรับเดือนนั้นค่อนข้างซ้ำซาก แทนที่จะเก็บชื่อเดือนDate.prototype.toLocaleDateString()สามารถใช้เพื่อสร้างชื่อเดือนแบบไดนามิกอาจเป็นภาษาที่ระบุโดยผู้ใช้และ / หรือnavigator.languageถ้ามี

ชื่อฟังก์ชันที่อ่านได้มากขึ้น

// Set the background color of the cell that contains today's day.
function is_today(months, cell) {

ตัดสินโดยชื่อฉันเดาว่าฟังก์ชันจะส่งคืนบูลีนที่ระบุว่าค่าบางอย่างเป็นวันนี้หรือไม่แม้ว่าจะได้รับความคิดเห็นและการใช้งานที่ไม่เป็นเช่นนั้นก็ตาม อาจมีชื่อที่เหมาะสมstyle_todays_date_cellกว่า

ความเสมอภาคที่เข้มงวด

เป็นนิสัยที่ดีที่จะใช้ตัวดำเนินการเปรียบเทียบที่เข้มงวดเช่น===และ!==เมื่อเป็นไปได้ - เช่นสำหรับการเปรียบเทียบนี้ภายในload_DB():

if (x == 6 || x == 0) cell.style.color = "rgba(175, 175, 175, 0.45)";

ส่วนเพิ่มก่อนแก้ไข

บรรทัดเหล่านี้:

month++;
load_DB(year, month);

สามารถรวมกันได้เมื่อค่าของmonthเพิ่มขึ้นก่อนการโทรโดยใช้ตัวดำเนินการเพิ่มคำนำหน้า :

 load_DB(year, ++month);

รูปแบบอินไลน์ซ้ำซ้อน -> CSS

ภาพภายในปุ่มมีลักษณะแบบอินไลน์เช่น

 style="width: 35px; height: 35px"

ที่สามารถย้ายไปยังสไตล์ชีต:

button img {
    width: 35px;
    height: 35px;
}

สไตล์อินไลน์สำหรับส่วนหัวตาราง

เซลล์แรกและเซลล์สุดท้ายมีลักษณะแบบอินไลน์เพื่อแทนที่thกฎเริ่มต้น:

<th style="color: lightsalmon">Sun</th>

ที่สามารถย้ายไปยังสไตล์ชีตได้เช่นกันโดยใช้ตัวเลือกที่เฉพาะเจาะจงมากขึ้นเช่นตัวเลือกหลอก:first-childและ:last-child:

th:first-child,
th:last-child {
    color: lightsalmon;
}
          

ทางเลือกอื่น ๆ รวมถึง:first-of-typeและ:last-of-typeเทคนิคนั้นยังสามารถใช้เพื่อย้ายบรรทัดด้านบนออกจาก JavaScript และไปยังสไตล์ชีตโดยใช้tdแทนtr:

if (x == 6 || x == 0) cell.style.color = "rgba(175, 175, 175, 0.45)";

PS: คำตอบนั้นประกอบด้วยiPhone VII +

6 JoshuaHall Oct 17 2020 at 04:42

ตัวแปร

ใช้การประกาศตัวแปรทุกครั้งเมื่อประกาศตัวแปรของคุณเป็นครั้งแรก หากคุณไม่ใส่ตัวแปรของคุณไว้ในขอบเขตส่วนกลางซึ่งแทบจะไม่เป็นไปตามที่คุณต้องการเลย นอกจากนี้ยังทำให้ชัดเจนยิ่งขึ้นว่าคุณไม่ต้องการกำหนดซ้ำให้กับตัวแปรส่วนกลางที่มีอยู่แล้ว

ดังนั้นสำหรับstarting_dayตัวอย่างเช่นใส่var, letหรือconstในด้านหน้าของมัน

แอตทริบิวต์ HTML

แทนที่จะใช้แอตทริบิวต์เช่นonclickหรือonloadเพื่อจัดการเหตุการณ์ (เรียกอีกอย่างว่าตัวจัดการแบบอินไลน์) โดยทั่วไปถือว่าเป็นแนวทางปฏิบัติที่ดีที่สุดในการจัดการเหตุการณ์เหล่านี้ใน JavaScript ของคุณเอง ช่วยแยก HTML / JavaScript ของคุณ

ตัวอย่างเช่นในการแทนที่onloadแอตทริบิวต์คุณสามารถใช้ฟังก์ชัน JS ต่อไปนี้แทน:

function ready(func) {
  if (document.readyState !== 'loading') {
    func();
  } else {
    document.addEventListener('DOMContentLoaded', func);
  }
}

คุณสามารถประกาศฟังก์ชันที่เรียกว่าmainตัวอย่างเช่นซึ่งคุณจะใส่รหัสใด ๆ ที่คุณต้องการเรียกใช้เมื่อ DOM พร้อมที่จะใช้งานแล้วจึงเรียกready(main);ใช้

ในการแทนที่onclickแอตทริบิวต์คุณควรเพิ่มคลาส / รหัส CSS เพื่อใช้เป็นตัวเลือกในโค้ด JavaScript const backButton = document.querySelector('calendarBack');แล้วคุณจะเลือกใช้สิ่งที่ต้องการ จากนั้นคุณสามารถแนบตัวฟังเหตุการณ์เข้ากับองค์ประกอบนั้นได้ดังนี้:

backButton.addEventListener("click", () => {
   // The code you want to run when the button is clicked goes here
});

การอ้างอิงรหัส

months

การmonthsประกาศตัวแปรค่อนข้างซ้ำซากและมีเพียงสิ่งเดียวที่เปลี่ยนไปในแต่ละเดือนนั่นคือ 'ดัชนี' ของมัน สิ่งนี้ทำให้ผู้สมัครที่สมบูรณ์แบบในการม้วนเข้าสู่แผนที่ลูป / อาร์เรย์

ฉันจะทำเช่นนี้โดยใช้mapฟังก์ชัน :

// This would ideally not be in load_DB but be a top level declaration, since you could then reference this again in the identify_month function instead of rewriting the months
const monthNames = [
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December",
];

// Saving this in a variable for brevity and since it doesn't change
const fullYear = date_object.getFullYear();

const months = monthNames.map((monthName, index) => {
  return {
    month: monthName,
    first_day: starting_day(fullYear, index),
    days: Array(total_month_days(fullYear, index)),
  };
});

สิ่งหนึ่งที่ฉันไม่แน่ใจคือdaysคุณสมบัติและเหตุใดจึงเป็นอาร์เรย์ที่จะเริ่มต้นด้วย สถานที่เดียวที่ถูกอ้างอิงในรหัสคือการตรวจสอบความยาว นี่เป็นส่วนหนึ่งของรหัสที่ยังไม่เสร็จสมบูรณ์ที่คุณกล่าวถึงหรือไม่ ถ้าไม่ใช่ฉันจะแทนที่บรรทัดด้วยวัน: Array(total_month_days(fullYear, index)),แทน

identify_month

การใช้งานฟังก์ชันนี้แบบดั้งเดิมควรใช้งานได้แม้ว่าจะเป็นวิธีที่ยาวในการพูดว่า "รับดัชนีของเดือนจากmonthNamesอาร์เรย์"

นี่คือวิธีที่ฉันจะเขียนใหม่:

// Converts the month name to the month number (January = 0).
function identify_month(string) {
  return monthNames.indexOf(string);
}

รหัสเดือนปัจจุบัน

รหัสเดิมค่อนข้างอ่านยากเนื่องจากการค้นหาซ้ำของวัตถุเดือนที่พบในmonthsอาร์เรย์ วางไว้ในตัวแปรเพื่อปรับปรุงความสามารถในการอ่านและลบการเข้าถึงอาร์เรย์เพิ่มเติมที่ไม่จำเป็น

const currentMonth = date_object.getMonth();

const currentMonthObj = months[currentMonth];

// Prints the name of the current month and year.
document.getElementById("year_title").textContent = fullYear;
document.getElementById("month_title").textContent = currentMonthObj.month;

// Month days that will appear in the first row.
const number_of_cells_in_the_first_row = 7 - currentMonthObj.first_day;

ฉันจะพยายามกลับมาและเพิ่มความคิดในภายหลัง :)