Å gjøre et raskt søk her på CSS-Tricks viser hvor mange forskjellige måter det er å nærme seg kalendere på. Noen viser hvordan CSS Grid kan lage oppsettet effektivt. Noen forsøk på å bringe faktiske data inn i blandingen. Noen stole på et rammeverk å hjelpe til med statlig forvaltning.
Det er mange hensyn når du bygger en kalenderkomponent - langt flere enn det som er dekket i artiklene jeg koblet til. Hvis du tenker på det, er kalendere fulle av nyanser, fra håndtering av tidssoner og datoformater til lokalisering og til og med å sørge for at datoer flyter fra en måned til den neste ... og det er før vi i det hele tatt kommer inn på tilgjengelighets- og tilleggsbetraktninger avhengig av hvor kalenderen er. vises og sånt.
Mange utviklere frykter Date()
objekt og hold deg til eldre biblioteker som moment.js
. Men selv om det er mange "gotchas" når det kommer til datoer og formatering, har JavaScript mange kule APIer og ting å hjelpe!
Jeg ønsker ikke å gjenskape hjulet her, men jeg skal vise deg hvordan vi kan få en god kalender med vanilje JavaScript. Vi skal se nærmere på tilgjengelighet, ved hjelp av semantisk markup og skjermleservennlig <time>
-tags — samt internasjonalisering og formatering, bruker Intl.Locale
, Intl.DateTimeFormat
og Intl.NumberFormat
- API-er.
Med andre ord, vi lager en kalender ... bare uten de ekstra avhengighetene du vanligvis ser brukt i en opplæring som denne, og med noen av nyansene du kanskje ikke vanligvis ser. Og i prosessen håper jeg at du vil få en ny forståelse for nyere ting som JavaScript kan gjøre, samtidig som du får en ide om hva slags ting jeg tenker på når jeg setter sammen noe slikt.
Først og fremst navngivning
Hva skal vi kalle kalenderkomponenten vår? På morsmålet mitt vil det bli kalt "kalenderelement", så la oss bruke det og forkorte det til "Kal-El" - også kjent som Supermans navn på planeten Krypton.
La oss lage en funksjon for å få ting i gang:
function kalEl(settings = {}) { ... }
Denne metoden vil gjengi en enkelt måned. Senere vil vi kalle denne metoden fra [...Array(12).keys()]
å gjengi et helt år.
Innledende data og internasjonalisering
En av de vanlige tingene en typisk nettkalender gjør, er å fremheve gjeldende dato. Så la oss lage en referanse for det:
const today = new Date();
Deretter lager vi et "konfigurasjonsobjekt" som vi slår sammen med det valgfrie settings
objektet for den primære metoden:
const config = Object.assign( { locale: (document.documentElement.getAttribute('lang') || 'en-US'), today: { day: today.getDate(), month: today.getMonth(), year: today.getFullYear() } }, settings
);
Vi sjekker om rotelementet (<html>
) inneholder en lang
-attributt med lokale info; ellers faller vi tilbake til å bruke en-US
. Dette er det første skrittet mot internasjonalisering av kalenderen.
Vi må også bestemme hvilken måned som skal vises først når kalenderen gjengis. Det er derfor vi utvidet config
objekt med det primære date
. På denne måten, hvis ingen dato er oppgitt i settings
objekt, bruker vi today
referanse i stedet:
const date = config.date ? new Date(config.date) : today;
Vi trenger litt mer informasjon for å formatere kalenderen riktig basert på lokalitet. For eksempel vet vi kanskje ikke om den første dagen i uken er søndag eller mandag, avhengig av lokalitet. Hvis vi har informasjonen, flott! Men hvis ikke, oppdaterer vi den ved å bruke Intl.Locale
API. API-en har en weekInfo
objekt som returnerer a firstDay
eiendom som gir oss akkurat det vi leter etter uten problemer. Vi kan også se hvilke ukedager som er tilordnet weekend
:
if (!config.info) config.info = new Intl.Locale(config.locale).weekInfo || { firstDay: 7, weekend: [6, 7] };
Igjen skaper vi fallbacks. Ukens "første dag" for en-US
er søndag, så den har som standard verdien på 7
. Dette er litt forvirrende, som getDay
metode i JavaScript returnerer dagene som [0-6]
, Hvor 0
er søndag ... ikke spør meg hvorfor. Helgene er derfor lørdag og søndag [6, 7]
.
Før vi hadde Intl.Locale
API og dens weekInfo
metoden, var det ganske vanskelig å lage en internasjonal kalender uten mange **objekter og matriser med informasjon om hver lokalitet eller region. I dag er det lettvint. Hvis vi går inn en-GB
, returnerer metoden:
// en-GB
{ firstDay: 1, weekend: [6, 7], minimalDays: 4
}
I et land som Brunei (ms-BN
), helgen er fredag og søndag:
// ms-BN
{ firstDay: 7, weekend: [5, 7], minimalDays: 1
}
Du lurer kanskje på hva det minimalDays
eiendom er. Det er færrest dager som kreves i den første uken i en måned for å bli regnet som en hel uke. I noen regioner kan det bare være én dag. For andre kan det være hele syv dager.
Deretter lager vi en render
metode innenfor vår kalEl
-metode:
const render = (date, locale) => { ... }
Vi trenger fortsatt litt mer data å jobbe med før vi gjengir noe:
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);
Den siste er en Boolean
som sjekker om today
eksisterer i måneden vi er i ferd med å gjengi.
Semantisk markering
Vi kommer til å gå dypere i gjengivelsen på et øyeblikk. Men først vil jeg sørge for at detaljene vi setter opp har semantiske HTML-tagger knyttet til seg. Å sette det opp rett ut av boksen gir oss tilgjengelighetsfordeler fra starten.
Kalenderomslag
Først har vi den ikke-semantiske innpakningen: <kal-el>
. Det er greit fordi det ikke er en semantikk <calendar>
tag eller noe sånt. Hvis vi ikke laget et tilpasset element, <article>
kan være det mest passende elementet siden kalenderen kan stå på sin egen side.
Månedens navn
De <time>
element kommer til å bli et stort element for oss fordi det hjelper til med å oversette datoer til et format som skjermlesere og søkemotorer kan analysere mer nøyaktig og konsekvent. For eksempel, her er hvordan vi kan formidle "januar 2023" i markeringen vår:
<time datetime="2023-01">January <i>2023</i></time>
Dagens navn
Raden over kalenderens datoer som inneholder navnene på ukedagene kan være vanskelig. Det er ideelt hvis vi kan skrive ut de fulle navnene for hver dag – f.eks. søndag, mandag, tirsdag osv. – men det kan ta mye plass. Så, la oss forkorte navnene for nå inne i en <ol>
hvor hver dag er en <li>
:
<ol> <li><abbr title="Sunday">Sun</abbr></li> <li><abbr title="Monday">Mon</abbr></li> <!-- etc. -->
</ol>
Vi kan bli vanskelige med CSS for å få det beste fra begge verdener. For eksempel, hvis vi endret markeringen litt slik:
<ol> <li> <abbr title="S">Sunday</abbr> </li>
</ol>
...vi får de fulle navnene som standard. Vi kan deretter "skjule" hele navnet når plassen er tom og vise title
attributt i stedet:
@media all and (max-width: 800px) { li abbr::after { content: attr(title); }
}
Men vi går ikke den veien fordi Intl.DateTimeFormat
API kan hjelpe her også. Vi kommer til det i neste avsnitt når vi dekker gjengivelse.
Dagstall
Hver dato i kalendernettet får et tall. Hvert tall er et listeelement (<li>
) i en ordnet liste (<ol>
), og inline <time>
tag omslutter det faktiske antallet.
<li> <time datetime="2023-01-01">1</time>
</li>
Og selv om jeg ikke planlegger å gjøre noen styling ennå, vet jeg at jeg vil ha en måte å style datotallene på. Det er mulig som det er, men jeg vil også kunne style ukedagstall annerledes enn helgetall hvis jeg trenger det. Så jeg skal inkludere data-*
attributter spesielt for det: data-weekend
og data-today
.
Ukenummer
Det er 52 uker i et år, noen ganger 53. Selv om det ikke er supervanlig, kan det være greit å vise tallet for en gitt uke i kalenderen for ekstra kontekst. Jeg liker å ha den nå, selv om jeg ikke ender opp med å ikke bruke den. Men vi vil bruke det fullt ut i denne opplæringen.
Vi bruker en data-weeknumber
attributtet som en stylinghook og inkludere det i markeringen for hver dato som er ukens første date.
<li data-day="7" data-weeknumber="1" data-weekend=""> <time datetime="2023-01-08">8</time>
</li>
Rendering
La oss få kalenderen på en side! Det vet vi allerede <kal-el>
er navnet på vårt tilpassede element. Det første vi må konfigurere er å stille inn firstDay
eiendom på den, slik at kalenderen vet om søndag eller en annen dag er den første dagen i uken.
<kal-el data-firstday="${ config.info.firstDay }">
Vi bruker malbokstaver for å gjengi markeringen. For å formatere datoene for et internasjonalt publikum, bruker vi Intl.DateTimeFormat
API, igjen ved å bruke locale
vi spesifiserte tidligere.
Måneden og året
Når vi ringer til month
, kan vi angi om vi vil bruke long
navn (f.eks. februar) eller short
navn (f.eks. feb.). La oss bruke long
navn siden det er tittelen over kalenderen:
<time datetime="${year}-${(pad(month))}"> ${new Intl.DateTimeFormat( locale, { month:'long'}).format(date)} <i>${year}</i>
</time>
Ukedagers navn
For ukedager som vises over rutenettet med datoer, trenger vi både long
(f.eks. "søndag") og short
(forkortet, dvs. "Sol") navn. På denne måten kan vi bruke det "korte" navnet når det er lite plass i kalenderen:
Intl.DateTimeFormat([locale], { weekday: 'long' })
Intl.DateTimeFormat([locale], { weekday: 'short' })
La oss lage en liten hjelpemetode som gjør det litt lettere å ringe hver enkelt:
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;
}
Slik bruker vi det i malen:
<ol> ${weekdays(config.info.firstDay,locale).map(name => ` <li> <abbr title="${name.long}">${name.short}</abbr> </li>`).join('') }
</ol>
Dagstall
Og til slutt, dagene, pakket inn i en <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('')}
La oss bryte det ned:
- Vi lager en "dummy"-array, basert på "antall dager"-variabelen, som vi vil bruke til å iterere.
- Vi lager en
day
variabel for gjeldende dag i iterasjonen. - Vi fikser avviket mellom
Intl.Locale
API oggetDay()
. - Dersom
day
er liktoday
, legger vi til endata-*
attributt. - Til slutt returnerer vi
<li>
element som en streng med sammenslåtte data. tabindex="0"
gjør elementet fokuserbart når du bruker tastaturnavigasjon, etter eventuelle positive tabindex-verdier (Merk: du bør aldri legge til positiv tabindex-verdier)
Til "pad" tallene i datetime
attributt, bruker vi en liten hjelpemetode:
const pad = (val) => (val + 1).toString().padStart(2, '0');
Ukenummer
Igjen, "ukenummeret" er der en uke faller inn i en 52-ukers kalender. Vi bruker en liten hjelpemetode for det også:
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);
}
Jeg skrev ikke dette getWeek
-metode. Det er en ryddet versjon av dette skriptet.
Og det er det! Takk til Intl.Locale
, Intl.DateTimeFormat
og Intl.NumberFormat
APIer, kan vi nå ganske enkelt endre lang
-attributtet til <html>
element for å endre konteksten til kalenderen basert på gjeldende region:
Styling av kalenderen
Du husker kanskje hvordan alle dagene bare er én <ol>
med listeelementer. For å style disse til en lesbar kalender, dykker vi inn i den fantastiske verdenen til CSS Grid. Faktisk kan vi gjenbruke samme rutenett fra en startkalendermal her på CSS-Tricks, men oppdaterte litt med :is()
relasjonspseudo for å optimalisere koden.
Legg merke til at jeg definerer konfigurerbare CSS-variabler underveis (og prefikser dem med ---kalel-
for å unngå konflikter).
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;
}
La oss tegne grenser rundt datotallene for å hjelpe å skille dem visuelt:
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); }
Rutenettet med syv kolonner fungerer fint når den første dagen i måneden er også den første dagen i uken for den valgte lokaliteten). Men det er unntaket snarere enn regelen. De fleste ganger må vi flytte den første dagen i måneden til en annen ukedag.
Husk alt det ekstra data-*
attributter vi definerte da vi skrev markeringen vår? Vi kan koble til dem for å oppdatere hvilken rutenettkolonne (--kalel-li-gc
) månedens første datonummer er plassert på:
[data-firstday="1"] [data-day="3"]:first-child { --kalel-li-gc: 1 / 4;
}
I dette tilfellet spenner vi fra den første rutenettkolonnen til den fjerde rutenettkolonnen - som automatisk vil "skyve" neste element (dag 2) til den femte rutenettkolonnen, og så videre.
La oss legge til litt stil til den "gjeldende" datoen, så den skiller seg ut. Dette er bare mine stiler. Du kan gjøre det du vil her.
[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;
}
Jeg liker ideen om å style datotallene for helger annerledes enn hverdager. Jeg skal bruke en rødlig farge for å style dem. Merk at vi kan strekke oss etter :not()
pseudo-klasse for å velge dem mens du lar gjeldende dato være:
[data-weekend]:not([data-today]) { --kalel-day-c: var(--kalel-weekend-c, hsl(0, 86%, 46%));
}
Å, og la oss ikke glemme ukenumrene som går før det første datonummeret for hver uke. Vi brukte a data-weeknumber
attributt i markeringen for det, men tallene vises faktisk ikke med mindre vi avslører dem med CSS, noe vi kan gjøre på ::before
pseudo-element:
[data-weeknumber]::before { display: var(--kalel-weeknumber-d, inline-block); content: attr(data-weeknumber); position: absolute; inset-inline-start: 0; /* additional styles */
}
Vi er teknisk ferdige på dette tidspunktet! Vi kan gjengi et kalenderrutenett som viser datoene for gjeldende måned, komplett med hensyn til lokalisering av data etter lokalitet, og for å sikre at kalenderen bruker riktig semantikk. Og alt vi brukte var vanilje JavaScript og CSS!
Men la oss ta dette Ett steg til...
Gjengir et helt år
Kanskje du må vise et helt år med datoer! Så i stedet for å gjengi gjeldende måned, vil du kanskje vise alle månedsrutene for gjeldende år.
Vel, det fine med tilnærmingen vi bruker er at vi kan kalle render
metode så mange ganger vi vil, og bare endre heltallet som identifiserer måneden for hver forekomst. La oss kalle det 12 ganger basert på inneværende år.
så enkelt som å ringe til render
-metode 12 ganger, og bare endre heltall for month
- i
:
[...Array(12).keys()].map(i => render( new Date(date.getFullYear(), i, date.getDate()), config.locale, date.getMonth() )
).join('')
Det er sannsynligvis en god idé å lage en ny forelderomslag for det gjengitte året. Hvert kalendernett er en <kal-el>
element. La oss kalle den nye foreldreinnpakningen <jor-el>
, Hvor Jor-El er navnet på faren til Kal-El.
<jor-el id="app" data-year="true"> <kal-el data-firstday="7"> <!-- etc. --> </kal-el> <!-- other months -->
</jor-el>
Vi kan bruke <jor-el>
å lage et rutenett for våre nett. Så 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);
}
Endelig demo
Bonus: Konfetti-kalender
Jeg leste en utmerket bok som heter Lage og bryte rutenettet her om dagen og snublet over denne vakre «nyttårsplakaten»:
Jeg tenkte at vi kunne gjøre noe lignende uten å endre noe i HTML eller JavaScript. Jeg har tatt meg friheten til å inkludere fulle navn i måneder, og tall i stedet for dagnavn, for å gjøre det mer lesbart. Nyt!
- SEO-drevet innhold og PR-distribusjon. Bli forsterket i dag.
- Platoblokkkjede. Web3 Metaverse Intelligence. Kunnskap forsterket. Tilgang her.
- kilde: https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/
- :er
- $OPP
- 1
- 11
- 2023
- 7
- 8
- 9
- 98
- a
- I stand
- Om oss
- om det
- ovenfor
- Absolute
- tilgjengelighet
- nøyaktig
- faktisk
- Ytterligere
- Etter
- Alle
- alene
- allerede
- og
- api
- APIer
- app
- takknemlighet
- tilnærming
- hensiktsmessig
- ER
- rundt
- Array
- artikler
- AS
- tildelt
- assosiert
- At
- attributter
- publikum
- automatisk
- bakgrunn
- basert
- BE
- vakker
- fordi
- før du
- Fordeler
- BEST
- mellom
- Stor
- Bit
- bok
- Eske
- Break
- Breaking
- Bygning
- by
- Kalender
- ring
- som heter
- ringer
- CAN
- Kan få
- saken
- endring
- endring
- sjekk
- Sjekker
- kode
- farge
- Kolonne
- COM
- Felles
- fullføre
- komponent
- forvirrende
- betraktninger
- inneholder
- innhold
- kontekst
- Kul
- kunne
- land
- dekke
- dekket
- skape
- Kryss
- CSS
- Gjeldende
- skikk
- dato
- Dato
- datoer
- dag
- Dager
- dypere
- Misligholde
- mislighold
- definert
- definere
- avhengig
- detaljer
- Bestem
- utviklere
- forskjellig
- avvik
- Vise
- dokument
- ikke
- ned
- tegne
- e
- hver enkelt
- Tidligere
- enklere
- utgave
- element
- Motorer
- sikrer
- Hele
- etc
- Selv
- nøyaktig
- eksempel
- utmerket
- unntak
- finnes
- ekstra
- Falling
- Falls
- frykt
- Februar
- Februar
- tenkte
- Endelig
- slutt
- Først
- Fix
- flyten
- Til
- format
- Fjerde
- Fredag
- fra
- fullt
- funksjon
- Gevinst
- mellomrom
- få
- få
- gitt
- gir
- Go
- skal
- god
- Grid
- rutenett-mal-kolonner
- Håndtering
- Hard
- Ha
- å ha
- hjelpe
- hjelper
- her.
- Uthev
- håp
- Hvordan
- HTML
- HTTPS
- i
- Tanken
- ideell
- identifiserer
- in
- inkludere
- info
- informasjon
- innledende
- i utgangspunktet
- f.eks
- i stedet
- internasjonalt
- IT
- varer
- køyring
- DET ER
- Januar
- Javascript
- bare én
- Vet
- kjent
- SPRÅK
- Språk
- Siste
- Layout
- forlater
- Li
- Liberty
- bibliotekene
- i likhet med
- linjer
- knyttet
- Liste
- lite
- Lokalisering
- Lang
- Se
- ser
- Lot
- gjøre
- GJØR AT
- Making
- ledelse
- mange
- Margin
- math
- max bredde
- bare
- Flett
- metode
- kunne
- tankene
- modifisert
- øyeblikk
- mandag
- Måned
- måneder
- mer
- mest
- Mozilla
- navn
- navn
- innfødt
- Navigasjon
- Trenger
- Ny
- neste
- Nuance
- Antall
- tall
- objekt
- of
- on
- ONE
- på nett
- Optimalisere
- Annen
- andre
- ellers
- egen
- pad
- side
- planet
- planlegging
- plato
- Platon Data Intelligence
- PlatonData
- posisjon
- positiv
- mulig
- pen
- primære
- sannsynligvis
- prosess
- ordentlig
- riktig
- eiendom
- forutsatt
- Sette
- Rask
- heller
- å nå
- Lese
- rødaktig
- region
- regioner
- gjengivelse
- påkrevd
- retur
- avkastning
- avsløre
- root
- RAD
- Regel
- s
- samme
- Søk
- Søkemotorer
- Seksjon
- valgt
- semantikk
- separat
- sett
- innstilling
- innstillinger
- syv
- skift
- Kort
- bør
- Vis
- vist
- Viser
- lignende
- Enkelt
- ganske enkelt
- siden
- enkelt
- liten
- So
- solid
- noen
- noe
- Rom
- spesielt
- spesifisert
- stå
- står
- Begynn
- Tilstand
- Trinn
- Still
- stil
- Super
- TAG
- Ta
- mal
- Takk
- Det
- De
- Dem
- Disse
- ting
- ting
- tid
- ganger
- Tittel
- til
- i dag
- sammen
- HELT KLART
- mot
- oversette
- sant
- tirsdag
- tutorial
- typisk
- typisk
- Oppdater
- oppdatert
- us
- bruke
- verdi
- Verdier
- versjon
- W3
- Vei..
- måter
- uke
- helg
- uker
- VI VIL
- Hva
- Hva er
- Hjul
- om
- hvilken
- mens
- vil
- vind
- med
- innenfor
- uten
- herlig
- ord
- Arbeid
- virker
- verden
- Verdens
- ville
- Innpakket
- skrive
- skriving
- år
- Du
- zephyrnet