Kalendrite koostamine, pidades silmas juurdepääsetavust ja rahvusvahelistumist

Kalendrite koostamine, pidades silmas juurdepääsetavust ja rahvusvahelistumist

Kiire otsing siin CSS-Tricksis näitab, kui palju erinevaid viise on kalendritele lähenemiseks. Mõned näitavad, kuidas CSS Grid saab paigutuse tõhusalt luua. Mõned üritavad tuua segusse tegelikud andmed. Mõned tugineda raamistikule aidata riigi juhtimisel.

Kalendri komponendi loomisel on palju kaalutlusi – palju rohkem kui minu lingitud artiklites käsitletud. Kui järele mõelda, on kalendrid tulvil nüansse, alates ajavööndite ja kuupäevavormingute käsitlemisest kuni lokaliseerimiseni ja isegi selle tagamiseni, et kuupäevad liiguvad ühest kuust teise… ja seda enne, kui jõuame ligipääsetavuse ja täiendavate paigutuse kaalutluste juurde olenevalt kalendri asukohast. kuvatakse ja mis muud.

Paljud arendajad kardavad Date() objekt ja pidage kinni vanematest raamatukogudest nagu moment.js. Kuid kuigi kuupäevade ja vormindamise osas on palju häid asju, on JavaScriptil palju lahedaid API-sid ja muud, mis aitavad teid!

2023. aasta jaanuari kalendriruudustik.
Kalendrite koostamine, pidades silmas juurdepääsetavust ja rahvusvahelistumist

Ma ei taha siin ratast uuesti luua, kuid näitan teile, kuidas saame vanilje JavaScriptiga hea kalendri. Me uurime kättesaadavus, kasutades semantilist märgistust ja ekraanilugejasõbralikku <time> -sildid — samuti rahvusvahelistumine ja vormingu, kasutades Intl.Locale, Intl.DateTimeFormat ja Intl.NumberFormat- API-d.

Teisisõnu, me koostame kalendrit... ainult ilma lisasõltuvusteta, mida tavaliselt sellises õpetuses kasutatakse, ja mõningate nüanssidega, mida te tavaliselt ei näe. Ja selle käigus loodan, et saate uue hinnangu uuemate asjade vastu, mida JavaScript saab teha, saades samal ajal aimu asjadest, mis mulle midagi sellist kokku pannes pähe tulevad.

Kõigepealt nime panemine

Mida peaksime nimetama oma kalendrikomponendiks? Minu emakeeles kutsutaks seda "kalendri elemendiks", nii et kasutame seda ja lühendagem "Kal-El" - tuntud ka kui Supermani nimi planeedil Krypton.

Loome asjade käimalükkamiseks funktsiooni:

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

See meetod renderdab üksainus kuu. Hiljem nimetame seda meetodit [...Array(12).keys()] renderdada terve aasta.

Algandmed ja rahvusvahelistumine

Üks tavalisemaid asju, mida tavaline veebikalender teeb, on praeguse kuupäeva esiletõstmine. Loome selle jaoks viite:

const today = new Date();

Järgmisena loome "konfiguratsiooniobjekti", mille liidame valikulise objektiga settings esmase meetodi objekt:

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

Kontrollime, kas juurelement (<html>) sisaldab a lang-atribuut koos locale info; vastasel juhul jätkame kasutamist en-US. See on esimene samm selle poole kalendri rahvusvahelistumine.

Samuti peame määrama, millist kuud kalendri renderdamisel algselt kuvada. Seetõttu pikendasime config objekt primaarsega date. Sel viisil, kui kuupäev pole esitatud settings objekti, kasutame today selle asemel viide:

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

Kalendri lokaadi alusel õigeks vormindamiseks vajame veidi rohkem teavet. Näiteks ei pruugi me teada, kas nädala esimene päev on olenevalt asukohast pühapäev või esmaspäev. Kui meil on teavet, siis suurepärane! Aga kui ei, siis värskendame seda kasutades Intl.Locale API. API-l on a weekInfo objekt mis tagastab a firstDay kinnisvara, mis annab meile ilma probleemideta täpselt selle, mida otsime. Samuti saame teada, millised nädalapäevad on määratud weekend:

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

