Naptárkészítés a kisegítő lehetőségek és a nemzetközivé válás szem előtt tartásával

Naptárkészítés a kisegítő lehetőségek és a nemzetközivé válás szem előtt tartásával

Egy gyors keresés itt a CSS-Tricks oldalon megmutatja, hányféleképpen lehet megközelíteni a naptárakat. Néhányan megmutatják, hogyan A CSS Grid hatékonyan tudja létrehozni az elrendezést. Néhányan megpróbálják valós adatokat visz be a keverékbe. Néhány keretre támaszkodni hogy segítse az államigazgatást.

A naptárösszetevők összeállítása során számos szempontot figyelembe kell venni – sokkal többet, mint amit az általam linkelt cikkek tárgyalnak. Ha belegondolunk, a naptárak tele vannak árnyalatokkal, az időzónák és a dátumformátumok kezelésétől a lokalizációig, sőt még azelőtt is, hogy a dátumok egyik hónapról a másikra folyjanak… és ez még azelőtt még azelőtt, hogy a naptár helyétől függően a hozzáférhetőség és a további elrendezési megfontolásokba belemennénk. megjelenik és miegymás.

Sok fejlesztő fél a Date() tárgy és ragaszkodjon a régebbi könyvtárakhoz, mint pl moment.js. De míg a dátumokkal és a formázással kapcsolatban sok a baj, a JavaScript sok klassz API-val és mással segít!

2023. januári naptárrács.
Naptárkészítés a kisegítő lehetőségek és a nemzetközivé válás szem előtt tartásával

Nem szeretném itt újra létrehozni a kereket, de megmutatom, hogyan készítsünk egy jó naptárt vaníliás JavaScripttel. utánanézünk megközelíthetőség, szemantikus jelöléssel és képernyőolvasó-barát <time> -tags — valamint nemzetközivé és a formázás, használni a Intl.Locale, Intl.DateTimeFormat és a Intl.NumberFormat-API-k.

Más szóval, naptárt készítünk… csak az olyan extra függőségek nélkül, amelyeket egy ilyen oktatóanyagban általában használnak, és néhány olyan árnyalattal, amelyeket általában nem lát. És eközben remélem, hogy új elismerésre tesz szert az újabb dolgok iránt, amelyekre a JavaScript képes, miközben képet kap arról, hogy milyen dolgok járnak a fejemben, amikor valami hasonlót összeállítok.

Először is, névadás

Hogyan nevezzük naptárkomponensünket? Az én anyanyelvemen „kalender elemnek” hívnák, ezért használjuk ezt, és rövidítsük le „Kal-El”-re – más néven Superman neve a Krypton bolygón.

Hozzon létre egy függvényt a dolgok elindításához:

function kalEl(settings = {}) { ... }

Ez a módszer fog renderelni egyetlen hónap. Később ezt a módszert innen fogjuk nevezni [...Array(12).keys()] egy egész évet renderelni.

Kiindulási adatok és nemzetközivé válás

Az egyik gyakori dolog, amit egy tipikus online naptár tesz, az az aktuális dátum kiemelése. Tehát hozzunk létre egy referenciát ehhez:

const today = new Date();

Ezután létrehozunk egy „konfigurációs objektumot”, amelyet egyesítünk az opcionálissal settings az elsődleges módszer célja:

const config = Object.assign( { locale: (document.documentElement.getAttribute('lang') || 'en-US'), today: { day: today.getDate(), month: today.getMonth(), year: today.getFullYear() } }, settings
);

Ellenőrizzük, hogy a gyökérelem (<html>) tartalmazza a lang-attribútum -val helyszín info; ellenkező esetben visszatérünk a használathoz en-US. Ez az első lépés afelé a naptár nemzetközivé tétele.

Azt is meg kell határoznunk, hogy melyik hónapot jelenítsük meg először a naptár megjelenítésekor. Ezért meghosszabbítottuk a config tárgy az elsődleges date. Így, ha nincs dátum megadva a settings objektumot használjuk today hivatkozás helyett:

const date = config.date ? new Date(config.date) : today;

Kicsit több információra van szükségünk a naptár megfelelő formázásához a területi beállítások alapján. Például előfordulhat, hogy nem tudjuk, hogy a hét első napja vasárnap vagy hétfő, a területtől függően. Ha megvan az infó, szuper! De ha nem, akkor frissítjük a Intl.Locale API. Az API rendelkezik a weekInfo tárgy amely visszaadja a firstDay olyan ingatlant, amely gond nélkül pontosan azt adja, amit keresünk. Azt is megkaphatjuk, hogy a hét mely napjai vannak hozzárendelve a weekend:

if (!config.info) config.info = new Intl.Locale(config.locale).weekInfo || { firstDay: 7, weekend: [6, 7] };

