Привет!

Я собираюсь поделиться с вами простым календарем, который я недавно сделал с помощью Vue.js и CSS-сетки (без внешних библиотек). Логика, конечно, заложена в JavaScript, и я проведу вас через весь процесс ее создания. Это намного проще, чем вы думаете 😉

Что мы строим?

Итак, вот предварительный просмотр того, что мы собираемся произвести в конце этого процесса:

Кода немного, и все делается с нуля.

Что следует знать заранее.

Вот несколько основных вещей, которые вы должны знать, чтобы не слишком запутаться:

  • Базовое понимание Vue.js (очевидно). По крайней мере, вы должны понимать, как работают компоненты и props.
  • Базовое понимание JavaScript. Главное, что мы здесь будем использовать, - это его объект Date.
  • Базовое понимание сетки CSS. Изображение, которое вы видели в начале, стилизовано в основном с использованием сетки CSS. Итак, вы должны понимать, как элементы расположены в сетке. Через минуту мы определим их позиции с помощью JavaScript. Как это весело?

Как видите, все довольно просто.

Давайте начнем!

Все начинается с попытки получить все даты в данном месяце. И это делается, как я уже сказал, в JavaScript с использованием его объекта Date.

Но сначала имейте в виду:

  • «Дата» относится к числу в календаре. Например, 12 мая - дата здесь 12.
  • «День» означает день недели (понедельник, вторник и т. Д.).
  • Месяцы в JavaScript пронумерованы, и они начинаются с 0 и заканчиваются 11 (потому что у нас 12 месяцев). Итак, январь - 0, февраль - 1 и так далее. 11 декабря.
  • Дни также нумеруются, начиная с 0 (воскресенье), 1 (понедельник) и так далее до 6 (суббота).

Итак, давайте создадим функцию, которая принимает два аргумента: год и месяц, чтобы мы могли генерировать даты в этом конкретном месяце.

Вот код. Я объясню, что делает каждая строка:

function getDates(year, month) {
    var d = new Date(year, month);
    while(d.getMonth() == month) {
        console.log(d.getDate());
        d = new Date(d.getTime() + (1000 * 60 * 60 * 24));
    }
}

Объяснение:

  • function getDates(year, month){…} - Создает функцию getDates, которая принимает два аргумента, год и месяц, как целые числа.
  • var d = new Date(year, month); -Создает переменную с именем d и присваивает ей объект Date, которому передаются значения year и month, те же самые, которые были переданы в функцию getDates. Объект Date может принимать больше аргументов: new Date(year, month, date, hour, minutes, seconds, milliseconds). Год и месяц - это самое меньшее, что нужно. Аргументы можно опустить справа. В этом случае они установлены по умолчанию. Если вы еще не знакомы с объектом даты, вот ссылка на руководство по w3schools: https://www.w3schools.com/js/js_dates.asp.
  • while (d.getMonth() == month){…} - этот цикл while гарантирует, что полученные даты находятся в пределах заданного месяца. По мере увеличения объекта даты на 24 часа (1000 * 60 * 60 * 24 миллисекунды) передаваемое нами значение date неограниченно увеличивается. И что интересно, передача 32 января в объект Date, как в new Date(2018,0,32), не приведет к ошибке, но будет переведена на 1 февраля того же года. В нашем случае мы говорим JavaScript, что он должен увеличить дату на 1 , пока месяц остается неизменным. Понятно?
  • console.log(d.getDate()); - записывает сгенерированные значения даты в консоль.
  • d = new Date(d.getTime() + (1000 * 60 * 60 * 24)); - увеличивает объект даты на 24 часа (то есть сутки). метод d.getTime() возвращает значение в миллисекундах объекта даты d, то есть количество миллисекунд с 01 января 1970 г., 00:00:00 UTC до времени создания этого объекта. Таким образом, он получает это значение, добавляет 24 часа (в миллисекундах) и передает новое значение в тот же объект. Итак, теперь у нас следующий день.

Теперь, если мы вызовем getDates(2018,0) (это январь 2018 г.), мы получим с 1 по 31, зарегистрированные в консоли. getDates(2016,1) даст вам от 1 до 29. Значит, 2016 год был високосным.

Итак, теперь у нас есть центральный код.

Что дальше?

Теперь мы собираемся создать наш компонент vue. Назовем его monthView.vue.

Это шаблон, который мы собираемся использовать. Это еще не все, но через секунду я объясню, как это работает:

Пока ничего не отображается. Мы вставим необходимые коды позже. А пока позвольте мне объяснить, как это должно работать.

Компонент имеет некоторые свойства данных:

  • months - это массив, содержащий названия месяцев в порядке возрастания. Это позволяет нам легко получить название месяца, данное нам объектом Date, используя его в качестве индекса, поскольку массив также основан на нуле.
  • dayNames также представляет собой массив, содержащий названия дней недели в порядке возрастания, что позволяет нам получить день по соответствующему номеру.
  • dates - пустой массив, который будет заполнен всеми датами в данном месяце при вызове функции getDates.
  • firstSatDate - На данный момент это может показаться странным, но мы будем использовать это свойство для хранения даты первой субботы любого созданного нами месяца. Я расскажу вам, зачем нам это нужно, по ходу дела.

Примечание. Компонент получает значения года и месяца для использования в качестве "реквизита". Это позволяет нам передавать разные значения, и компонент отображается динамически. Как удобно!

Теперь что касается методов, у нас есть только один: getDates. Это та же функция, которую мы создали в начале, для генерации всех дат в данном месяце. Всего несколько дополнений, и я их объясню:

  • line 28: this.dates = []; -Это сбрасывает свойство данных dates компонента каждый раз, когда вызывается функция getDates. Это сделано для того, чтобы избежать добавления данных к существующим при повторном вызове функции.
  • line 30: if (!!d.getTime() && mth <= 11 && mth >= 0) { - Этот оператор if предназначен просто для обработки ошибки недопустимых аргументов, переданных функции. Например, отрицательное значение для month. Если это так, то пустой объект передается в dates, как показано в line 37.
  • line 32: this.dates.push({date: d.getDate(), day: d.getDay()}); -Теперь вместо записи значений в консоль мы добавляем объекты в массив dates. Каждый из этих объектов имеет два свойства: date для фактической даты и day для дня, на который она приходится. Итак, если месяц начинается в среду, массив dates будет содержать [{1, 3},{2, 4},...] в указанном порядке. Обратите внимание, что day достигнет 6 (суббота) и сбрасывается до 0 по мере увеличения даты.
  • line 35: this.firstSatDate = 7 — this.dates[0].day; -После того, как даты были сгенерированы, мы вычисляем дату первой субботы данного месяца. Мы делаем это, вычитая значение дня (не даты) первого дня месяца из 7 (потому что у нас семь дней в неделе) . Так, если первый день месяца - вторник, например, значение дня - 2, а 7 -2 = 5. Это означает, что первая суббота этого месяца - 5-е. Все просто, правда?
  • Наблюдатели должны действовать, если значения year и month prop изменяются, поэтому компонент повторно визуализируется. Без них компонент НЕ будет реагировать (это не каламбур), даже когда свойства меняются.

Мы почти закончили!

Отлично, мы практически закончили. Теперь нам нужно вставить код для отображения значений. Итак, давайте сделаем это:

Хорошо, теперь у нас отображается название нашего месяца (строка 3). Он просто принимает значение свойства month и использует его в качестве индекса массива months, чтобы вернуть соответствующее имя.

Далее у нас есть строка 6. Это создает элементы HTML <div> для каждого дня в массиве dayNames и присваивает каждому из них класс «dayName» и фактическое название дня. Таким образом, div будет отображаться как, например, <div class="dayName MON" ...></div>. У них также есть ключи в виде year-month-dayName, который будет выглядеть как 2018-0-SUN. Таким образом, если на одной странице есть несколько компонентов для разных месяцев, не будет дублирующихся ключей. Затем названия дней печатаются в тегах <h5>. Итак, у нас есть что-то вроде <div ...><h5>SUN</h5></div> на каждый день.

Наконец, у нас есть строка 9, которая создает <div> элементов для каждой даты в месяце. У каждого из них также есть два класса: «дата» и день, на который они приходятся. Итак, у нас есть что-то вроде <div class="date WED" ...>. У них также есть идентификаторы и ключи в форме year-month-date, чтобы различать <div> каждую дату на странице.

Теперь давайте добавим несколько стилей, потому что теперь все отображается в отдельной строке. Вот так:

DECEMBER
SUN
MON
TUE
...
SAT
1
2
3
...
31

Итак, давайте добавим немного CSS:

Это очень простые стили, настройка шаблона сетки и добавление некоторых основных правил. Если вы понимаете CSS-сетку, это должно быть проще простого. Обратите внимание, что в последнем правиле я стилизовал каждый div классом «СОЛНЦЕ» по-разному, придав им светло-красный фон и красный текст, чтобы выделить столбец «Воскресенье». Прямо как настоящие календари.

Теперь вот в чем проблема:

Показано, что каждый месяц начинается с воскресенья. Итак, как нам определить, где даты расположены в сетке на основе их значения дня? Здесь на помощь приходит математика. Не волнуйтесь, это один из простейших алгоритмов, с которыми вы могли столкнуться!

Во-первых, мы можем определить столбец, в который должна быть помещена дата, на основе ее значения day (это лучший способ сделать это, вы поймете почему). Мы просто увеличиваем значение на 1 и получаем строку столбца, с которой оно должно начинаться. Так, например, если это воскресенье, его значение даты равно 0. и 0 + 1 = 1. Таким образом, оно начинается в строке столбца 1 (помните, что линии сетки начинаются с 1, а дни отсчитываются от нуля) и заканчивается в следующую линию сетки.

Затем нам нужно определить строку, в которой div даты будет помещен в сетку.

Помните firstSatDate? Вот где это вступает в игру! Есть разные способы сделать это, но на ум пришел следующий алгоритм:

Мы знаем, что при использовании календаря, основанного на воскресении, все первые дни месяца до первой субботы (включительно) находятся в первой строке. После первой субботы следующие 7 дат переходят в следующий ряд и так далее. Итак, что мы собираемся сделать, так это посмотреть на смещение каждой даты от даты первой субботы и выполнить с ним некоторые математические вычисления. Мы будем рассматривать только те, которые после первой субботы, потому что те, которые были до первой субботы (включительно), по умолчанию будут в первой строке. Итак, теперь следующее воскресенье смещено на 1, следующий понедельник смещено на 2 и так до следующей субботы, смещено на 7 (это следующая неделя). Обратите внимание, что все они находятся во второй строке. Итак, теперь мы можем видеть здесь закономерность: смещения 1–7 находятся в строке 2, смещения 8–14 находятся в строке 3 и так далее.

Так как же это вычислить? Простой! разделите смещение на 7 и используйте функцию потолок для результата. Примечание. Функция потолка просто округляет правильное (не целое число) значение с плавающей запятой до ближайшее большее целое число, независимо от десятичной точки. В основном это обозначается как ceil. Итак, ceil(1.1) = 2, ceil(1.9) = 2,, но ceil(1.0) = 1.

Следовательно, смещение 1 даст нам 1/7 = 0.14…, смещение 7 даст нам 7/7 = 1. Все, что находится между ними, даст нам значение от 0,14… до 1. Используя функцию потолка, мы получим 1 для всех из них. Это говорит о том, что они все в одном ряду. Но поскольку мы смотрим на смещение с первой субботы, первая строка уже занята. Итак, мы добавим 1 к результату, чтобы указать правильную строку, в которой они должны находиться.

смещения 1-7 дали нам 1 после вычисления, поэтому мы добавляем 1, чтобы получить 2. Это говорит нам, что они должны быть во 2-й строке. Итак, теперь смещения 8-14 (в следующее воскресенье по субботу) после вычисления дадут нам 2: ceil(8/7) = 2, ceil(14/7) = 2. Итак, мы добавляем 1, чтобы получить 3, чтобы сказать нам, что они находятся в 3-м ряду.

Вот и все! Теперь мы можем добавить правило стиля grid-area для размещения каждой даты. Я буду использовать сокращенное значение в виде grid-area: row-line-start / column-line-start / row-line-end / column-line-end;. Для значений в конце строки я заменю оба на span 1, поскольку каждый элемент охватывает 1 строку и 1 столбец. Получаем: grid-area: row-line-start / column-line-start / span 1 / span 1;. Теперь давайте обновим наш код:

Теперь мы добавили новый атрибут:

:style=”`grid-area: ${(date.date > firstSatDate) ? (Math.ceil((date.date — firstSatDate)/7))+1 : 1}/${date.day + 1}/span 1/span 1`”

Во-первых, двоеточие (:) перед атрибутом «style» является сокращением для v-bind:, что позволяет нам заключать выражения JavaScript в кавычки. И здесь я использую строку шаблона ES6. Итак, теперь мы определяем значение row-line-start с помощью кода (date.date > firstSatDate) ? (Math.ceil((date.date — firstSatDate)/7))+1 : 1.

Explanation: Сначала этот запрос запрашивает JavaScript, если текущее значение даты больше (является положительным смещением) даты первой субботы. Если да, то он вызывает функцию JavaScripts Math.ceil() и выполняет с ней математические вычисления, чтобы определить номер строки. В противном случае он принимает значение 1. (если он не смещен положительно от даты первой субботы, тогда дата либо предшествующая, либо в эту субботу. Таким образом, она находится в строке 1). Также он определяет column-line-start, просто добавляя 1 к значению дня даты, как мы уже говорили.

Вуаля!

Так что у нас это! Вы можете указать этому компоненту любой год и месяц, и он будет отображать даты, как мы видели на изображении в начале.

Итак, чтобы использовать этот компонент, мы должны вложить его в родительский компонент и передать ему реквизиты. вот так:

<template>
    <month-view year=2018 month=11 />
</template>

Что еще более полезно, вы можете отображать все месяцы в данном году, как в реальном календаре!

<template>
    <month-view v-for="month in months" :month=month year=2018 />
</template>
<script>
    import monthView from 'monthView.vue';
    export default{
        components: {
            monthView
        }
        data(){
            return {
                months: [0,1,2,3,4,5,6,7,8,9,10,11]
            }
        }
    }
</script>

Очень просто, не правда ли? И с ним можно было делать все, что угодно. Так же, как я добавил поле ввода, где я мог бы ввести год и нажать клавишу ВВОД, тогда месяцы будут повторно отображены, чтобы сформировать календарь на этот год.

Вот как создать календарь с помощью Vue.js и CSS-сетки!

Спойлер… 🤫

Через консоль devTools я понял, что постоянно получаю сообщение об ошибке (предупреждение) о повторяющихся ключах в компоненте monthView. Это произошло потому, что в некоторые месяцы производились дубликаты определенной даты. Например, 28 октября 2018 г. было продублировано, прежде чем перейти к 29, затем 30 и т. Д. Таким образом, в массиве dates было 32 элемента !.

Проблема была в самом JavaScript: Рассмотрим объект даты new Date(2018,9,28). В настоящее время в этот день он установлен на 12 часов утра. Но увеличение значения времени (миллисекунды) на 24 часа (1000 * 60 * 60 * 24 миллисекунды) вернет дату, установленную на 28 октября 2018 г., 11:00:00 pm вместо 29 октября. , 2018 12:00:00. Интересно. Итак, я провел небольшое исследование и выяснил, что это происходит из-за какой-то ерунды сохранения дневного света. На данный момент я действительно не знаю об этом, но, по крайней мере, я знал, что это действительно так.

Но вот и хорошие новости ... Поскольку в CSS мы определили, где будет располагаться каждая дата на основе ее даты и дня с помощью JavaScript, этот дубликат не повлиял на макет, потому что все они боролись за одно и то же место!

Cooool! Вот почему не рекомендуется (как у меня было вначале) заполнять пустые дни в начале месяца (если они есть) пустыми элементами <div>, а остальные даты приходиться на свои места. насколько позволяет сетка. Таким образом, дубликаты БУДУТ ПОКАЗАННЫМИ и повлияют на остальные даты, искажая календарь!

Но я исправил.

Несмотря на то, что CSS решил проблему за меня, мне все равно не нравилась эта красная полоса в консоли. И, будучи перфекционистом, я нашел очень простое решение проблемы:

В функции getDates я немного изменил объект по умолчанию Date:

var d = new Date(yr, mth, 1, 1);

Я установил время первого дня на час ночи. Так что каждые 24 часа будут давать мне следующий день с часом ночи. Но таким образом, если час будет потерян, как 28 октября 2018 г., я окажусь в 12:00, что все равно даст мне следующий день, 29 октября!

Классика !, подумал я.

Конец!

Итак, мы прошли долгий путь. Спасибо, что остались со мной. Не стесняйтесь, чтобы оставить комментарий ниже. И если есть что-то, что вы хотите спросить по этому поводу, или какие-либо предложения, пожалуйста, добро пожаловать! Я хотел бы увидеть, что вы, ребята, можете из этого извлечь, так что делайте это.

Увидимся в следующий раз!

И ох! ... С Рождеством Христовым! 😉.