Jällegi loome tagavarasid. Nädala "esimene päev". en-US on pühapäev, seega on selle vaikimisi väärtus 7. See on veidi segane, kuna getDay meetod JavaScriptis tagastab päevad as [0-6], Kus 0 on pühapäev... ärge küsige, miks. Nädalavahetused on seega laupäev ja pühapäev [6, 7].

Enne kui meil oli Intl.Locale API ja selle weekInfo meetod, oli üsna raske luua rahvusvahelist kalendrit ilma paljude **objektide ja massiivideta, mis sisaldaks teavet iga lokaadi või piirkonna kohta. Tänapäeval on see lihtne-peasy. Kui me sisse astume en-GB, meetod tagastab:

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

Sellises riigis nagu Brunei (ms-BN), nädalavahetus on reede ja pühapäev:

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

Võite küsida, mis see minimalDays vara on. See on kõige vähem päevi, mida kuu esimesel nädalal on vaja täisnädalaks lugeda. Mõnes piirkonnas võib see olla vaid üks päev. Teiste jaoks võib see olla tervelt seitse päeva.

Järgmisena loome a render meetod meie sees kalEl- meetod:

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

Enne millegi renderdamist vajame veel mõned andmed, millega töötada:

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

Viimane on a Boolean mis kontrollib, kas today on olemas sellel kuul, mil renderdame.

Semantiline märgistus

Läheme hetkega renderdamisse sügavamale. Kuid kõigepealt tahan veenduda, et meie seadistatud üksikasjadega on seotud semantilised HTML-sildid. Selle kohe karbist välja võtmine annab meile juurdepääsetavuse eeliseid algusest peale.

Kalendri ümbris

Esiteks on meil mittesemantiline ümbris: <kal-el>. See on hea, sest sellel pole semantikat <calendar> silt või midagi sellist. Kui me ei teeks kohandatud elementi, <article> võib olla kõige sobivam element, kuna kalender võib seista eraldi lehel.

Kuude nimed

. <time> element on meie jaoks suur, sest see aitab tõlkida kuupäevad vormingusse, mida ekraanilugejad ja otsingumootorid saaksid täpsemalt ja järjepidevamalt sõeluda. Näiteks saame oma märgistuses sõna „jaanuar 2023” edastada järgmiselt.

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

Päevade nimed

Kalendri kuupäevade kohal olev rida, mis sisaldab nädalapäevade nimesid, võib olla keeruline. Ideaalne on, kui saame iga päeva täisnimed välja kirjutada – nt pühapäev, esmaspäev, teisipäev jne –, kuid see võib võtta palju ruumi. Niisiis, lühendame praegu nimesid an sees <ol> kus iga päev on a <li>:

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

CSS-iga võib olla keeruline, et saada mõlemast maailmast parim. Näiteks kui me muutsime märgistust natuke järgmiselt:

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

… vaikimisi saame täisnimed. Seejärel saame täisnime peita, kui ruum otsa saab, ja kuvada title atribuut selle asemel:

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

Kuid me ei lähe seda teed, sest Intl.DateTimeFormat API saab ka siin abiks olla. Selleni jõuame järgmises jaotises, kui käsitleme renderdamist.

Päeva numbrid

Iga kuupäev kalendriruudustikus saab numbri. Iga number on loendi üksus (<li>) järjestatud loendis (<ol>) ja tekstisisene <time> silt ümbritseb tegelikku arvu.

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

Ja kuigi ma ei plaani veel stiili teha, tean, et tahan kuidagi kuupäevanumbrite stiili kujundada. See on võimalik nii, nagu on, kuid soovin ka, et saaksin vajaduse korral kujundada nädalapäevade numbreid teisiti kui nädalavahetuse numbreid. Niisiis, ma kavatsen kaasata data-* atribuudid spetsiaalselt selleks: data-weekend ja data-today.

Nädalanumbrid