Ismét tartalékokat hozunk létre. A hét „első napja” számára en-US vasárnap van, tehát az alapértelmezett érték 7. Ez egy kicsit zavaró, mivel a getDay módszer JavaScriptben a napokat adja vissza, mint [0-6], Ahol 0 vasárnap van… ne kérdezd miért. A hétvégék tehát szombat és vasárnap [6, 7].

Mielőtt nálunk volt a Intl.Locale API és annak weekInfo módszerrel, elég nehéz volt létrehozni egy nemzetközi naptárt sok **objektum és tömb nélkül, amelyek minden egyes területről vagy régióról tartalmaznak információkat. Manapság ez könnyű-peasy. Ha bemegyünk en-GB, a metódus a következőket adja vissza:

// en-GB
{ firstDay: 1, weekend: [6, 7], minimalDays: 4
}

Egy olyan országban, mint Brunei (ms-BN), a hétvége péntek és vasárnap:

// ms-BN
{ firstDay: 7, weekend: [5, 7], minimalDays: 1
}

Lehet, hogy vajon mi az minimalDays ingatlan az. Az a a hónap első hetében szükséges legkevesebb nap teljes hétnek számításához. Egyes régiókban ez csak egy nap lehet. Mások számára ez akár egy teljes hét nap is lehet.

Ezután létrehozunk egy render módszer a miénkben kalEl-módszer:

const render = (date, locale) => { ... }

Még mindig szükségünk van néhány további adatra, mielőtt bármit is megjelenítenénk:

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);

Az utolsó a Boolean amely ellenőrzi, hogy today abban a hónapban létezik, amelyet hamarosan megjelenítünk.

Szemantikus jelölés

Egy pillanat alatt mélyebbre fogunk jutni a renderelésben. Először azonban meg akarok győződni arról, hogy az általunk beállított részletekhez szemantikus HTML-címkék vannak társítva. Ha ezt a dobozból azonnal beállítja, a kezdetektől fogva a kisegítő lehetőségek előnyeit élvezhetjük.

Naptárcsomagoló

Először is megvan a nem szemantikus burkoló: <kal-el>. Ez rendben van, mert nincs szemantika <calendar> címke vagy bármi hasonló. Ha nem egyedi elemet készítenénk, <article> lehet a legmegfelelőbb elem, mivel a naptár megállhat a saját oldalán.

A hónapok nevei

A <time> elem nagyon fontos lesz számunkra, mert segít lefordítani a dátumokat olyan formátumba, amelyet a képernyőolvasók és a keresőmotorok pontosabban és következetesebben tudnak elemezni. Például a következőképpen adhatjuk meg a „2023. január” kifejezést a jelölésünkben:

<time datetime="2023-01">January <i>2023</i></time>

Napok nevei

A naptár dátumai feletti sor, amely a hét napjainak nevét tartalmazza, trükkös lehet. Ideális, ha minden naphoz ki tudjuk írni a teljes neveket – pl. Vasárnap, hétfő, kedd stb. – de ez sok helyet foglalhat el. Tehát rövidítsük le a neveket most egy an belül <ol> ahol minden nap egy <li>:

<ol> <li><abbr title="Sunday">Sun</abbr></li> <li><abbr title="Monday">Mon</abbr></li> <!-- etc. -->
</ol>

Trükkös lehet a CSS, hogy mindkét világból a legjobbat hozzuk ki. Például, ha egy kicsit így módosítottuk a jelölést:

<ol> <li> <abbr title="S">Sunday</abbr> </li>
</ol>

…alapértelmezés szerint a teljes neveket kapjuk. Ezután „elrejthetjük” a teljes nevet, amikor elfogy a hely, és megjeleníthetjük a title attribútum helyett:

@media all and (max-width: 800px) { li abbr::after { content: attr(title); }
}

De nem erre megyünk, mert a Intl.DateTimeFormat Az API itt is segíthet. Erre a következő részben fogunk rátérni, amikor a rendereléssel foglalkozunk.

Napszámok

A naptárrácsban minden dátum kap egy számot. Minden szám egy listaelem (<li>) egy rendezett listában (<ol>), és a soron belüli <time> címke a tényleges számot foglalja össze.

<li> <time datetime="2023-01-01">1</time>
</li>

És bár egyelőre nem tervezek semmilyen stílust, tudom, hogy valami módot szeretnék alakítani a dátumok számában. Ez lehetséges úgy, ahogy van, de azt is szeretném, hogy a hétköznapi számokat másképp szabhassam, mint a hétvégi számokat, ha szükséges. Szóval, beleteszem data-* attribútumok kifejezetten erre: data-weekend és a data-today.

Heti számok

