I created an app which counts days, hours, minutes and seconds until some event.
I learned a lot while creating it and as always I would love some feedback from you guys to improve my code by learning from mistakes and maybe implement a better solution. In particular, I am not very proud of the way my startTimer()
works.
'use strict';
// #####################################################################
// start - event name and calculating difference between the event and today
const startBtn = document.querySelector('.start-btn');
const resetBtn = document.querySelector('.reset-btn');
const eventName = document.querySelector('#event-name');
const eventDate = document.querySelector('#event-date');
const eventTime = document.querySelector('#event-time');
const untilParagraph = document.querySelector('.until');
let isStartClicked = false;
startBtn.addEventListener('click', () => {
const today = new Date();
let eventDay = new Date(eventDate.value);
if (isValid('time')) {
const splitedTime = eventTime.value.split(':');
eventDay.setHours(splitedTime[0]);
eventDay.setMinutes(splitedTime[1]);
}
//date in the past
if (eventDay.getTime() < today.getTime()) {
const message = document.querySelector('.message');
message.classList.remove('hidden');
const messageClose = document.querySelector('.message__close');
messageClose.addEventListener('click', () => {
message.classList.add('hidden');
});
return;
}
if (isValid('name') && isValid() && !isStartClicked) {
untilParagraph.innerHTML = `<span class='until--until'>until</span> ${eventName.value}`;
let difference = eventDay - today; //in ms
const daysLeft = Math.floor(difference / (1000 * 3600 * 24));
difference -= daysLeft * 1000 * 3600 * 24;
let hoursLeft, minutesLeft, secondsLeft;
hoursLeft = Math.floor(difference / (1000 * 3600));
difference -= hoursLeft * 1000 * 3600;
minutesLeft = Math.floor(difference / (1000 * 60));
difference -= minutesLeft * 1000 * 60;
secondsLeft = Math.floor(difference / 1000);
difference -= secondsLeft * 1000;
startTimer({ daysLeft, hoursLeft, minutesLeft, secondsLeft });
isStartClicked = true;
}
});
// #####################################################################
// reset
resetBtn.addEventListener('click', () => {
stopTimer();
insertTime({ daysLeft: 0, hoursLeft: 0, minutesLeft: 0, secondsLeft: 0 });
isStartClicked = false;
});
// #####################################################################
// Validation
const isValid = (type) => {
if (type === 'name') {
if (eventName.value) return true;
return false;
} else if (type === 'time') {
const timePattern = /^([0-1][0-9]|[2][0-3]):([0-5][0-9])$/;
if (!timePattern.test(eventTime.value)) return false;
else return true;
} else {
// date
const datePattern = /^(((0[13-9]|1[012])[-/]?(0[1-9]|[12][0-9]|30)|(0[13578]|1[02])[-/]?31|02[-/]?(0[1-9]|1[0-9]|2[0-8]))[-/]?[0-9]{4}|02[-/]?29[-/]?([0-9]{2}(([2468][048]|[02468][48])|[13579][26])|([13579][26]|[02468][048]|0[0-9]|1[0-6])00))$/;
if (!datePattern.test(eventDate.value)) return false;
else return true;
}
};
eventName.addEventListener('blur', () => {
if (!isValid('name'))
document.querySelector('#name-invalid').classList.remove('hidden');
else document.querySelector('#name-invalid').classList.add('hidden');
});
eventDate.addEventListener('blur', () => {
if (!isValid('date'))
document.querySelector('#date-invalid').classList.remove('hidden');
else document.querySelector('#date-invalid').classList.add('hidden');
});
eventTime.addEventListener('blur', () => {
if (!isValid('time'))
document.querySelector('#time-invalid').classList.remove('hidden');
else document.querySelector('#time-invalid').classList.add('hidden');
});
// #####################################################################
// Timer
let interval;
const startTimer = (time) => {
interval = setInterval(() => {
if (
time.daysLeft != 0 ||
time.hoursLeft != 0 ||
time.minutesLeft != 0 ||
time.secondsLeft != 0
) {
if (time.secondsLeft > 0) {
time.secondsLeft -= 1;
} else {
time.secondsLeft = 59;
if (time.minutesLeft > 0) time.minutesLeft -= 1;
else {
time.minutesLeft = 59;
if (time.hoursLeft > 0) time.hoursLeft -= 1;
else {
time.hoursLeft = 23;
if (time.daysLeft > 0) time.daysLeft -= 1;
}
}
}
}
insertTime(time);
}, 1000);
};
const stopTimer = () => {
clearInterval(interval);
};
const insertTime = (time) => {
document.querySelector('.days').textContent =
time.daysLeft < 10 ? `0${time.daysLeft}` : time.daysLeft;
document.querySelector('.hours').textContent =
time.hoursLeft < 10 ? `0${time.hoursLeft}` : time.hoursLeft;
document.querySelector('.minutes').textContent =
time.minutesLeft < 10 ? `0${time.minutesLeft}` : time.minutesLeft;
document.querySelector('.seconds').textContent =
time.secondsLeft < 10 ? `0${time.secondsLeft}` : time.secondsLeft;
};
// TODO:
// ? Improve algorythmic notation of timer
@import url("https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap");
*,
*::after,
*::before {
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-family: 'Ubuntu', sans-serif;
}
body {
background-color: #111010;
height: 100vh;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
main .menu {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
padding: 5rem 15rem;
border: 2px solid #fffbfc;
border-radius: 3px;
}
main .menu div {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
color: #fffbfc;
position: relative;
}
main .menu div:not(:first-child) {
margin-left: 3rem;
}
main .menu div label {
font-size: 0.8rem;
font-weight: 400;
}
main .menu div label span {
font-size: 0.7rem;
color: #c2c2c2;
font-weight: 300;
}
main .menu div input {
padding: 1rem 0.7rem;
margin-top: 1rem;
background-color: #292727;
border: none;
border-radius: 5px;
color: #fffbfc;
}
main .menu div input::-webkit-input-placeholder {
color: #c2c2c2;
}
main .menu div input:-ms-input-placeholder {
color: #c2c2c2;
}
main .menu div input::-ms-input-placeholder {
color: #c2c2c2;
}
main .menu div input::placeholder {
color: #c2c2c2;
}
main .menu div .valid-info {
color: #e94949;
margin-top: 0.5rem;
font-size: 0.8rem;
font-weight: bold;
position: absolute;
top: 100%;
}
main .menu .btn {
height: 45px;
width: 80px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-ms-flex-item-align: end;
align-self: flex-end;
border: 2px solid #fffbfc;
color: #fffbfc;
text-decoration: none;
padding: 0.8rem 1.3rem;
-webkit-transition: all 0.3s;
transition: all 0.3s;
border-radius: 5px;
background: none;
cursor: pointer;
}
main .menu .btn:hover {
background-color: #fffbfc;
color: black;
}
main .menu .btn--margin-small {
margin-left: 1rem;
}
main .menu .btn--margin-big {
margin-left: 3rem;
}
main .event {
padding: 10rem 15rem;
}
main .event .timer-box {
color: white;
font-size: 8rem;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
font-weight: 500;
}
main .event .timer-box p {
font-size: 5rem;
}
main .event .timer-box .days,
main .event .timer-box .hours,
main .event .timer-box .minutes,
main .event .timer-box .seconds {
position: relative;
}
main .event .timer-box .days::before,
main .event .timer-box .hours::before,
main .event .timer-box .minutes::before,
main .event .timer-box .seconds::before {
font-size: 1rem;
position: absolute;
bottom: 100%;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
color: #c2c2c2;
font-weight: 300;
}
main .event .timer-box .days::before {
content: 'days';
}
main .event .timer-box .hours::before {
content: 'hours';
}
main .event .timer-box .minutes::before {
content: 'minutes';
}
main .event .timer-box .seconds::before {
content: 'seconds';
}
main .event .until {
font-size: 1rem;
font-weight: bold;
color: #c2c2c2;
margin-top: 2rem;
text-align: center;
-ms-flex-item-align: end;
align-self: flex-end;
}
main .event .until--until {
font-size: 0.8rem;
color: #aaaaaa;
font-weight: 300;
}
.message {
color: black;
font-weight: 500;
border-radius: 5px;
text-align: center;
background-color: white;
position: absolute;
top: 10rem;
left: 50%;
padding: 2rem 4rem;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
}
.message__close {
color: black;
text-decoration: none;
font-weight: bold;
font-size: 1.2em;
padding: 0.5rem;
position: absolute;
top: 0;
right: 0;
-webkit-transition: scale 0.3s;
transition: scale 0.3s;
}
.message__close:hover {
-webkit-transform: scale(1.5);
transform: scale(1.5);
}
.hidden {
visibility: hidden;
}
/*# sourceMappingURL=main.css.map */
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style/main.css">
<title>Countdown</title>
</head>
<body>
<main>
<form action="#" class="menu">
<div>
<label for="event-name">Event name</label>
<input type="text" id='event-name' placeholder="New Year Party" required>
<p class='valid-info hidden' id="name-invalid">Invalid name</p>
</div>
<div>
<label for="event-date">Date</label>
<input type="text" id='event-date' placeholder="12/31/2020" required>
<p class='valid-info hidden' id="date-invalid">Invalid date</p>
</div>
<div>
<label for="event-time">Time <span>(optional)</span></label>
<input type="text" id='event-time' placeholder="20:00">
<p class='valid-info hidden' id="time-invalid">Invalid time</p>
</div>
<button class='btn btn--margin-big start-btn'>Start</button>
<a href="#" class='btn btn--margin-small reset-btn'>Reset</a>
</form>
<div class="event">
<div class="timer-box">
<div class="days">00</div>
<p>:</p>
<div class="hours">00</div>
<p>:</p>
<div class="minutes">00</div>
<p>:</p>
<div class="seconds">00</div>
</div>
<div class="until"> <span class='until--until'>until</span> New Year Party</div>
</div>
<p class="message hidden">Your event has already happend
<a href="#" class="message__close">x</a>
</p>
</main>
<script src="script.js"></script>
</body>
</html>