Aastas on 52 nädalat, mõnikord 53. Kuigi see pole eriti levinud, võib olla tore kuvada kalendris konkreetse nädala numbrid täiendava konteksti saamiseks. Mulle meeldib see praegu omada, isegi kui ma ei jäta seda kasutamata. Kuid me kasutame seda selles õpetuses täielikult.

Me kasutame a data-weeknumber atribuut stiilikonksuks ja lisage see iga nädala esimeseks kuupäevaks oleva kuupäeva märgistusse.

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

visualiseerimine

Paneme kalendri lehele! Me juba teame seda <kal-el> on meie kohandatud elemendi nimi. Esimene asi, mida peame selle konfigureerima, on seadistada firstDay vara sellel, nii et kalender teab, kas pühapäev või mõni muu päev on nädala esimene päev.

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

Me kasutame malli literaalid märgistuse renderdamiseks. Kuupäevade vormindamiseks rahvusvahelisele vaatajaskonnale kasutame Intl.DateTimeFormat API, kasutades uuesti locale täpsustasime varem.

Kuu ja aasta

Kui me helistame month, saame määrata, kas tahame kasutada long nimi (nt veebruar) või short nimi (nt veebr.). Kasutame long nimi, kuna see on pealkiri kalendri kohal:

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

Nädalapäevade nimed

Kuupäevade ruudustiku kohal kuvatavate nädalapäevade jaoks vajame mõlemat long (nt “pühapäev”) ja short (lühendatult, st “Päike”) nimed. Nii saame kasutada "lühike" nime, kui kalendris napib ruumi:

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

Teeme väikese abimeetodi, mis teeb igaühele helistamise pisut lihtsamaks:

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

Siin on, kuidas me seda mallis kutsume:

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

Päeva numbrid

Ja lõpuks, päevad, pakitud an <ol> Element:

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

Teeme selle lahti:

  1. Loome muutuja "päevade arv" alusel "näiv" massiivi, mida kasutame itereerimiseks.
  2. Loome a day muutuja iteratsiooni jooksva päeva jaoks.
  3. Parandame lahknevuse Intl.Locale API ja getDay().
  4. Kui day on võrdne today, lisame a data-* atribuut.
  5. Lõpuks tagastame <li> element stringina ühendatud andmetega.
  6. tabindex="0" muudab elemendi fokuseeritavaks klaviatuuriga navigeerimisel pärast positiivseid tabindeksi väärtusi (Märkus: peaksite mitte kunagi lisama positiivne tabindex-väärtused)

Et "polster" numbreid aasta datetime atribuut, kasutame väikest abimeetodit:

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

Nädala number

Jällegi on "nädala number" see, kuhu nädal 52-nädalases kalendris langeb. Kasutame selleks ka väikest abimeetodit:

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

Ma ei kirjutanud seda getWeek- meetod. See on puhastatud versioon see skript.

Ja see ongi kõik! Tänu sellele, Intl.Locale, Intl.DateTimeFormat ja Intl.NumberFormat API-sid, saame nüüd lihtsalt muuta lang- atribuut <html> element kalendri konteksti muutmiseks praeguse piirkonna alusel:

2023. aasta jaanuari kalendriruudustik.
de-DE
2023. aasta jaanuari kalendriruudustik.
fa-IR
2023. aasta jaanuari kalendriruudustik.
zh-Hans-CN-u-nu-hanidec

Kalendri kujundamine

Võib-olla mäletate, kuidas kõik päevad on ainult üks <ol> loendiüksustega. Et kujundada need loetavaks kalendriks, sukeldume CSS Gridi imelisse maailma. Tegelikult saame sama ruudustiku uuesti kasutada algkalendri mall siinsamas CSS-Tricksis, kuid värskendati veidi rakendusega :is() relatsiooniline pseudo koodi optimeerimiseks.

Pange tähele, et defineerin konfigureeritavaid CSS-i muutujaid (ja lisan neile eesliite ---kalel- konfliktide vältimiseks).

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;
}
Seitsmeveeruline kalendriruudustik koos näidatud ruudustikujoontega.
Kalendrite koostamine, pidades silmas juurdepääsetavust ja rahvusvahelistumist

