Platon Data Intelligence.
Pystyhaku ja Ai.

Kalenterien tekeminen esteettömyys ja kansainvälistyminen mielessä

Treffi:

Nopea haku täällä CSS-Tricksissä näyttää kuinka monta eri tapaa lähestyä kalentereita on. Jotkut näyttävät kuinka CSS Grid voi luoda asettelun tehokkaasti. Jotkut yrittävät tuoda todellista dataa sekoitukseen. Jonkin verran luottaa kehyksiin auttamaan valtionhallinnossa.

Kalenterikomponenttia rakennettaessa on otettava huomioon monia näkökohtia – paljon enemmän kuin mitä linkittämissäni artikkeleissa käsitellään. Jos ajattelee sitä, kalenterit ovat täynnä vivahteita, aikavyöhykkeiden ja päivämäärämuotojen käsittelystä lokalisointiin ja jopa sen varmistamiseen, että päivämäärät siirtyvät kuukaudesta toiseen… ja se on ennen kuin pääsemme käsittelemään käytettävyyttä ja muita asettelunäkökohtia riippuen kalenterin sijainnista. näkyy ja mitä muuta.

Monet kehittäjät pelkäävät Date() objekti ja pysy vanhemmissa kirjastoissa, kuten moment.js. Mutta vaikka päivämäärien ja muotoilun suhteen on monia "hankaluuksia", JavaScriptillä on paljon hienoja sovellusliittymiä ja muuta apua!

Tammikuun 2023 kalenteriruudukko.

En halua luoda pyörää uudelleen tänne, mutta näytän sinulle kuinka saamme hyvän kalenterin vanilja JavaScriptillä. Tutkimme asiaa saavutettavuus, käyttämällä semanttista merkintää ja näytönlukijaystävällistä <time> -tunnisteet - sekä kansainvälistyminen ja muotoilu, käyttämällä Intl.Locale, Intl.DateTimeFormat ja Intl.NumberFormat-API:t.

Toisin sanoen, teemme kalenteria… vain ilman ylimääräisiä riippuvuuksia, joita saatat tavallisesti nähdä käytettävän tällaisessa opetusohjelmassa, ja joidenkin vivahteiden kanssa, joita et ehkä näe. Ja tässä prosessissa toivon, että saat uuden arvostuksen uudemmista asioista, joita JavaScript voi tehdä, ja saat käsityksen siitä, millaisia ​​asioita mieleeni tulee, kun kokoan jotain tällaista.

Ensin nimeäminen

Mitä meidän pitäisi kutsua kalenterikomponentiksi? Omalla äidinkielelläni sitä kutsutaan "kalenterielementiksi", joten käytetään sitä ja lyhennetään se "Kal-El" - tunnetaan myös nimellä Supermanin nimi Krypton-planeetalla.

Luodaan funktio, joka saa asiat käyntiin:

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

Tämä menetelmä hahmontaa yksi kuukausi. Myöhemmin kutsumme tätä menetelmää nimellä [...Array(12).keys()] kuvaamaan kokonaisen vuoden.

Alkutiedot ja kansainvälistyminen

Yksi tavallisista asioista, joita tyypillinen online-kalenteri tekee, on korostaa nykyistä päivämäärää. Luodaan siis viite sille:

const today = new Date();

Seuraavaksi luomme "määritysobjektin", jonka yhdistämme valinnaiseen settings ensisijaisen menetelmän kohde:

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

Tarkistamme, onko juurielementti (<html>) sisältää a lang-attribuutti kanssa locale tiedot; muuten palaamme käyttämään en-US. Tämä on ensimmäinen askel kohti kalenterin kansainvälistäminen.

Meidän on myös määritettävä, mikä kuukausi näytetään aluksi, kun kalenteri hahmonnetaan. Siksi pidensimme config esine ensisijaisen kanssa date. Tällä tavalla, jos päivämäärää ei ole ilmoitettu settings objektia, käytämme today viittaus sen sijaan:

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

Tarvitsemme hieman lisätietoja muotoillaksemme kalenterin oikein kielen perusteella. Emme esimerkiksi ehkä tiedä, onko viikon ensimmäinen päivä sunnuntai vai maanantai maa-asetuksen mukaan. Jos meillä on tietoa, hienoa! Mutta jos ei, päivitämme sen käyttämällä Intl.Locale API. API:ssa on a weekInfo objekti joka palauttaa a firstDay kiinteistön, joka antaa meille juuri sen, mitä etsimme, ilman vaivaa. Voimme myös saada mitkä viikonpäivät on määritetty weekend:

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

