Calendario animado usando HTML + CSS + JS
Soy un desarrollador principiante y estoy creando un calendario para teléfonos inteligentes. Estoy usando HTML, CSS y JS. Aún no he terminado del todo con el proyecto, sin embargo, tengo la sensación de que estoy haciendo un código desordenado . Publicaré el enlace al git + el código aquí, para que puedas leerlo / clonarlo mejor y ayudarme con los patrones de diseño, etc.
PD: El proyecto se desarrolló utilizando la resolución IPhoneX (375 x 812). Todavía no responde, por lo que recomendaría ejecutar el código con la resolución anterior.
Enlace de 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);
}
Respuestas
Bienvenido a Revisión de código. El CSS se ve bastante bien. No he visto demasiadas hojas de estilo con color lightsalmon😃. A continuación se presentan algunas sugerencias.
Caché de referencias DOM
"... el acceso al DOM es bastante costoso, lo pienso como si tuviera un puente, como dos terrenos con un puente de peaje, y el motor JavaScript está en un lado y el DOM está en el otro, y cada vez que quiero acceder al DOM desde el motor JavaScript, tengo que pagar ese peaje ”
- John Hrvatin, Microsoft, MIX09, en esta charla Building High Performance Web Applications and Sites at 29:38, también citado en O'Reilly Libro de Javascript de Nicholas C Zakas Pg 36 , así como se menciona en esta publicación
Teniendo en cuenta que se dijo hace más de 10 años y es probable que los navegadores hayan avanzado mucho desde entonces, es aconsejable estar al tanto de esto. Almacene referencias a elementos DOM, por ejemplo, document.getElementById("year_title")en variables, esto podría suceder en una devolución de llamada al DOMContentLoadedevento .
let titleEl;
window.addEventListener('DOMContentLoaded', (event) => {
titleEl = document.getElementById("year_title");
});
Como mencionó Joshua, los controladores de eventos en HTML están mal vistos por los estándares actuales, y esa devolución de llamada sería un lugar apropiado para tener el código actualmente en el onloadatributo del elemento del cuerpo.
nombres de mes
Como Joshua ya mencionó, el código para los meses es bastante repetitivo. En lugar de almacenar los nombres de los meses, se Date.prototype.toLocaleDateString()podrían utilizar para generar los nombres de los meses de forma dinámica, posiblemente en un idioma especificado por el usuario y / o navigator.languagesi está disponible.
Nombre de función más legible
// Set the background color of the cell that contains today's day. function is_today(months, cell) {
A juzgar por el nombre, supongo que la función devolvería un booleano que indica si algún valor es hoy, aunque dado el comentario y la implementación, ese no es el caso. Un nombre más apropiado podría ser style_todays_date_cell.
Igualdad estricta
Es una buena costumbre utilizar operadores de comparación estricta - es decir, ===y !==cuando sea posible - por ejemplo, para esta comparación en load_DB():
if (x == 6 || x == 0) cell.style.color = "rgba(175, 175, 175, 0.45)";
Incremento de prefijo
Estas líneas:
month++; load_DB(year, month);
Se puede combinar cuando el valor de monthse incrementa antes de la llamada usando el operador de incremento de prefijo :
load_DB(year, ++month);
estilos en línea redundantes -> CSS
Las imágenes dentro de los botones tienen estilos en línea, es decir
style="width: 35px; height: 35px"
Eso se puede mover a la hoja de estilo:
button img {
width: 35px;
height: 35px;
}
Estilos en línea para encabezados de tabla
La primera y la última celda tienen estilos en línea para anular la thregla predeterminada :
<th style="color: lightsalmon">Sun</th>
Eso también se puede mover a la hoja de estilo utilizando selectores más específicos, por ejemplo, pseudo-selectores :first-childy :last-child:
th:first-child,
th:last-child {
color: lightsalmon;
}
Las alternativas incluyen :first-of-typey :last-of-typeEsa técnica también podría usarse para mover la línea anterior fuera del JavaScript y dentro de la hoja de estilo usando en tdlugar de tr:
if (x == 6 || x == 0) cell.style.color = "rgba(175, 175, 175, 0.45)";
PD: La respuesta se compuso usando el iPhone VII +
Variables
Utilice siempre una declaración de variable cuando declare su variable por primera vez. Si no lo hace, su variable se colocará en el ámbito global , que casi nunca es lo que desea. También deja más claro que no desea reasignar a una variable global ya existente.
Así que para starting_day, por ejemplo, poner una var, leto consten frente de ella.
Atributos HTML
En lugar de usar atributos como onclicko onloadpara realizar el manejo de eventos (también se conocen como controladores en línea), generalmente se considera una mejor práctica manejar estos eventos en su propio JavaScript. Ayuda a separar su HTML / JavaScript.
Por ejemplo, para reemplazar el onloadatributo, podría usar la siguiente función JS:
function ready(func) {
if (document.readyState !== 'loading') {
func();
} else {
document.addEventListener('DOMContentLoaded', func);
}
}
Podría declarar una función llamada, mainpor ejemplo, donde colocaría cualquier código que desee ejecutar cuando el DOM esté listo para trabajar y luego llamar ready(main);.
Para reemplazar los onclickatributos, debe agregarles algunas clases / identificadores CSS para usarlos como selectores en el código JavaScript. Luego los seleccionaría usando algo como const backButton = document.querySelector('calendarBack');. Luego, puede adjuntar un detector de eventos a ese elemento así:
backButton.addEventListener("click", () => {
// The code you want to run when the button is clicked goes here
});
Refactorizaciones de código
months
La monthsdeclaración de variable es bastante repetitiva y solo cambia una cosa para cada mes, el 'índice' de la misma. Esto lo convierte en un candidato perfecto para rodar en un mapa de bucle / matriz.
Lo haría así usando la mapfunción :
// 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 de la que no estoy seguro es la dayspropiedad y por qué es una matriz para empezar. Los únicos lugares en los que se hace referencia en el código es para verificar su longitud. ¿Es esto parte del código inacabado que mencionaste? Si no es así, simplemente reemplazaría la línea con días: en su Array(total_month_days(fullYear, index)),lugar.
identify_month
La implementación original de esta función debería funcionar, aunque es una forma larga de decir "obtener el índice del mes de la monthNamesmatriz".
Así es como lo reescribiría:
// Converts the month name to the month number (January = 0).
function identify_month(string) {
return monthNames.indexOf(string);
}
Código del mes actual
El código original es bastante difícil de leer debido a las búsquedas repetidas del objeto del mes que se encuentra en la monthsmatriz. Colóquelos en una variable para mejorar la legibilidad y eliminar los accesos de matriz adicionales innecesarios.
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;
Intentaré volver y agregar más pensamientos más tarde :)