dr.Brain

doctor Brain

мир глазами веб-разработчика

Простой календарь

календарь на Vanilla JS

dr.Brain

время чтения 5 мин.

Photo by Eric Rothermel on Unsplash

При создании различных веб-сервисов работа с датами и временем практически всегда занимает весомое место. Создание календаря - достаточно простая задача, которая, тем не менее, часто вызывает затруднения в реализации у начинающих разработчиков.

Сегодня мы создадим виджет простого месячного календаря, отвечающий следующим условиям:

  1. по умолчанию загружается текущий месяц,
  2. текущая дата выделяется,
  3. возможно смещение по календарю на месяц вперед или назад.

Содержание


HTML

Создадим контейнер обертку month-calendar, в котором разместим три блока:

  1. month: содержит наименования месяца и года, а также элементы навигации,
  2. weekdays: наименования дней недели,
  3. days: собственно, сам календарь (табличная часть, содержит дни месяца).

Так выглядит html-разметка основных блоков календаря:

<div id="month-calendar">
    <ul class="month"></ul>
    <ul class="weekdays"></ul>
    <ul class="days"></ul>
</div>

Обратите внимание: несмотря на визуальную реализацию виджета в виде таблицы, для каждого из вышеперечисленных дочерних элементов контейнера month-calendar мы используем немаркированный список ul. Соответственно, в дальнейшем мы будем манипулировать элементами списков li.

А вот и финальная верстка:

<div id="month-calendar">
    <!-- Блок с заголовками (наименования месяца и года) и навигацией (на месяц вперед и назад) -->
    <ul class="month">
        <li class="prev"><i class="fas fa-angle-double-left"></i></li>
        <li class="next"><i class="fas fa-angle-double-right"></i></li>
        <li class="month-name"></li>
        <li class="year-name"></li>
    </ul>
     <!-- Блок с наименования дней недели, начинается с понедельника (Пн) -->
    <ul class="weekdays">
        <li>Пн</li>
        <li>Вт</li>
        <li>Ср</li>
        <li>Чт</li>
        <li>Пт</li>
        <li>Сб</li>
        <li>Вс</li>
    </ul>
    <!-- табличная часть, дни месяца -->
    <ul class="days">
        <li>1</li>
        <li>2</li>
        <li>...</li>
        <li>31</li>
    </ul>
</div>

В данном случае для элементов навигации нужно подключить библиотеку 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;
}

На что обратить внимание:

  1. ширина контейнера month-calendar равна 100%: width: 100%,
  2. для элементов навигации prev и next используются свойства float: left и float-right,
  3. блок days имеет фиксированную высоту height: 14rem, что позволяет избежать смещения или наложения элементов при динамической смене данных виджета,
  4. для табличной части календаря элементы li располагаются слева направо и сверху вниз: горизонтальное выравнивание - justify-content: left, вертикальное - align-content: flex-start,
  5. в одном ряду табличной части находится семь элементов, это достигается за счет использования выражения calc: flex: 0 0 calc(100% / 7),
  6. для исключения влияния размеров отсутпов на расчетные значения для всех элементов установлено свойство 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 += '<li></li>';
        }
    }

    for (let i = 1; i <= monthDays; i++){
        monthDaysText += '<li>' + i + '</li>';
    }

    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:


Спасибо за внимание.

Новые публикации

Далее

Категории

О нас

Frontend & Backend. Статьи, обзоры, заметки, код, уроки.