Быстрый поиск здесь, в CSS-Tricks, показывает, сколько существует разных способов работы с календарями. Некоторые показывают, как CSS Grid может эффективно создавать макет. Некоторые попытки привнести фактические данные в микс. Некоторые полагаться на структуру помогать в управлении государством.
При создании компонента календаря нужно учитывать множество соображений — гораздо больше, чем описано в статьях, на которые я ссылался. Если подумать, календари полны нюансов, от обработки часовых поясов и форматов даты до локализации и даже обеспечения того, чтобы даты перетекали из одного месяца в другой… отображается и не только.
Многие разработчики опасаются Date()
объект и придерживайтесь старых библиотек, таких как moment.js
. Но несмотря на то, что когда дело доходит до дат и форматирования, есть много «подводных камней», в JavaScript есть много крутых API и других вещей, которые могут помочь!
Я не хочу воссоздавать колесо здесь, но я покажу вам, как мы можем получить чертовски хороший календарь с помощью ванильного JavaScript. мы рассмотрим доступность, используя семантическую разметку и удобный для чтения с экрана <time>
-tags — а также интернационализация и форматирование, С использованием Intl.Locale
, Intl.DateTimeFormat
и Intl.NumberFormat
-API.
Другими словами, мы делаем календарь… только без дополнительных зависимостей, которые вы обычно видите в подобном руководстве, и с некоторыми нюансами, которые вы обычно не замечаете. И в процессе, я надеюсь, вы по-новому оцените новые возможности JavaScript, а также получите представление о тех вещах, которые приходят мне в голову, когда я собираю что-то подобное.
Во-первых, именование
Как мы должны назвать наш компонент календаря? На моем родном языке это называлось бы «элемент календаря», так что давайте воспользуемся этим и сократим до «Кал-Эл», также известного как Имя Супермена на планете Криптон.
Давайте создадим функцию, чтобы все заработало:
function kalEl(settings = {}) { ... }
Этот метод будет отображать один месяц. Позже мы будем вызывать этот метод из [...Array(12).keys()]
рендерить целый год.
Исходные данные и интернационализация
Одна из распространенных функций типичного онлайн-календаря — выделение текущей даты. Итак, давайте создадим ссылку для этого:
const today = new Date();
Далее мы создадим «объект конфигурации», который мы объединим с необязательным settings
объект основного метода:
const config = Object.assign( { locale: (document.documentElement.getAttribute('lang') || 'en-US'), today: { day: today.getDate(), month: today.getMonth(), year: today.getFullYear() } }, settings
);
Проверяем, если корневой элемент (<html>
) содержит lang
-атрибут с местный Информация; в противном случае мы вернемся к использованию en-US
. Это первый шаг к интернационализация календаря.
Нам также необходимо определить, какой месяц будет отображаться при рендеринге календаря. Именно поэтому мы расширили config
объект с первичным date
. Таким образом, если дата не указана в settings
объект, мы будем использовать today
ссылка вместо этого:
const date = config.date ? new Date(config.date) : today;
Нам нужно немного больше информации, чтобы правильно отформатировать календарь в зависимости от локали. Например, мы можем не знать, является ли первый день недели воскресеньем или понедельником, в зависимости от региона. Если у нас есть информация, отлично! Но если нет, мы обновим его с помощью Intl.Locale
API. API имеет weekInfo
объект который возвращает firstDay
свойство, которое дает нам именно то, что мы ищем без каких-либо хлопот. Мы также можем получить, какие дни недели назначены для weekend
:
if (!config.info) config.info = new Intl.Locale(config.locale).weekInfo || { firstDay: 7, weekend: [6, 7] };
Опять же, мы создаем запасные варианты. «Первый день» недели для en-US
воскресенье, поэтому по умолчанию используется значение 7
. Это немного сбивает с толку, так как getDay
метод в JavaScript возвращает дни как [0-6]
, Где 0
воскресенье… не спрашивайте меня, почему. Выходные - суббота и воскресенье, поэтому [6, 7]
.
Раньше у нас был Intl.Locale
API и его weekInfo
метод, было довольно сложно создать международный календарь без множества **объектов и массивов с информацией о каждой локали или регионе. В наше время это легко. Если мы пройдем в en-GB
, метод возвращает:
// en-GB
{ firstDay: 1, weekend: [6, 7], minimalDays: 4
}
В такой стране, как Бруней (ms-BN
), выходные - пятница и воскресенье:
// ms-BN
{ firstDay: 7, weekend: [5, 7], minimalDays: 1
}
Вы можете задаться вопросом, что это minimalDays
свойство есть. Это наименьшее количество дней, необходимых в первую неделю месяца, чтобы считаться полной неделей. В некоторых регионах это может быть всего один день. Для других это могут быть полные семь дней.
Далее мы создадим render
метод в рамках нашего kalEl
-метод:
const render = (date, locale) => { ... }
Нам все еще нужны некоторые данные для работы, прежде чем мы что-то отрендерим:
const month = date.getMonth();
const year = date.getFullYear();
const numOfDays = new Date(year, month + 1, 0).getDate();
const renderToday = (year === config.today.year) && (month === config.today.month);
Последний - это Boolean
это проверяет, есть ли today
существует в том месяце, который мы собираемся визуализировать.
Семантическая разметка
Через мгновение мы углубимся в рендеринг. Но сначала я хочу убедиться, что детали, которые мы настраиваем, имеют семантические теги HTML, связанные с ними. Установка этого прямо из коробки дает нам преимущества доступности с самого начала.
Оболочка календаря
Во-первых, у нас есть несемантическая оболочка: <kal-el>
. Это нормально, потому что нет семантического <calendar>
тег или что-то в этом роде. Если бы мы не делали пользовательский элемент, <article>
может быть наиболее подходящим элементом, поскольку календарь может располагаться на отдельной странице.
Названия месяцев
Ассоциация <time>
Этот элемент будет для нас важным, потому что он помогает переводить даты в формат, который программы чтения с экрана и поисковые системы могут анализировать более точно и последовательно. Например, вот как мы можем передать «январь 2023» в нашей разметке:
<time datetime="2023-01">January <i>2023</i></time>
Названия дней
Строка над датами календаря, содержащая названия дней недели, может быть сложной. Было бы идеально, если бы мы могли написать полные названия для каждого дня — например, воскресенья, понедельника, вторника и т. д. — но это может занять много места. Итак, давайте пока сократим имена внутри <ol>
где каждый день <li>
:
<ol> <li><abbr title="Sunday">Sun</abbr></li> <li><abbr title="Monday">Mon</abbr></li> <!-- etc. -->
</ol>
Мы могли бы поэкспериментировать с CSS, чтобы получить лучшее из обоих миров. Например, если мы изменили разметку примерно так:
<ol> <li> <abbr title="S">Sunday</abbr> </li>
</ol>
… мы получаем полные имена по умолчанию. Затем мы можем «скрыть» полное имя, когда закончится место, и отобразить title
атрибут вместо этого:
@media all and (max-width: 800px) { li abbr::after { content: attr(title); }
}
Но мы не идем по этому пути, потому что Intl.DateTimeFormat
Здесь также может помочь API. Мы вернемся к этому в следующем разделе, когда будем рассматривать рендеринг.
Номера дней
Каждой дате в календарной сетке присваивается номер. Каждое число является элементом списка (<li>
) в упорядоченном списке (<ol>
), и встроенный <time>
Тег оборачивает фактический номер.
<li> <time datetime="2023-01-01">1</time>
</li>
И хотя я пока не планирую делать какие-либо стили, я знаю, что мне понадобится какой-то способ стилизовать числа дат. Это возможно как есть, но я также хочу иметь возможность оформлять номера рабочих дней иначе, чем номера выходных, если мне нужно. Итак, я собираюсь включить data-*
Атрибуты специально для этого: data-weekend
и data-today
.
Номера недель
В году 52 недели, иногда 53. Хотя это не очень распространено, может быть неплохо отображать число для данной недели в календаре для дополнительного контекста. Мне нравится иметь его сейчас, даже если я не перестану им пользоваться. Но мы полностью используем его в этом уроке.
Мы будем использовать data-weeknumber
в качестве крючка стиля и включите его в разметку для каждой даты, которая является первой датой недели.
<li data-day="7" data-weeknumber="1" data-weekend=""> <time datetime="2023-01-08">8</time>
</li>
Рендеринг
Давайте разместим календарь на странице! Мы уже знаем, что <kal-el>
это имя нашего пользовательского элемента. Первое, что нам нужно настроить, это установить firstDay
свойство на нем, поэтому календарь знает, является ли воскресенье или какой-либо другой день первым днем недели.
<kal-el data-firstday="${ config.info.firstDay }">
Мы будем использовать литералы шаблона для рендеринга разметки. Чтобы отформатировать даты для международной аудитории, мы будем использовать Intl.DateTimeFormat
API, снова используя locale
мы указали ранее.
Месяц и год
Когда мы называем month
, мы можем установить, хотим ли мы использовать long
имя (например, февраль) или short
имя (например, февраль). Давайте использовать long
имя, так как это заголовок над календарем:
<time datetime="${year}-${(pad(month))}"> ${new Intl.DateTimeFormat( locale, { month:'long'}).format(date)} <i>${year}</i>
</time>
Названия дней недели
Для дней недели, отображаемых над сеткой дат, нам нужны как long
(например, «воскресенье») и short
(сокращенно, т.е. «солнце») названия. Таким образом, мы можем использовать «короткое» имя, когда в календаре мало места:
Intl.DateTimeFormat([locale], { weekday: 'long' })
Intl.DateTimeFormat([locale], { weekday: 'short' })
Давайте создадим небольшой вспомогательный метод, который немного упростит вызов каждого из них:
const weekdays = (firstDay, locale) => { const date = new Date(0); const arr = [...Array(7).keys()].map(i => { date.setDate(5 + i) return { long: new Intl.DateTimeFormat([locale], { weekday: 'long'}).format(date), short: new Intl.DateTimeFormat([locale], { weekday: 'short'}).format(date) } }) for (let i = 0; i < 8 - firstDay; i++) arr.splice(0, 0, arr.pop()); return arr;
}
Вот как мы вызываем это в шаблоне:
<ol> ${weekdays(config.info.firstDay,locale).map(name => ` <li> <abbr title="${name.long}">${name.short}</abbr> </li>`).join('') }
</ol>
Номера дней
И, наконец, дни, завернутые в <ol>
элемент:
${[...Array(numOfDays).keys()].map(i => { const cur = new Date(year, month, i + 1); let day = cur.getDay(); if (day === 0) day = 7; const today = renderToday && (config.today.day === i + 1) ? ' data-today':''; return ` <li data-day="${day}"${today}${i === 0 || day === config.info.firstDay ? ` data-weeknumber="${new Intl.NumberFormat(locale).format(getWeek(cur))}"`:''}${config.info.weekend.includes(day) ? ` data-weekend`:''}> <time datetime="${year}-${(pad(month))}-${pad(i)}" tabindex="0"> ${new Intl.NumberFormat(locale).format(i + 1)} </time> </li>`
}).join('')}
Давайте разберемся с этим:
- Мы создаем «фиктивный» массив на основе переменной «количество дней», которую мы будем использовать для итерации.
- Мы создаем
day
переменная для текущего дня в итерации. - Устраняем несоответствие между
Intl.Locale
API иgetDay()
. - Если же линия индикатора
day
равноtoday
, мы добавляемdata-*
атрибутов. - Наконец, мы возвращаем
<li>
элемент в виде строки с объединенными данными. tabindex="0"
делает элемент доступным для фокусировки при использовании навигации с помощью клавиатуры после любых положительных значений tabindex (Примечание: вы должны никогда Добавить положительный tabindex-значения)
к «дополнить» числа в datetime
атрибут, мы используем небольшой вспомогательный метод:
const pad = (val) => (val + 1).toString().padStart(2, '0');
Номер недели
Опять же, «номер недели» — это место, где неделя приходится на 52-недельный календарь. Для этого мы также используем небольшой вспомогательный метод:
function getWeek(cur) { const date = new Date(cur.getTime()); date.setHours(0, 0, 0, 0); date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); const week = new Date(date.getFullYear(), 0, 4); return 1 + Math.round(((date.getTime() - week.getTime()) / 86400000 - 3 + (week.getDay() + 6) % 7) / 7);
}
я этого не писал getWeek
-метод. Это очищенная версия этот скрипт.
Вот и все! Благодаря Intl.Locale
, Intl.DateTimeFormat
и Intl.NumberFormat
API, теперь мы можем просто изменить lang
-атрибут <html>
элемент для изменения контекста календаря в зависимости от текущего региона:
Стиль календаря
Вы можете вспомнить, как все дни всего лишь один <ol>
с элементами списка. Чтобы оформить их в удобочитаемый календарь, мы погрузимся в удивительный мир CSS Grid. На самом деле, мы можем перепрофилировать ту же сетку из стартовый шаблон календаря прямо здесь, на CSS-Tricks, но немного обновил :is()
реляционное псевдо для оптимизации кода.
Обратите внимание, что по пути я определяю настраиваемые переменные CSS (и добавляю к ним префикс ---kalel-
во избежание конфликтов).
kal-el :is(ol, ul) { display: grid; font-size: var(--kalel-fz, small); grid-row-gap: var(--kalel-row-gap, .33em); grid-template-columns: var(--kalel-gtc, repeat(7, 1fr)); list-style: none; margin: unset; padding: unset; position: relative;
}
Давайте нарисуем границы вокруг чисел даты, чтобы визуально разделить их:
kal-el :is(ol, ul) li { border-color: var(--kalel-li-bdc, hsl(0, 0%, 80%)); border-style: var(--kalel-li-bds, solid); border-width: var(--kalel-li-bdw, 0 0 1px 0); grid-column: var(--kalel-li-gc, initial); text-align: var(--kalel-li-tal, end); }
Сетка из семи столбцов отлично работает, когда первый день месяца причислены первый день недели для выбранного региона). Но это скорее исключение, чем правило. В большинстве случаев нам нужно перенести первый день месяца на другой день недели.
Помните обо всех доп. data-*
атрибуты, которые мы определили при написании нашей разметки? Мы можем подключиться к ним, чтобы обновить столбец сетки (--kalel-li-gc
) номер первого числа месяца ставится на:
[data-firstday="1"] [data-day="3"]:first-child { --kalel-li-gc: 1 / 4;
}
В этом случае мы переходим от первого столбца сетки к четвертому столбцу сетки, что автоматически «подталкивает» следующий элемент (День 2) к пятому столбцу сетки и так далее.
Давайте добавим немного стиля к «текущей» дате, чтобы она выделялась. Это только мои стили. Вы можете полностью делать то, что вы хотели бы здесь.
[data-today] { --kalel-day-bdrs: 50%; --kalel-day-bg: hsl(0, 86%, 40%); --kalel-day-hover-bgc: hsl(0, 86%, 70%); --kalel-day-c: #fff;
}
Мне нравится идея стилизовать номера дат для выходных иначе, чем для будних дней. Я собираюсь использовать красноватый цвет, чтобы стилизовать их. Обратите внимание, что мы можем достичь :not()
псевдокласс, чтобы выбрать их, оставив только текущую дату:
[data-weekend]:not([data-today]) { --kalel-day-c: var(--kalel-weekend-c, hsl(0, 86%, 46%));
}
О, и давайте не будем забывать номера недель, которые идут перед номером первой даты каждой недели. Мы использовали data-weeknumber
атрибут в разметке для этого, но числа не будут отображаться, если мы не покажем их с помощью CSS, что мы можем сделать на ::before
псевдоэлемент:
[data-weeknumber]::before { display: var(--kalel-weeknumber-d, inline-block); content: attr(data-weeknumber); position: absolute; inset-inline-start: 0; /* additional styles */
}
На этом мы технически закончили! Мы можем визуализировать сетку календаря, которая показывает даты текущего месяца, с учетом локализации данных по локали и обеспечения правильной семантики календаря. И все, что мы использовали, это ванильный JavaScript и CSS!
Но давайте возьмем это Еще один шаг...
Отрисовка всего года
Возможно, вам нужно отобразить даты за весь год! Таким образом, вместо того, чтобы отображать текущий месяц, вы можете отобразить все сетки месяцев для текущего года.
Хорошая вещь в подходе, который мы используем, заключается в том, что мы можем вызывать render
метод столько раз, сколько мы хотим, и просто меняем целое число, которое идентифицирует месяц в каждом экземпляре. Давайте назовем это 12 раз, исходя из текущего года.
так же просто, как позвонить в render
-метод 12 раз и просто измените целое число для month
- i
:
[...Array(12).keys()].map(i => render( new Date(date.getFullYear(), i, date.getDate()), config.locale, date.getMonth() )
).join('')
Вероятно, хорошей идеей будет создать новую родительскую оболочку для отображаемого года. Каждая календарная сетка представляет собой <kal-el>
элемент. Давайте вызовем новую родительскую оболочку <jor-el>
, Где Джор-Эл — имя отца Кал-Эла..
<jor-el id="app" data-year="true"> <kal-el data-firstday="7"> <!-- etc. --> </kal-el> <!-- other months -->
</jor-el>
Мы можем использовать <jor-el>
чтобы создать сетку для наших сеток. Итак, мета!
jor-el { background: var(--jorel-bg, none); display: var(--jorel-d, grid); gap: var(--jorel-gap, 2.5rem); grid-template-columns: var(--jorel-gtc, repeat(auto-fill, minmax(320px, 1fr))); padding: var(--jorel-p, 0);
}
Финальная демонстрация
Бонус: календарь конфетти
Я прочитал прекрасную книгу под названием Создание и разрушение сетки на днях и наткнулась на вот этот красивый «новогодний плакат»:
Я подумал, что мы могли бы сделать что-то подобное, ничего не меняя в HTML или JavaScript. Я позволил себе включить полные названия месяцев и числа вместо названий дней, чтобы сделать их более читабельными. Наслаждаться!
- SEO-контент и PR-распределение. Получите усиление сегодня.
- Платоблокчейн. Интеллект метавселенной Web3. Расширение знаний. Доступ здесь.
- Источник: https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/
- :является
- $UP
- 1
- 11
- 2023
- 7
- 8
- 9
- 98
- a
- в состоянии
- О нас
- об этом
- выше
- Absolute
- доступность
- точно
- на самом деле
- дополнительный
- После
- Все
- в одиночестве
- уже
- и
- API
- API
- приложение
- признательность
- подхода
- соответствующий
- МЫ
- около
- массив
- статьи
- AS
- назначенный
- связанный
- At
- Атрибуты
- аудитория
- автоматически
- избежать
- фон
- основанный
- BE
- красивая
- , так как:
- до
- Преимущества
- ЛУЧШЕЕ
- между
- большой
- Немного
- книга
- Коробка
- Ломать
- Разрыв
- Строительство
- by
- Календарь
- призывают
- под названием
- вызова
- CAN
- Может получить
- случаев
- изменение
- изменения
- проверка
- Проверки
- код
- цвет
- Column
- COM
- Общий
- полный
- компонент
- заблуждение
- соображения
- содержит
- содержание
- контекст
- Холодные
- может
- страна
- чехол для варгана
- покрытый
- Создайте
- Пересекать
- CSS
- Текущий
- изготовленный на заказ
- данным
- Время
- Финики
- день
- Дней
- более глубокий
- По умолчанию
- по умолчанию
- определенный
- определяющий
- в зависимости
- подробнее
- Определять
- застройщиков
- различный
- несоответствие
- Дисплей
- документ
- Dont
- вниз
- e
- каждый
- Ранее
- легче
- edition
- элемент
- Двигатели
- обеспечение
- Весь
- и т.д
- Эфир (ETH)
- Даже
- точно,
- пример
- отлично
- исключение
- существует
- дополнительно
- Падение
- Водопад
- далеко
- страх
- фев
- февраль
- фигурный
- в заключение
- конец
- First
- фиксированный
- поток
- Что касается
- формат
- Четвертый
- пятница
- от
- полный
- функция
- Gain
- разрыв
- получить
- получающий
- данный
- дает
- Go
- будет
- хорошо
- сетка
- сетка-шаблон-столбцы
- Управляемость
- Жесткий
- Есть
- имеющий
- помощь
- помогает
- здесь
- Выделите
- надежды
- Как
- HTML
- HTTPS
- i
- идея
- идеальный
- идентифицирует
- in
- включают
- info
- информация
- начальный
- первоначально
- пример
- вместо
- Мультиязычность
- IT
- пункты
- итерация
- ЕГО
- январь
- JavaScript
- только один
- Знать
- известный
- ДЛИННЫЙ
- язык
- Фамилия
- Планировка
- уход
- свобода
- библиотеки
- такое как
- линий
- связанный
- Список
- мало
- Локализация
- Длинное
- посмотреть
- искать
- серия
- сделать
- ДЕЛАЕТ
- Создание
- управление
- многих
- Маржа
- математике
- макс-ширина
- просто
- идти
- метод
- может быть
- против
- модифицировало
- момент
- понедельник
- Месяц
- месяцев
- БОЛЕЕ
- самых
- Mozilla
- имя
- имена
- родной
- Навигация
- Необходимость
- Новые
- следующий
- Нюанс
- номер
- номера
- объект
- of
- on
- ONE
- онлайн
- Оптимизировать
- Другое
- Другое
- в противном случае
- собственный
- площадка
- страница
- планета
- планирование
- Платон
- Платон Интеллектуальные данные
- ПлатонДанные
- должность
- положительный
- возможное
- довольно
- первичный
- вероятно
- процесс
- правильный
- должным образом
- собственность
- при условии
- Полагая
- САЙТ
- скорее
- достигать
- Читать
- красновато
- область
- районы
- оказание
- обязательный
- возвращают
- Возвращает
- показывать
- корень
- РЯД
- Правило
- s
- то же
- Поиск
- Поисковые системы
- Раздел
- выбранный
- семантика
- отдельный
- набор
- установка
- настройки
- семь
- сдвиг
- Короткое
- должен
- показывать
- показанный
- Шоу
- аналогичный
- просто
- просто
- с
- одинарной
- небольшой
- So
- твердый
- некоторые
- удалось
- Space
- конкретно
- указанный
- стоять
- стоит
- Начало
- Область
- Шаг
- Придерживаться
- По-прежнему
- стиль
- супер
- TAG
- взять
- шаблон
- который
- Ассоциация
- Их
- Эти
- задача
- вещи
- Think
- время
- раз
- Название
- в
- сегодня
- вместе
- ПОЛНОСТЬЮ
- к
- переведите
- правда
- вторник
- учебник
- типичный
- типично
- юникода
- Обновление ПО
- обновление
- us
- использование
- VAL
- ценностное
- Наши ценности
- переменные
- версия
- W3
- Путь..
- способы
- неделя
- уик-энд
- Недели
- ЧТО Ж
- Что
- Что такое
- Колесо
- будь то
- который
- в то время как
- будете
- ветер
- в
- без
- замечательный
- слова
- Работа
- работает
- Мир
- мире
- бы
- Завернутый
- записывать
- письмо
- год
- зефирнет