Calendrier animé utilisant HTML + CSS + JS
Je suis un développeur débutant et je construis un calendrier pour les smartphones. J'utilise HTML, CSS et JS. Je n'ai pas encore tout à fait terminé le projet, cependant, j'ai le sentiment que je crée un code compliqué . Je publierai le lien vers le git + le code ici, afin que vous puissiez mieux le lire / cloner, et m'aider avec les modèles de conception, etc.
PS: Le projet a été développé en utilisant la résolution IPhoneX (375 x 812). Il n'est pas encore réactif, donc je recommanderais d'exécuter le code en utilisant la résolution ci-dessus.
Lien 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);
}
Réponses
Bienvenue dans Code Review. Le CSS a l'air assez bon. Je n'ai pas vu trop de feuilles de style avec de la couleur lightsalmon😃. Voici quelques suggestions.
Cache les références DOM
"... L'accès au DOM est en fait assez coûteux - je pense que c'est comme si j'avais un pont - comme deux terrains avec un pont à péage, et le moteur JavaScript est d'un côté, et le DOM est de l'autre, et chaque fois que je veux accéder au DOM à partir du moteur JavaScript, je dois payer ce péage »
- John Hrvatin, Microsoft, MIX09, dans cette conférence sur la création d'applications et de sites Web haute performance à 29:38, également cité dans le O'Reilly Livre Javascript de Nicholas C Zakas Pg 36 , ainsi que mentionné dans cet article
Compte tenu de ce qui a été déclaré il y a plus de 10 ans et que les navigateurs ont probablement évolué depuis lors, il est toujours sage d'en être conscient. Stocker des références à des éléments DOM - par exemple document.getElementById("year_title")dans des variables - cela pourrait se produire lors d'un rappel de l' DOMContentLoadedévénement .
let titleEl;
window.addEventListener('DOMContentLoaded', (event) => {
titleEl = document.getElementById("year_title");
});
Comme Joshua l'a mentionné, les gestionnaires d'événements en HTML sont désapprouvés par les normes actuelles, et ce rappel serait un endroit approprié pour avoir le code actuellement dans l' onloadattribut de l'élément body.
noms de mois
Comme Joshua l'a déjà mentionné, le code des mois est assez répétitif. Au lieu de stocker, les noms de mois Date.prototype.toLocaleDateString()pourraient être utilisés pour générer les noms de mois de manière dynamique, éventuellement dans une langue spécifiée par l'utilisateur et / ou navigator.languagesi elle est disponible.
Nom de fonction plus lisible
// Set the background color of the cell that contains today's day. function is_today(months, cell) {
A en juger par le nom, je suppose que la fonction renverrait un booléen qui indique si une valeur est aujourd'hui, bien que compte tenu du commentaire et de l'implémentation, ce n'est pas le cas. Un nom plus approprié pourrait être style_todays_date_cell.
Égalité stricte
C'est une bonne habitude d'utiliser des opérateurs de comparaison stricts - c'est-à-dire ===et !==lorsque cela est possible - par exemple pour cette comparaison dans load_DB():
if (x == 6 || x == 0) cell.style.color = "rgba(175, 175, 175, 0.45)";
Incrément de pré-correction
Ces lignes:
month++; load_DB(year, month);
Peut être combiné lorsque la valeur de monthest incrémentée avant l'appel à l'aide de l' opérateur d'incrémentation du préfixe :
load_DB(year, ++month);
styles en ligne redondants -> CSS
Les images dans les boutons ont des styles en ligne, c'est-à-dire
style="width: 35px; height: 35px"
Cela peut être déplacé vers la feuille de style:
button img {
width: 35px;
height: 35px;
}
Styles en ligne pour les en-têtes de tableau
Les première et dernière cellules ont des styles en ligne pour remplacer la thrègle par défaut :
<th style="color: lightsalmon">Sun</th>
Cela peut également être déplacé vers la feuille de style en utilisant des sélecteurs plus spécifiques, par exemple des pseudo-sélecteurs :first-childet :last-child:
th:first-child,
th:last-child {
color: lightsalmon;
}
Les alternatives incluent :first-of-typeet :last-of-typeCette technique pourrait également être utilisée pour déplacer la ligne ci-dessus hors du JavaScript et dans la feuille de style en utilisant tdau lieu de tr:
if (x == 6 || x == 0) cell.style.color = "rgba(175, 175, 175, 0.45)";
PS: La réponse a été composée à l'aide de l' iPhone VII +
Variables
Utilisez toujours une déclaration de variable lors de la première déclaration de votre variable. Si vous ne le faites pas, votre variable sera placée dans la portée globale , ce qui n'est presque jamais ce que vous voulez. Cela indique également plus clairement que vous ne souhaitez pas réattribuer à une variable globale déjà existante.
Donc , pour starting_day, par exemple, mettre var, letou constdevant elle.
Attributs HTML
Au lieu d'utiliser des attributs comme onclickou onloadpour faire la gestion des événements (ceux-ci sont également appelés gestionnaires en ligne), il est généralement considéré comme une bonne pratique de gérer ces événements dans votre JavaScript lui-même. Cela aide à séparer votre HTML / JavaScript.
Par exemple, pour remplacer l' onloadattribut, vous pouvez utiliser à la place la fonction JS suivante:
function ready(func) {
if (document.readyState !== 'loading') {
func();
} else {
document.addEventListener('DOMContentLoaded', func);
}
}
Vous pouvez déclarer une fonction appelée mainpar exemple, dans laquelle vous placeriez tout code que vous souhaitez exécuter lorsque le DOM est prêt à être utilisé, puis appelez ready(main);.
Pour remplacer les onclickattributs, vous devez leur ajouter des classes / ID CSS à utiliser comme sélecteurs dans le code JavaScript. Vous les sélectionneriez ensuite en utilisant quelque chose comme const backButton = document.querySelector('calendarBack');. Ensuite, vous pouvez attacher un écouteur d'événement à cet élément comme ceci:
backButton.addEventListener("click", () => {
// The code you want to run when the button is clicked goes here
});
Refactorisations de code
months
La monthsdéclaration de variable est assez répétitive et il n'y a qu'une seule chose qui change pour chaque mois, son «index». Cela en fait un candidat parfait pour rouler dans une carte de boucle / tableau.
Je le ferais comme ça en utilisant la mapfonction :
// 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)),
};
});
Une chose dont je ne suis pas sûr est la dayspropriété et pourquoi c'est un tableau pour commencer. Le seul endroit où il est référencé dans le code est de vérifier sa longueur. Cela fait-il partie du code inachevé que vous avez mentionné? Si ce n'est pas le cas, je remplacerais simplement la ligne par days: à la Array(total_month_days(fullYear, index)),place.
identify_month
L'implémentation originale de cette fonction devrait fonctionner, bien que ce soit une longue façon de dire "obtenir l'index du mois à partir du monthNamestableau".
Voici comment je le réécrirais:
// Converts the month name to the month number (January = 0).
function identify_month(string) {
return monthNames.indexOf(string);
}
Code du mois en cours
Le code d'origine est assez difficile à lire en raison des recherches répétées de l'objet de mois trouvé dans le monthstableau. Placez-les dans une variable pour améliorer la lisibilité et supprimer les accès supplémentaires inutiles au tableau.
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;
J'essaierai de revenir et d'ajouter quelques réflexions plus tard :)