Jälleen luomme varaosia. Viikon "ensimmäinen päivä". en-US on sunnuntai, joten sen oletusarvo on 7. Tämä on hieman hämmentävää, koska getDay menetelmä JavaScriptissä palauttaa päivät as [0-6], Jossa 0 on sunnuntai… älkää kysykö miksi. Viikonloput ovat siis lauantai ja sunnuntai [6, 7].

Ennen kuin meillä oli Intl.Locale API ja sen weekInfo menetelmällä oli melko vaikeaa luoda kansainvälistä kalenteria ilman monia **objekteja ja taulukoita, joissa oli tietoa jokaisesta maa- tai alueesta. Nykyään se on helppoa. Jos menemme sisään en-GB, menetelmä palauttaa:

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

Brunein kaltaisessa maassa (ms-BN), viikonloppu on perjantai ja sunnuntai:

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

Saatat ihmetellä mitä se minimalDays omaisuus on. Se on vähiten vaadittavia päiviä kuukauden ensimmäisellä viikolla, jotta se lasketaan kokonaiseksi viikoksi. Joillakin alueilla se voi olla vain yksi päivä. Toisille se voi olla täydet seitsemän päivää.

Seuraavaksi luomme a render menetelmä sisällämme kalEl-menetelmä:

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

Tarvitsemme vielä lisää dataa, ennen kuin hahmonnamme mitään:

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

Viimeinen on a Boolean joka tarkistaa onko today on olemassa kuukaudessa, jonka aiomme hahmontaa.

Semanttinen merkintä

Aiomme syventää renderöintiä hetkessä. Mutta ensin haluan varmistaa, että määrittämiimme tietoihin liittyy semanttisia HTML-tageja. Sen määrittäminen heti käyttöön antaa meille esteettömyysetuja alusta alkaen.

Kalenterin kääre

Ensinnäkin meillä on ei-semanttinen kääre: <kal-el>. Se on hyvä, koska siinä ei ole semanttia <calendar> tag tai jotain vastaavaa. Jos emme tekisi mukautettua elementtiä, <article> saattaa olla sopivin elementti, koska kalenteri voisi olla omalla sivullaan.

Kuukausien nimet

- <time> elementti tulee olemaan meille suuri, koska se auttaa kääntämään päivämäärät muotoon, jonka näytönlukijat ja hakukoneet voivat jäsentää tarkemmin ja johdonmukaisemmin. Esimerkiksi näin voimme ilmaista "tammikuu 2023" merkinnöissämme:

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

Päivän nimet

Kalenterin päivämäärien yläpuolella oleva rivi, joka sisältää viikonpäivien nimet, voi olla hankala. On ihanteellista, jos voimme kirjoittaa jokaisen päivän täydelliset nimet - esim. sunnuntai, maanantai, tiistai jne. - mutta se voi viedä paljon tilaa. Eli lyhennetään toistaiseksi nimet an:n sisällä <ol> jossa jokainen päivä on a <li>:

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

CSS:n kanssa voi olla hankalaa saada molempien maailmojen parhaat puolet. Jos esimerkiksi muokkasimme merkintää hieman näin:

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

…saamme oletuksena täydet nimet. Voimme sitten "piilottaa" koko nimen, kun tila loppuu ja näyttää title attribuutti sen sijaan:

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

Mutta emme ole menossa siihen suuntaan, koska Intl.DateTimeFormat API voi auttaa myös tässä. Käsittelemme sitä seuraavassa osiossa, kun käsittelemme renderöintiä.

Päivän numerot

Jokainen kalenteriruudukon päivämäärä saa numeron. Jokainen numero on luettelon kohde (<li>) järjestetyssä luettelossa (<ol>) ja rivi <time> tagi kääri todellisen luvun.

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

Ja vaikka en aio tehdä vielä mitään muotoilua, tiedän, että haluan jollain tavalla muotoilla päivämääränumeroita. Se on mahdollista sellaisenaan, mutta haluan myös pystyä muotoilemaan arkipäivien numerot eri tavalla kuin viikonloppunumerot tarvittaessa. Joten aion sisällyttää data-* attribuutteja nimenomaan sitä varten: data-weekend ja data-today.