Egy évben 52 hét van, néha 53. Bár ez nem túl gyakori, hasznos lehet egy adott hét számát megjeleníteni a naptárban további kontextus érdekében. Most szeretem, ha nem használom, még ha nem is használom. De ebben az oktatóanyagban teljes mértékben használni fogjuk.

Használjuk a data-weeknumber attribútumot stíluskampóként, és szerepeltesse azt minden olyan dátum jelölésében, amely a hét első dátuma.

<li data-day="7" data-weeknumber="1" data-weekend=""> <time datetime="2023-01-08">8</time>
</li>

vakolás

Tegyük oldalra a naptárat! Ezt már tudjuk <kal-el> az egyéni elemünk neve. Az első dolog, amit konfigurálnunk kell, az a firstDay tulajdonság rajta, így a naptár tudja, hogy vasárnap vagy más nap a hét első napja.

<kal-el data-firstday="${ config.info.firstDay }">

Használunk sablon literálok a jelölés megjelenítéséhez. A dátumok nemzetközi közönség számára történő formázásához a Intl.DateTimeFormat API, ismét a locale korábban meghatároztuk.

A hónap és az év

Amikor hívjuk a month, beállíthatjuk, hogy szeretnénk-e használni a long név (pl. február) vagy a short név (pl. febr.). Használjuk a long név, mivel ez a cím a naptár felett:

<time datetime="${year}-${(pad(month))}"> ${new Intl.DateTimeFormat( locale, { month:'long'}).format(date)} <i>${year}</i>
</time>

Hétköznapok nevei

A dátumrács felett megjelenő hétköznapokhoz mind a long (pl. „vasárnap”) és short (rövidítve, azaz „Nap”) nevek. Így a „rövid” nevet használhatjuk, ha a naptárban kevés a hely:

Intl.DateTimeFormat([locale], { weekday: 'long' })
Intl.DateTimeFormat([locale], { weekday: 'short' })

Készítsünk egy kis segítő módszert, amely egy kicsit megkönnyíti mindegyik felhívását:

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;
}

Így hívjuk meg ezt a sablonban:

<ol> ${weekdays(config.info.firstDay,locale).map(name => ` <li> <abbr title="${name.long}">${name.short}</abbr> </li>`).join('') }
</ol>

Napszámok

És végül, a napok, csomagolva egy <ol> elem:

${[...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('')}

Bontsuk szét:

  1. Létrehozunk egy „dummy” tömböt a „napok száma” változó alapján, amelyet az iterációhoz használunk.
  2. Létrehozunk egy day változó az aktuális napra az iterációban.
  3. Kijavítjuk az eltérést a Intl.Locale API és getDay().
  4. Ha a day egyenlő today, hozzáadjuk a data-* tulajdonság.
  5. Végül visszaadjuk a <li> elemet egyesített adatokkal rendelkező karakterláncként.
  6. tabindex="0" az elemet fókuszálhatóvá teszi a billentyűzetes navigáció használatakor minden pozitív tabindex érték után (Megjegyzés: érdemes soha hozzá pozitív tabindex-értékek)

Nak nek „betétezze” a számokat a datetime attribútum, egy kis segítő módszert használunk:

const pad = (val) => (val + 1).toString().padStart(2, '0');

Hét száma

Ismét a „hétszám” az, ahol egy hét esik egy 52 hetes naptárban. Ehhez egy kis segítő módszert is alkalmazunk:

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);
}

ezt nem én írtam getWeek-módszer. Ez egy letisztított változata ez a szkript.

És ez az! Köszönhetően a Intl.Locale, Intl.DateTimeFormat és a Intl.NumberFormat API-kat, most egyszerűen megváltoztathatjuk a lang- attribútuma a <html> elem a naptár kontextusának megváltoztatásához az aktuális régió alapján:

2023. januári naptárrács.
de-DE
2023. januári naptárrács.
fa-IR
2023. januári naptárrács.
zh-Hans-CN-u-nu-hanidec

A naptár stílusának kialakítása

Talán emlékszel arra, hogy minden nap csak egy <ol> listaelemekkel. Ahhoz, hogy ezeket egy olvasható naptárká alakítsuk, belemerülünk a CSS Grid csodálatos világába. Valójában ugyanazt a rácsot újrahasznosíthatjuk egy kezdő naptársablon itt a CSS-Tricks oldalon, de egy kicsit frissítve a :is() relációs pszeudo a kód optimalizálásához.

Figyelje meg, hogy menet közben konfigurálható CSS-változókat definiálok (és előtagot adok nekik ---kalel- konfliktusok elkerülése érdekében).

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;
}
Hétoszlopos naptárrács, rácsvonalakkal.
Naptárkészítés a kisegítő lehetőségek és a nemzetközivé válás szem előtt tartásával

