Простой календарь
календарь на Vanilla JS
При создании различных веб-сервисов работа с датами и временем практически всегда занимает весомое место. Создание календаря - достаточно простая задача, которая, тем не менее, часто вызывает затруднения в реализации у начинающих разработчиков.
Сегодня мы создадим виджет простого месячного календаря, отвечающий следующим условиям:
- по умолчанию загружается текущий месяц,
- текущая дата выделяется,
- возможно смещение по календарю на месяц вперед или назад.
Содержание
HTML
Создадим контейнер обертку month-calendar, в котором разместим три блока:
- month: содержит наименования месяца и года, а также элементы навигации,
- weekdays: наименования дней недели,
- days: собственно, сам календарь (табличная часть, содержит дни месяца).
Так выглядит html-разметка основных блоков календаря:
Обратите внимание: несмотря на визуальную реализацию виджета в виде таблицы, для каждого из вышеперечисленных дочерних элементов контейнера month-calendar мы используем немаркированный список ul
. Соответственно, в дальнейшем мы будем манипулировать элементами списков li
.
А вот и финальная верстка:
-
-
- Пн
- Вт
- Ср
- Чт
- Пт
- Сб
- Вс
- 1
- 2
- ...
- 31
В данном случае для элементов навигации нужно подключить библиотеку Font Awesome (Web Font или SVG версию).
CSS
В основе позиционирования элементов календаря находится модель flexbox.
CSS-код:
*{
box-sizing: border-box;
}
body{
font-family: sans-serif;
}
/* месяцы и годы */
#month-calendar{
width: 100%;
}
.month{
margin: 0;
padding: 3rem 2rem 2rem;
background: #555555;
text-align: center;
width: 100%;
color: #ffffff;
list-style: none;
}
.month li{
padding: 0;
margin: 0;
font-size: 1.5rem;
line-height: 1.4;
letter-spacing: 0.1rem;
text-transform: uppercase;
font-weight: 700;
}
.month li.prev,
.month li.next{
cursor: pointer;
}
.month li.prev{
float: left;
}
.month li.next{
float: right;
}
.month li.year-name{
font-size: 1.2rem;
font-weight: 400;
}
/* дни недели */
.weekdays{
margin: 0;
padding: 1rem 0;
background-color: #dddddd;
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: left;
}
.weekdays li{
display: inline-block;
flex: 0 0 calc(100% / 7);
text-align: center;
}
/* дни */
.days{
margin: 0;
padding: 1rem 0;
background-color: #eeeeee;
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: left;
align-content: flex-start;
height: 14rem;
}
.days li{
padding: 0.5rem;
list-style: none;
display: inline-block;
flex: 0 0 calc(100% / 7);
text-align: center;
color: #999;
font-size: 0.9rem;
line-height: 1rem;
}
.days li.date-now{
color: #000;
font-weight: 700;
}
На что обратить внимание:
- ширина контейнера month-calendar равна 100%:
width: 100%
, - для элементов навигации prev и next используются свойства
float: left
иfloat-right
, - блок days имеет фиксированную высоту
height: 14rem
, что позволяет избежать смещения или наложения элементов при динамической смене данных виджета, - для табличной части календаря элементы
li
располагаются слева направо и сверху вниз: горизонтальное выравнивание -justify-content: left
, вертикальное -align-content: flex-start
, - в одном ряду табличной части находится семь элементов, это достигается за счет использования выражения calc:
flex: 0 0 calc(100% / 7)
, - для исключения влияния размеров отсутпов на расчетные значения для всех элементов установлено свойство
box-sizing: border-box
.
JS
Основу работы виджета составляют свойства и методы встроенного объекта Date .
Экземпляр объекта Date
создать новый экземпляр объекта Date можно только через конструктор:
let nowDate = new Date(); // создаем экземпляра объекта с текущей датой
let curDate = new Date(year,month,day); // создаем экземпляр объекта для определенной календарной даты;
Вызов Date() как функции (без new) вернет не экземпляр объекта, а строку.
Как получить год, месяц, дату?
Чтобы получить год, месяц, дату экземпляра объетка Date, обратимся к встроенным методам:
nowDate.getFullYear(); // возвращает год в четырехзначном формате
nowDate.getMonth(); // возвращает номер месяца (значение от 0 до 11. Январь равен 0)
nowDate.getDate(); // возвращает текущую дату
Как получить название месяца?
Чтобы получить назавние месяца воспользуемся массивом:
let arrMonthName = ['январь','февраль','март','апрель','май','июнь','июль','август','сентябрь','октябрь','ноябрь','декабрь'];
let monthName = arrMonthName[NowDate.getMonth()]; // получаем название месяца
Как получить текущий день недели?
Для определения текущего дня недели тоже существует встроенная функция:
nowDate.getDay() // возвращает номер дня недели
Обратие внимание: отсчет дней недели идет с воскресенья, которое равно 0.
Как получить количество дней в месяце?
Существует небольшая хитрость, основанная на способности методов объекта Date автоматически пересчитывать параметры, приводя их к корректному значению. Так, если мы установим дату экземпляра объекта равной 0, метод getDate() вернет дату последнего дня предыдущего месяца, то есть полученное значение будет равнятся количеству дней в предыдущем месяце:
monthDays = new Date(year, month + 1, 0).getDate(); // возвращает количество дней в выбранном месяце
Как получить количество дней недели до начала текущего месяца.
Если неделя стартует с воскресенья, для определения количества дней недели до начала текущего месяца достаточно получить номер первого дня текущего месяца:
new Date(year,month,1).getDay() // возвращает номер дня недели
В случае, когда неделя начинается с понедельника, нужно получит номер последнего дня недели предыдущего месяца:
new Date(year,month,0).getDay() // возвращает номер дня недели
Как корректно сместить дату на месяц назад или вперед?
Мы опять обращаемся к способности методов объекта Date автоматически корректировать дату:
curDate.setMonth(curDate.getMonth() + 1); // смещает дату на месяц вперед
curDate.setMonth(curDate.getMonth() - 1); // смещает дату на месяц назад
Итоговый JS-файл
Опираясь на данные, полученные выше создадим итоговый код JavaScript:
let nowDate = new Date(),
nowDateNumber = nowDate.getDate(),
nowMonth = nowDate.getMonth(),
nowYear = nowDate.getFullYear(),
container = document.getElementById('month-calendar'),
monthContainer = container.getElementsByClassName('month-name')[0],
yearContainer = container.getElementsByClassName('year-name')[0],
daysContainer = container.getElementsByClassName('days')[0],
prev = container.getElementsByClassName('prev')[0],
next = container.getElementsByClassName('next')[0],
monthName = ['январь','февраль','март','апрель','май','июнь','июль','август','сентябрь','октябрь','ноябрь','декабрь'];
let curDate = nowDate.setMonth(nowDate.getMonth() - 1);
console.log(nowDate.getFullYear());
function setMonthCalendar(year,month) {
let monthDays = new Date(year, month + 1, 0).getDate(),
monthPrefix = new Date(year, month, 0).getDay(),
monthDaysText = '';
monthContainer.textContent = monthName[month];
yearContainer.textContent = year;
daysContainer.innerHTML = '';
if (monthPrefix > 0){
for (let i = 1 ; i <= monthPrefix; i++){
monthDaysText += '';
}
}
for (let i = 1; i <= monthDays; i++){
monthDaysText += ' ' + i + ' ';
}
daysContainer.innerHTML = monthDaysText;
if (month == nowMonth && year == nowYear){
days = daysContainer.getElementsByTagName('li');
days[monthPrefix + nowDateNumber - 1].classList.add('date-now');
}
}
setMonthCalendar(nowYear,nowMonth);
prev.onclick = function () {
let curDate = new Date(yearContainer.textContent,monthName.indexOf(monthContainer.textContent));
curDate.setMonth(curDate.getMonth() - 1);
let curYear = curDate.getFullYear(),
curMonth = curDate.getMonth();
setMonthCalendar(curYear,curMonth);
}
next.onclick = function () {
let curDate = new Date(yearContainer.textContent,monthName.indexOf(monthContainer.textContent));
curDate.setMonth(curDate.getMonth() + 1);
let curYear = curDate.getFullYear(),
curMonth = curDate.getMonth();
setMonthCalendar(curYear,curMonth);
}
Результат
А вот и пример на codepen:
Спасибо за внимание.