Viikon numerot

Vuodessa on 52 viikkoa, joskus 53. Vaikka se ei ole kovin yleistä, voi olla mukavaa näyttää tietyn viikon numero kalenterissa lisäkontekstia varten. Pidän siitä nyt, vaikka en päädy käyttämättä sitä. Mutta käytämme sitä täysin tässä opetusohjelmassa.

Käytämme a data-weeknumber määritteen muotoilukoukuna ja sisällytä se jokaisen viikon ensimmäisen päivämäärän merkintöihin.

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

tulkinta

Laitetaan kalenteri sivulle! Tiedämme jo sen <kal-el> on mukautetun elementtimme nimi. Ensimmäinen asia, joka meidän on määritettävä, on asettaa firstDay omaisuutta, joten kalenteri tietää, onko sunnuntai vai jokin muu päivä viikon ensimmäinen päivä.

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

Käytämme malli literaalit merkinnän tekemiseen. Käytämme päivämäärien muotoilua kansainväliselle yleisölle Intl.DateTimeFormat API, jälleen käyttämällä locale määritimme aiemmin.

Kuukausi ja vuosi

Kun soitamme month, voimme määrittää, haluammeko käyttää long nimi (esim. helmikuu) tai short nimi (esim. helmikuu). Käytetään long nimi, koska se on otsikko kalenterin yläpuolella:

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

Viikonpäivän nimet

Päivämääräruudukon yläpuolella näkyville arkipäiville tarvitsemme molemmat long (esim. "sunnuntai") ja short (lyhennetty eli "aurinko") nimiä. Tällä tavalla voimme käyttää "lyhyttä" nimeä, kun kalenterissa on vähän tilaa:

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

Tehdään pieni apumenetelmä, joka helpottaa kunkin kutsumista:

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

Näin käytämme sitä mallissa:

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

Päivän numerot

Ja lopuksi päivät käärittynä <ol> elementti:

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

Puretaan se:

  1. Luomme "dummy" -taulukon "päivien lukumäärä" -muuttujan perusteella, jota käytämme iterointiin.
  2. Luomme day muuttuja kuluvalle päivälle iteraatiossa.
  3. Korjaamme eron Intl.Locale API ja getDay().
  4. Jos day on yhtä suuri kuin today, lisäämme a data-* määrite.
  5. Lopuksi palaamme <li> elementti merkkijonona yhdistetyillä tiedoilla.
  6. tabindex="0" tekee elementistä tarkennettavissa käytettäessä näppäimistönavigointia positiivisten tabindex-arvojen jälkeen (Huomaa: sinun pitäisi ei ikinä lisätä positiivinen tabindex-arvot)

jotta "tyhjennä" numerot vuonna datetime attribuutti, käytämme vähän apumenetelmää:

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

Viikon numero

Jälleen "viikon numero" on paikka, jossa viikko osuu 52 viikon kalenteriin. Käytämme siihen myös pientä apumenetelmää:

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

En kirjoittanut tätä getWeek-menetelmä. Se on puhdistettu versio tämä kirjoitus.

Ja siinä se! Kiitos Intl.Locale, Intl.DateTimeFormat ja Intl.NumberFormat API, voimme nyt yksinkertaisesti muuttaa lang- ominaisuus <html> elementti muuttaa kalenterin kontekstia nykyisen alueen perusteella:

Tammikuun 2023 kalenteriruudukko.
de-DE
Tammikuun 2023 kalenteriruudukko.
fa-IR
Tammikuun 2023 kalenteriruudukko.
zh-Hans-CN-u-nu-hanidec

Kalenterin muotoilu

Saatat muistaa, kuinka kaikki päivät ovat vain yksi <ol> luettelon kohteiden kanssa. Muodostaaksemme nämä luettavaksi kalenteriksi sukeltaamme CSS Gridin ihmeelliseen maailmaan. Itse asiassa voimme käyttää samaa ruudukkoa uudelleen aloituskalenterimalli täällä CSS-Tricksissä, mutta päivitetty hieman kanssa :is() relaatiopseudo koodin optimoimiseksi.

Huomaa, että määritän konfiguroitavia CSS-muuttujia matkan varrella (ja liitän ne etuliitteeseen ---kalel- konfliktien välttämiseksi).

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;
}
Seitsemänsarakkeinen kalenteriruudukko, jossa ruudukkoviivat näkyvät.

