Animierter Kalender mit HTML + CSS + JS

Oct 17 2020

Ich bin ein Anfänger-Entwickler und erstelle einen Kalender für Smartphones. Ich benutze HTML, CSS und JS. Ich bin noch nicht ganz mit dem Projekt fertig, aber ich habe das Gefühl, dass ich einen chaotischen Code mache . Ich werde den Link zum Git + den Code hier veröffentlichen, damit Sie ihn besser lesen / klonen und mir bei Designmustern usw. helfen können.

PS: Das Projekt wurde mit der IPhoneX-Auflösung (375 x 812) entwickelt. Es reagiert noch nicht, daher würde ich empfehlen, den Code mit der oben genannten Auflösung auszuführen.

Git Link: 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);
}

Antworten

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

Willkommen bei Code Review. Das CSS sieht ganz gut aus. Ich habe nicht zu viele Stylesheets mit Farbe lightsalmon😃 gesehen. Nachfolgend einige Vorschläge.

Cache-DOM-Referenzen

”... DOM-Zugriff ist eigentlich ziemlich kostspielig - ich denke, wenn ich eine Brücke habe - wie zwei Landstücke mit einer Mautbrücke, und die JavaScript-Engine befindet sich auf der einen Seite und das DOM auf der anderen Seite und Jedes Mal, wenn ich über die JavaScript-Engine auf das DOM zugreifen möchte, muss ich diese Gebühr zahlen. “
    - John Hrvatin, Microsoft, MIX09, in diesem Vortrag zum Erstellen von Hochleistungswebanwendungen und -sites um 29:38 Uhr, ebenfalls im O'Reilly zitiert Javascript Buch von Nicholas C Zakas Pg 36 , sowie in diesem Beitrag erwähnt

In Anbetracht dessen, dass dies vor mehr als 10 Jahren angegeben wurde und Browser wahrscheinlich seitdem auf dem richtigen Weg sind, ist es immer noch ratsam, sich dessen bewusst zu sein. Speichern Sie Verweise auf DOM-Elemente - z. B. document.getElementById("year_title")in Variablen - dies kann bei einem Rückruf auf das DOMContentLoadedEreignis geschehen .

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

Wie Joshua erwähnte, sind Ereignishandler in HTML nach heutigen Maßstäben verpönt, und dieser Rückruf wäre ein geeigneter Ort, um den Code derzeit im onloadAttribut des body-Elements zu haben.

Monatsnamen

Wie Joshua bereits erwähnt hat, wiederholt sich der Code für die Monate ziemlich oft. Anstatt die Monatsnamen zu speichern, können die Monatsnamen Date.prototype.toLocaleDateString()dynamisch generiert werden, möglicherweise in einer vom Benutzer angegebenen Sprache und / oder navigator.languagewenn sie verfügbar sind.

Besser lesbarer Funktionsname

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

Nach dem Namen zu urteilen, würde ich vermuten, dass die Funktion einen Booleschen Wert zurückgibt, der angibt, ob heute ein Wert vorliegt, obwohl dies angesichts des Kommentars und der Implementierung nicht der Fall ist. Ein passenderer Name könnte sein style_todays_date_cell.

Strikte Gleichheit

Es ist eine gute Angewohnheit, strenge Vergleichsoperatoren zu verwenden - dh ===und !==wenn möglich - für diesen Vergleich innerhalb von load_DB():

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

Inkrement vorfixieren

Diese Zeilen:

month++;
load_DB(year, month);

Kann kombiniert werden, wenn der Wert von monthvor dem Aufruf mit dem Präfix-Inkrement-Operator erhöht wird :

 load_DB(year, ++month);

redundante Inline-Stile -> CSS

Die Bilder in den Schaltflächen haben Inline-Stile, z

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

Das kann in das Stylesheet verschoben werden:

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

Inline-Stile für Tabellenüberschriften

Die erste und die letzte Zelle haben Inline-Stile, um die Standardregel zu überschreiben th:

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

Dies kann auch mit spezifischeren Selektoren in das Stylesheet verschoben werden, z. B. Pseudo-Selektoren :first-childund :last-child:

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

Zu den Alternativen gehören :first-of-typeund :last-of-typeDiese Technik kann auch verwendet werden, um die obige Linie aus dem JavaScript heraus und in das Stylesheet zu verschieben, indem tdanstelle von tr:

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

PS: Die Antwort wurde mit dem IPhone VII + verfasst

6 JoshuaHall Oct 17 2020 at 04:42

Variablen

Verwenden Sie immer eine Variablendeklaration, wenn Sie Ihre Variable zum ersten Mal deklarieren. Wenn Sie dies nicht tun, wird Ihre Variable in den globalen Bereich verschoben , was fast nie das ist, was Sie wollen. Außerdem wird klarer, dass Sie einer bereits vorhandenen globalen Variablen keine Neuzuweisung zuweisen möchten.

Also für starting_day, zum Beispiel, setzen Sie ein var, letoder constvor ihm.

HTML-Attribute

Anstatt Attribute wie onclickoder onloadfür die Ereignisbehandlung zu verwenden (diese werden auch als Inline-Handler bezeichnet), wird es im Allgemeinen als bewährte Methode angesehen, diese Ereignisse in Ihrem JavaScript selbst zu behandeln. Es hilft, Ihr HTML / JavaScript zu trennen.

Um das onloadAttribut beispielsweise zu ersetzen , können Sie stattdessen die folgende JS-Funktion verwenden:

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

Sie können beispielsweise eine aufgerufene Funktion deklarieren main, in die Sie den Code einfügen, den Sie ausführen möchten, wenn das DOM für die Bearbeitung bereit ist, und dann aufrufen ready(main);.

Um die onclickAttribute zu ersetzen , sollten Sie ihnen einige CSS-Klassen / IDs hinzufügen, die als Selektoren im JavaScript-Code verwendet werden sollen. Sie würden sie dann mit etwas wie auswählen const backButton = document.querySelector('calendarBack');. Anschließend können Sie diesem Element einen Ereignis-Listener wie folgt hinzufügen:

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

Code-Refactorings

months

Die monthsVariablendeklaration wiederholt sich ziemlich oft und für jeden Monat ändert sich nur eines: der 'Index'. Dies macht es zu einem perfekten Kandidaten, um in eine Loop- / Array-Map zu rollen.

Ich würde es so machen mit der mapFunktion :

// 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)),
  };
});

Eine Sache, bei der ich mir nicht sicher bin, ist die daysEigenschaft und warum es sich zunächst um ein Array handelt. Die einzige Stelle, auf die im Code verwiesen wird, ist die Überprüfung der Länge. Ist dies Teil des von Ihnen erwähnten unvollendeten Codes? Wenn nicht, würde ich die Zeile einfach durch Tage ersetzen: Array(total_month_days(fullYear, index)),stattdessen.

identify_month

Die ursprüngliche Implementierung dieser Funktion sollte funktionieren, obwohl es ein langer Weg ist, "den Index des Monats aus dem monthNamesArray abrufen" zu sagen .

Hier ist, wie ich es umschreiben würde:

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

Aktueller Monatscode

Der ursprüngliche Code ist aufgrund der wiederholten Suche nach dem im monthsArray gefundenen Monatsobjekt ziemlich schwer zu lesen . Platzieren Sie sie in einer Variablen, um die Lesbarkeit zu verbessern und nicht benötigte zusätzliche Array-Zugriffe zu entfernen.

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;

Ich werde versuchen, später wiederzukommen und weitere Gedanken hinzuzufügen :)