Rajzoljunk szegélyeket a dátumszámok köré, hogy vizuálisan el tudjuk különíteni őket:

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); }

A hétoszlopos rács jól működik, amikor a hónap első napja van Is a hét első napja a kiválasztott területhez). De ez inkább kivétel, mint szabály. Legtöbbször a hónap első napját át kell helyeznünk egy másik hétköznapra.

A hónap első napját mutatja, amely csütörtökre esik.
Naptárkészítés a kisegítő lehetőségek és a nemzetközivé válás szem előtt tartásával

Ne feledje az összes extrát data-* milyen attribútumokat határoztunk meg a jelölésünk írásakor? Ezekre rákapcsolhatunk, hogy melyik rácsoszlopot frissítsük (--kalel-li-gc) a hónap első dátumának száma a következőre kerül:

[data-firstday="1"] [data-day="3"]:first-child { --kalel-li-gc: 1 / 4;
}

Ebben az esetben az első rácsoszloptól a negyedik rácsoszlopig terjedünk – ami automatikusan „tolja” a következő elemet (2. nap) az ötödik rácsoszlopba, és így tovább.

Adjunk hozzá egy kis stílust az „aktuális” dátumhoz, így kiemelkedik. Ezek csak az én stílusaim. Itt teljesen azt csinálhatsz, amit akarsz.

[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;
}

Tetszik az ötlet, hogy a hétvégék dátumszámait másképp alakítsuk ki, mint a hétköznapokat. Vöröses színt fogok használni a stílushoz. Vegye figyelembe, hogy elérhetjük a :not() pszeudoosztály, hogy kijelölje őket, miközben az aktuális dátumot magára hagyja:

[data-weekend]:not([data-today]) { --kalel-day-c: var(--kalel-weekend-c, hsl(0, 86%, 46%));
}

Ja, és ne feledkezzünk meg a hét számokról sem, amelyek minden héten az első dátum előtt állnak. Használtuk a data-weeknumber attribútumot a jelölésben, de a számok valójában csak akkor jelennek meg, ha felfedjük őket CSS-sel, amit megtehetünk a ::before pszeudoelem:

[data-weeknumber]::before { display: var(--kalel-weeknumber-d, inline-block); content: attr(data-weeknumber); position: absolute; inset-inline-start: 0; /* additional styles */
}

Ezen a ponton technikailag készen vagyunk! Renderelhetünk egy naptárrácsot, amely az aktuális hónap dátumait mutatja, kiegészítve az adatok terület szerinti lokalizálásával és annak biztosításával, hogy a naptár megfelelő szemantikát használjon. És csak vanília JavaScriptet és CSS-t használtunk!

De vegyük ezt Még egy lépés...

Egy egész év megjelenítése

Talán egy teljes év dátumát kell megjelenítenie! Tehát az aktuális hónap megjelenítése helyett érdemes megjeleníteni az aktuális év összes hónapi rácsát.

Nos, az általunk használt megközelítésben az a szép, hogy hívhatjuk a render metódust annyiszor, ahányszor csak akarjuk, és csupán a hónapot azonosító egész számot változtatjuk meg minden egyes példányon. Nevezzük 12-szer az aktuális év alapján.

olyan egyszerű, mint felhívni a render-method 12-szer, és csak módosítsa az egész számot month - i:

[...Array(12).keys()].map(i => render( new Date(date.getFullYear(), i, date.getDate()), config.locale, date.getMonth() )
).join('')

Valószínűleg jó ötlet egy új szülőburkoló létrehozása a megjelenített évhez. Minden naptárrács a <kal-el> elem. Nevezzük az új szülőburkolót <jor-el>, Ahol Jor-El Kal-El apjának a neve.

<jor-el id="app" data-year="true"> <kal-el data-firstday="7"> <!-- etc. --> </kal-el> <!-- other months -->
</jor-el>

Tudjuk használni <jor-el> hogy létrehozzunk egy rácsot a rácsaink számára. Szóval meta!

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);
}

Végső demó

Bónusz: Konfetti naptár

Olvastam egy kiváló könyvet, az ún A rács készítése és feltörése a minap, és belebotlottam ebbe a gyönyörű „újévi plakátba”:

Making Calendars With Accessibility and Internationalization in Mind PlatoBlockchain Data Intelligence. Vertical Search. Ai.
Forrás: A rács készítése és feltörése (2. kiadás) írta Timothy Samara

Arra gondoltam, hogy valami hasonlót tehetünk anélkül, hogy bármit is megváltoztatnánk a HTML-ben vagy a JavaScriptben. Megvettem a bátorságot, hogy hónapokig teljes neveket, a napok neve helyett számokat írjak bele, hogy könnyebben olvasható legyen. Élvezd!

Időbélyeg:

Még több CSS trükkök