Calendario animato utilizzando HTML + CSS + JS
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
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 +
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 :)