Joonistame kuupäevanumbrite ümber äärised, et neid visuaalselt eraldada:

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

Seitsmeveeruline ruudustik töötab hästi, kui on kuu esimene päev Ka nädala esimene päev valitud lokaadi jaoks). Kuid see on pigem erand kui reegel. Enamasti peame kuu esimese päeva nihutama mõnele muule tööpäevale.

Kuvatakse kuu esimene päev, mis langeb neljapäevale.
Kalendrite koostamine, pidades silmas juurdepääsetavust ja rahvusvahelistumist

Pidage meeles kõik lisa data-* atribuudid, mille me märgistuse kirjutamisel määratlesime? Saame nende külge haakida, et värskendada millist ruudustiku veergu (--kalel-li-gc) kuu esimese kuupäeva number asetatakse järgmisele:

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

Sel juhul liigume esimesest ruudustiku veerust neljanda ruudustiku veeruni – see „tõukab” automaatselt järgmise üksuse (2. päev) viiendasse ruudustiku veergu ja nii edasi.

Lisame “praegusele” kuupäevale veidi stiili, nii et see paistab silma. Need on lihtsalt minu stiilid. Siin saate teha seda, mida soovite.

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

Mulle meeldib mõte kujundada nädalavahetuste kuupäevanumbrid erinevalt tööpäevade jaoks. Ma kasutan nende kujundamiseks punakat värvi. Pange tähele, et saame jõuda :not() pseudoklass nende valimiseks, jättes praeguse kuupäeva üksi:

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

Oh, ja ärgem unustagem nädala numbreid, mis lähevad enne iga nädala esimest kuupäeva numbrit. Kasutasime a data-weeknumber atribuut selle märgistuses, kuid numbreid tegelikult ei kuvata, kui me neid CSS-iga ei avalda, mida saame teha ::before pseudoelement:

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

Praeguseks oleme tehniliselt valmis! Saame renderdada kalendriruudustiku, mis näitab jooksva kuu kuupäevi koos kaalutlustega andmete lokaliseerimiseks lokaadi järgi ja tagab, et kalender kasutaks õiget semantikat. Ja kõik, mida me kasutasime, oli vanilje JavaScript ja CSS!

Aga võtame selle veel üks samm...

Renderdatakse terve aasta

Võib-olla peate kuvama terve aasta kuupäevi! Seega võiksite jooksva kuu renderdamise asemel kuvada kõik jooksva aasta kuu ruudustikud.

Noh, meie kasutatava lähenemisviisi tore on see, et saame helistada render meetodit nii palju kordi kui tahame ja muudame igal eksemplaril lihtsalt kuu identifitseerivat täisarvu. Nimetagem seda jooksva aasta põhjal 12 korda.

sama lihtne kui helistada render-meetodit 12 korda ja muutke lihtsalt täisarvu jaoks month - i:

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

Ilmselt on hea mõte luua renderdatud aasta jaoks uus põhiümbris. Iga kalendriruudustik on a <kal-el> element. Helistame uuele vanemümbrisele <jor-el>, Kus Jor-El on Kal-Eli isa nimi.

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

Meil on võimalik kasutada <jor-el> et luua meie võrkude jaoks ruudustik. Nii et 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);
}

Lõplik demo

Boonus: konfeti kalender

Lugesin suurepärast raamatut nimega Võre tegemine ja lõhkumine teisel päeval ja komistasin selle kauni "uusaasta plakati" otsa:

Making Calendars With Accessibility and Internationalization in Mind PlatoBlockchain Data Intelligence. Vertical Search. Ai.
Allikas: Võre loomine ja purustamine (2. väljaanne) autor Timothy Samara

Arvasin, et saame midagi sarnast teha ilma HTML-is või JavaScriptis midagi muutmata. Olen võtnud endale vabaduse lisada kuudeks täisnimed ja päevanimede asemel numbreid, et muuta see loetavamaks. Nautige!

Ajatempel:

Veel alates CSSi trikid