I Shar

I Shar

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

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

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

I Shar

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

Photo by Eric Rothermel on Unsplash

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

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

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

Содержание


HTML

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

  1. month: содержит наименования месяца и года, а также элементы навигации,
  2. weekdays: наименования дней недели,
  3. 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;
        }
        
        

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

        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 += '
      • '; } } 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:


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

          • frontend
          • css
          • js

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

        Далее

        Категории

        О нас

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