Создание чертовски простого макета календаря с помощью CSS Grid, Moment.js и Vue.js

Перевод статьи Building a Damn Simple Calendar Layout with CSS Grid, Moment, and Vue

Пару дней назад, клиент прислал мне фотографию макета очень просто календаря в качестве вдохновения для компонента, который мы пытались добавить на разрабатываемый тогда сайт. Я понял его логику. Мы работали над сайтом, в котором использовалась очень строгая типографика и макет этого календаря идеально соответствовал его эстетике. Обычное статическое размещение чего-то подобного с помощью сетки довольно тривиально, но мы хотели убедиться, что сможем создать такой календарь динамически и тогда он сможет превратиться в компонент выбора даты. По ссылке вы можете открыть демо на Codepen и продолжить чтение статьи.

Прежде всего убедитесь, что у вас есть корневой элемент, в котором вы будете инициализировать ваше Vue приложение. Для меня это стал <main> c идентификатором “calendar”. Добавьте скрипт Vue.js в проект и инициализируйте его.

const app = new Vue({
el: '#calendar',
data() {
return {
days: []
}
},
mounted() {
// загружаем список дней
}
})

Отлично. Теперь добавляем moment.js и пишем в хук mounted() код который заполнит массив days днями месяца. Для этого будем использовать функции из moment.js и немного магии ES6.

let monthDate = moment().startOf('month')this.days = [...Array(monthDate.daysInMonth())].map((_, i) => {
return monthDate.clone().add(i, 'day')
})

Во-первых, получаем начало месяца с помощью функции startOf(). Во-вторых мы будем использовать функцию daysInMonth() как часть нового временного массива который будет распределен в массив, равный длине дней в месяце. И затем мы можем использовать его что бы пройтись по списку дней c помощью функций moment.js clone и add. Это добавит дни месяца в наш итоговый массив days.

Теперь, когда у нас есть массив дней месяца мы можем сделать html каркас что бы отобразить их. Для это будем использовать стандартный способ отрисовки списков в Vue.js. Добавьте этот код в основной контейнер вашего календаря:

<div v-for="(day, index) in days">
{{ day.format('D') }}
</div>

Теперь вы должны увидеть список дней, равный количеству дней в вашем текущем месяце. Теперь добавим немного CSS что бы календарь выглядел приятнее. Я хочу что бы он был центрирован и в нем использовался шрифт без засечек.

html, body{
height: 100%;
width: 100%;
}
body{
align-items: center;
box-sizing: border-box;
display: flex;
font: calc(2vh) "Helvetica", sans-serif;
justify-content: "center";
padding: 2em;
}

Так же настроим сетку календаря так, что бы она состояла из 7 колонок, но по ширине оставалась в разумных пределах на больших экранах. Давайте сделаем так, что бы цифры в каждой ячейке были в центре. И используем псевдоэлемент ::before что бы наши ячейки оставались квадратными.

#calendar{
display: grid;
grid-template-columns: repeat(7, 1fr);
max-width: 1024px;
width: 100%;
}
#calendar > *{
align-items: center;
display: flex;
justify-content: center;
}
#calendar > *::before{
content: "";
display: inline-block;
height: 0;
padding-bottom: 100%;
width: 1px;
}

Теперь вы увидите отличную сетку с днями, но месяц может начинаться не с той колонки. Это связано с тем, что нам нужно указать, что первый день месяца должен начинаться с определенного столбца, а остальные дни будут отображаться соответствующим образом. Вы можете сделать это, просто добавив свойство gridColumn с необходимым индексом к первому элементу div. Для этого напрямую через Vue.js укажем необходимые нам стили.

<div v-for="(day, index) in days" :style="{ gridColumn: column(index) }">
{{ day.format('D') }}
</div>

Вызываем метод column и в него передаем index текущего элемента. Это позволит проверить нам что это первый элемент, и если это так то мы используем moment.js что бы получить первый день недели (0–6) плюс один. Это как раз подходит для сетки нашего календаря. Добавьте этот метод в methods экземпляра Vue.

column(index) {
if (index == 0) {
return this.days[0].day() + 1
}
}

Было бы неплохо указывать текущий день месяца. Для этого в нашем списке укажем нужный класс опять же с помощью Vue.

<div v-for="(day, index) in days" :style="{ gridColumn: column(index) }" :class="{ today: today(day) }">
{{ day.format('D') }}
</div>

Если метод today возвратит true то у элемента появится класс today. А для этого с помощью функции moment.js inSame() мы проверим, совпадает ли день в списке с текущим днем. Так же добавляем метод today в methods экземпляра Vue.

today(day) {
return moment().isSame(day, 'day')
}

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

#calendar > *.today{
border: 0.1em solid black;
border-radius: 100%;
}

Ну и наконец добавьте вручную 7 блоков для обозначения дней недели и вы увидите как ваш календарь сам раскладывает дни месяца автоматически.

Владимир Бандуристов

Written by

Frontend Разработчик в компании Webit.ru

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade