Calendario animato utilizzando HTML + CSS + JS

Oct 17 2020

Sono uno sviluppatore principiante e sto costruendo un calendario per smartphone. Sto usando HTML, CSS e JS. Non ho ancora finito con il progetto, tuttavia, ho la sensazione di creare un codice disordinato . Inserirò il link a git + il codice qui, così puoi leggerlo / clonarlo meglio e aiutarmi con i modelli di progettazione, ecc ...

PS: il progetto è stato sviluppato utilizzando la risoluzione IPhoneX (375 x 812). Non è ancora reattivo, quindi consiglierei di eseguire il codice usando la risoluzione sopra.

Collegamento 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);
}

Risposte

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

Benvenuto in Code Review. Il CSS sembra abbastanza buono. Non ho visto troppi fogli di stile con il colore lightsalmon😃. Di seguito sono riportati alcuni suggerimenti.

Cache dei riferimenti DOM

"... l'accesso DOM è in realtà piuttosto costoso - penso che sia come se avessi un ponte - come due pezzi di terreno con un ponte a pedaggio, e il motore JavaScript si trova su un lato e il DOM sull'altro, e ogni volta che voglio accedere al DOM dal motore JavaScript, devo pagare quel pedaggio "
    - John Hrvatin, Microsoft, MIX09, in questo discorso Building High Performance Web Applications and Sites at 29:38, citato anche in O'Reilly Libro Javascript di Nicholas C Zakas Pg 36 , così come menzionato in questo post

Tenendo presente che è stato affermato più di 10 anni fa e che probabilmente i browser sono arrivati ​​da allora, è ancora saggio esserne consapevoli. Memorizza i riferimenti agli elementi DOM, ad esempio document.getElementById("year_title")nelle variabili, ciò potrebbe accadere in una richiamata DOMContentLoadedall'evento .

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

Come menzionato da Joshua, i gestori di eventi in HTML sono disapprovati dagli standard odierni e quella richiamata sarebbe un luogo appropriato per avere il codice attualmente onloadnell'attributo dell'elemento body.

nomi dei mesi

Come già accennato da Joshua, il codice per i mesi è abbastanza ripetitivo. Invece di memorizzare i nomi dei mesi Date.prototype.toLocaleDateString()potrebbe essere utilizzato per generare dinamicamente i nomi dei mesi, possibilmente in una lingua specificata dall'utente e / o navigator.languagese disponibile.

Nome della funzione più leggibile

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

A giudicare dal nome, immagino che la funzione restituirebbe un booleano che indica se un valore è oggi, anche se dato il commento e l'implementazione non è così. Potrebbe essere un nome più appropriato style_todays_date_cell.

Rigorosa uguaglianza

È una buona abitudine utilizzare operatori di confronto rigorosi, ovvero ===e !==quando possibile, ad esempio per questo confronto all'interno di load_DB():

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

Incremento pre-correzione

Queste righe:

month++;
load_DB(year, month);

Può essere combinato quando il valore di monthviene incrementato prima della chiamata utilizzando l' operatore di incremento prefisso :

 load_DB(year, ++month);

stili inline ridondanti -> CSS

Le immagini all'interno dei pulsanti hanno stili in linea, ad es

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

Può essere spostato nel foglio di stile:

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

Stili in linea per le intestazioni di tabella

La prima e l'ultima cella hanno stili in linea per sovrascrivere la thregola predefinita :

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

Anche questo può essere spostato nel foglio di stile utilizzando selettori più specifici, ad esempio pseudo-selettori :first-childe :last-child:

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

Le alternative includono :first-of-typee :last-of-typeQuella tecnica potrebbe anche essere utilizzata per spostare la riga sopra da JavaScript e nel foglio di stile usando tdinvece di tr:

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

PS: la risposta è stata composta utilizzando l' iPhone VII +

6 JoshuaHall Oct 17 2020 at 04:42

Variabili

Usa sempre una dichiarazione di variabile quando dichiari per la prima volta la tua variabile. Se non lo fai, la tua variabile verrà inserita nell'ambito globale , che non è quasi mai quello che desideri. Rende anche più chiaro che non si desidera riassegnare a una variabile globale già esistente.

Così, per starting_day, ad esempio, mettere una var, leto constdi fronte ad essa.

Attributi HTML

Invece di utilizzare attributi come onclicko onloadper eseguire la gestione degli eventi (noti anche come gestori in linea), è generalmente considerata una buona pratica gestire questi eventi nel JavaScript stesso. Aiuta a separare il tuo HTML / JavaScript.

Ad esempio, per sostituire l' onloadattributo, potresti invece utilizzare la seguente funzione JS:

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

Potresti dichiarare una funzione chiamata mainad esempio, in cui inseriresti qualsiasi codice che desideri eseguire quando il DOM è pronto per essere lavorato, e quindi chiamare ready(main);.

Per sostituire gli onclickattributi, dovresti aggiungere alcune classi / ID CSS da utilizzare come selettori nel codice JavaScript. Dovresti quindi selezionarli usando qualcosa di simile const backButton = document.querySelector('calendarBack');. Quindi, puoi allegare un listener di eventi a quell'elemento in questo modo:

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

Refactoring del codice

months

La monthsdichiarazione della variabile è piuttosto ripetitiva e c'è solo una cosa che cambia per ogni mese, l '"indice" di essa. Questo lo rende un candidato perfetto per rotolare in una mappa loop / array.

Lo farei in questo modo usando la mapfunzione :

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

Una cosa di cui non sono sicuro è la daysproprietà e il motivo per cui è un array per cominciare. L'unico punto in cui viene fatto riferimento nel codice è controllarne la lunghezza. Fa parte del codice incompiuto che hai citato? In caso contrario, sostituirei semplicemente la riga con giorni: Array(total_month_days(fullYear, index)),invece.

identify_month

L'implementazione originale di questa funzione dovrebbe funzionare, sebbene sia un modo lungo di dire "ottieni l'indice del mese monthNamesdall'array".

Ecco come lo riscriverei:

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

Codice del mese corrente

Il codice originale è piuttosto difficile da leggere a causa delle ripetute ricerche dell'oggetto mese trovato monthsnell'array. Inseriscili in una variabile per migliorare la leggibilità e rimuovere gli accessi agli array aggiuntivi non necessari.

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;

Proverò a tornare e ad aggiungere altri pensieri più tardi :)