Piirretään reunukset päivämääränumeroiden ympärille, jotta ne voidaan erottaa visuaalisesti:

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

Seitsemänsarainen ruudukko toimii hyvin, kun on kuun ensimmäinen päivä Myös viikon ensimmäinen päivä valitulle maa-alueelle). Mutta se on pikemminkin poikkeus kuin sääntö. Useimmiten meidän on siirrettävä kuukauden ensimmäinen päivä eri arkipäivälle.

Näyttää kuukauden ensimmäisen päivän torstaina.

Muista kaikki ylimääräiset data-* attribuutit, jotka määritimme merkintöjämme kirjoittaessamme? Voimme kytkeytyä niihin päivittääksemme minkä ruudukon sarakkeen (--kalel-li-gc) kuukauden ensimmäinen päivämääränumero sijoitetaan:

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

Tässä tapauksessa siirrymme ensimmäisestä ruudukkosarakkeesta neljänteen ruudukkosarakkeeseen, joka automaattisesti "työntää" seuraavan kohteen (päivä 2) viidenteen ruudukon sarakkeeseen ja niin edelleen.

Lisätään "nykyiseen" päivämäärään hieman tyyliä, jotta se erottuu. Nämä ovat vain minun tyyliäni. Täällä voit tehdä täysin mitä haluat.

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

Pidän ajatuksesta muotoilla viikonlopun päivämääränumerot eri tavalla kuin arkipäiviä. Aion käyttää punertavaa väriä niiden muotoiluun. Huomaa, että voimme tavoittaa :not() pseudoluokka valitaksesi ne jättäen nykyisen päivämäärän yksin:

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

Älkäämme unohtako viikkonumeroita, jotka menevät ennen kunkin viikon ensimmäistä päivämääränumeroa. Käytimme a data-weeknumber attribuuttia sen merkinnöissä, mutta numerot eivät itse asiassa näy, ellemme paljasta niitä CSS:llä, minkä voimme tehdä ::before pseudoelementti:

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

Olemme teknisesti valmiita tässä vaiheessa! Voimme renderöidä kalenteriruudukon, joka näyttää kuluvan kuukauden päivämäärät sekä huomioita tietojen lokalisoimisesta kieliasetuksen mukaan ja varmistaa, että kalenteri käyttää oikeaa semantiikkaa. Ja kaikki mitä käytimme oli vanilja JavaScript ja CSS!

Mutta otetaan tämä vielä yksi askel...

Renderöidään kokonainen vuosi

Ehkä sinun täytyy näyttää koko vuosi päivämäärät! Joten sen sijaan, että hahmontaisit kuluvan kuukauden, saatat haluta näyttää kaikki kuluvan vuoden kuukausiruudukot.

No, hyvä puoli käyttämässämme lähestymistavassa on se, että voimme kutsua render menetelmää niin monta kertaa kuin haluamme ja muutamme vain kokonaisluvun, joka identifioi kuukauden kussakin tapauksessa. Kutsutaan sitä 12 kertaa kuluvan vuoden perusteella.

yhtä helppoa kuin soittaa render-method 12 kertaa, ja muuta vain kokonaisluku for month - i:

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

On luultavasti hyvä idea luoda uusi ylätason kääre renderoidulle vuodelle. Jokainen kalenteriruudukko on a <kal-el> elementti. Kutsutaan uusi vanhempi kääre <jor-el>, Jossa Jor-El on Kal-Elin isän nimi.

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

Voimme käyttää <jor-el> luodaksemme ruudukon ruudukoillemme. Siis 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);
}

Lopullinen demo

Bonus: Konfettikalenteri

Luin erinomaisen kirjan ns Verkon tekeminen ja rikkominen toissapäivänä ja törmäsin tähän kauniiseen "uudenvuoden julisteeseen":

Lähde: Verkon tekeminen ja rikkominen (2. painos) Kirjailija: Timothy Samara

Ajattelin, että voisimme tehdä jotain vastaavaa muuttamatta mitään HTML:ssä tai JavaScriptissä. Olen ottanut vapauden lisätä kuukausien koko nimet ja numerot päivien nimien sijaan, jotta se olisi helpompi lukea. Nauttia!

spot_img

Uusin älykkyys

spot_img