Att göra en snabb sökning här på CSS-Tricks visar hur många olika sätt det finns att närma sig kalendrar. Vissa visar hur CSS Grid kan skapa layouten effektivt. Några försök att ta med faktiska data i mixen. Några förlita sig på ett ramverk att hjälpa till med statlig förvaltning.
Det finns många överväganden när man bygger en kalenderkomponent - mycket mer än vad som tas upp i artiklarna jag länkade till. Om du tänker efter, kalendrar är fyllda med nyanser, från att hantera tidszoner och datumformat till lokalisering och till och med se till att datum flyter från en månad till nästa ... och det är innan vi ens kommer in på tillgänglighet och ytterligare layoutöverväganden beroende på var kalendern är visas och sånt.
Många utvecklare fruktar Date()
objektet och håll dig till äldre bibliotek som moment.js
. Men även om det finns många "gotchas" när det kommer till datum och formatering, har JavaScript många coola API:er och sånt som hjälper!
Jag vill inte återskapa hjulet här, men jag ska visa dig hur vi kan få en bra kalender med vanilj JavaScript. Vi ska undersöka tillgänglighet, med semantisk uppmärkning och skärmläsarvänlig <time>
-taggar — samt internationalisering och formatering, använda Intl.Locale
, Intl.DateTimeFormat
och Intl.NumberFormat
-API:er.
Med andra ord, vi skapar en kalender... bara utan de extra beroenden som du vanligtvis kan se använda i en självstudie som denna, och med några av de nyanser du kanske inte brukar se. Och i processen hoppas jag att du kommer att få en ny uppskattning för nyare saker som JavaScript kan göra samtidigt som du får en uppfattning om vad jag tänker på när jag sätter ihop något sådant här.
Först och främst, namngivning
Vad ska vi kalla vår kalenderkomponent? På mitt modersmål skulle det kallas "kalenderelement", så låt oss använda det och förkorta det till "Kal-El" - även känt som Stålmannens namn på planeten Krypton.
Låt oss skapa en funktion för att få saker att gå igång:
function kalEl(settings = {}) { ... }
Denna metod kommer att återge en enda månad. Senare kommer vi att kalla denna metod från [...Array(12).keys()]
att göra ett helt år.
Initial data och internationalisering
En av de vanligaste sakerna som en vanlig onlinekalender gör är att markera det aktuella datumet. Så låt oss skapa en referens för det:
const today = new Date();
Därefter skapar vi ett "konfigurationsobjekt" som vi slår samman med det valfria settings
objektet för den primära metoden:
const config = Object.assign( { locale: (document.documentElement.getAttribute('lang') || 'en-US'), today: { day: today.getDate(), month: today.getMonth(), year: today.getFullYear() } }, settings
);
Vi kontrollerar om rotelementet (<html>
) innehåller a lang
-attribut med locale info; annars går vi tillbaka till att använda en-US
. Detta är det första steget mot internationalisering av kalendern.
Vi måste också bestämma vilken månad som ska visas först när kalendern renderas. Det är därför vi utökade config
objekt med det primära date
. På detta sätt, om inget datum anges i settings
objekt kommer vi att använda today
referens istället:
const date = config.date ? new Date(config.date) : today;
Vi behöver lite mer information för att korrekt formatera kalendern baserat på språk. Till exempel kanske vi inte vet om den första veckodagen är söndag eller måndag, beroende på plats. Om vi har informationen, bra! Men om inte kommer vi att uppdatera den med hjälp av Intl.Locale
API. API:et har en weekInfo
objektet som returnerar a firstDay
fastighet som ger oss precis vad vi letar efter utan krångel. Vi kan också få vilka dagar i veckan som är tilldelade weekend
:
if (!config.info) config.info = new Intl.Locale(config.locale).weekInfo || { firstDay: 7, weekend: [6, 7] };
Återigen skapar vi fallbacks. Veckans "första dag" för en-US
är söndag, så den har som standard ett värde på 7
. Detta är lite förvirrande, eftersom getDay
metod i JavaScript returnerar dagarna som [0-6]
Där 0
är söndag... fråga mig inte varför. Helgerna är alltså lördag och söndag [6, 7]
.
Innan vi hade Intl.Locale
API och dess weekInfo
metod var det ganska svårt att skapa en internationell kalender utan många **objekt och arrayer med information om varje lokal eller region. Nuförtiden är det lättsamt. Om vi passerar in en-GB
, returnerar metoden:
// en-GB
{ firstDay: 1, weekend: [6, 7], minimalDays: 4
}
I ett land som Brunei (ms-BN
), helgen är fredag och söndag:
// ms-BN
{ firstDay: 7, weekend: [5, 7], minimalDays: 1
}
Du kanske undrar vad det är minimalDays
egendom är. Det är minsta antal dagar som krävs under den första veckan i en månad för att räknas som en hel vecka. I vissa regioner kan det bara vara en dag. För andra kan det vara hela sju dagar.
Därefter skapar vi en render
metod inom vår kalEl
-metod:
const render = (date, locale) => { ... }
Vi behöver fortfarande lite mer data att arbeta med innan vi renderar något:
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 sista är en Boolean
som kontrollerar om today
finns i månaden vi ska återge.
Semantisk uppmärkning
Vi kommer att gå djupare i renderingen på bara ett ögonblick. Men först vill jag se till att detaljerna vi ställer in har semantiska HTML-taggar kopplade till dem. Att ställa in det direkt från början ger oss tillgänglighetsfördelar från början.
Kalenderomslag
Först har vi det icke-semantiska omslaget: <kal-el>
. Det är bra eftersom det inte finns en semantik <calendar>
tagg eller något liknande. Om vi inte gjorde ett anpassat element, <article>
kan vara det mest lämpliga elementet eftersom kalendern kan stå på sin egen sida.
Månadsnamn
Smakämnen <time>
element kommer att bli stort för oss eftersom det hjälper till att översätta datum till ett format som skärmläsare och sökmotorer kan analysera mer exakt och konsekvent. Så här kan vi till exempel förmedla "januari 2023" i vår uppmärkning:
<time datetime="2023-01">January <i>2023</i></time>
Dagens namn
Raden ovanför kalenderns datum som innehåller namnen på veckodagarna kan vara knepig. Det är idealiskt om vi kan skriva ut de fullständiga namnen för varje dag — t.ex. söndag, måndag, tisdag, etc. — men det kan ta mycket utrymme. Så låt oss förkorta namnen för nu inuti en <ol>
där varje dag är en <li>
:
<ol> <li><abbr title="Sunday">Sun</abbr></li> <li><abbr title="Monday">Mon</abbr></li> <!-- etc. -->
</ol>
Vi kan bli knepiga med CSS för att få det bästa av två världar. Till exempel, om vi modifierade uppmärkningen lite så här:
<ol> <li> <abbr title="S">Sunday</abbr> </li>
</ol>
...vi får de fullständiga namnen som standard. Vi kan sedan "gömma" det fullständiga namnet när utrymmet tar slut och visa title
attribut istället:
@media all and (max-width: 800px) { li abbr::after { content: attr(title); }
}
Men vi går inte den vägen eftersom Intl.DateTimeFormat
API kan också hjälpa till här. Vi kommer till det i nästa avsnitt när vi tar upp rendering.
Dagsnummer
Varje datum i kalenderrutnätet får ett nummer. Varje nummer är ett listobjekt (<li>
) i en ordnad lista (<ol>
), och inline <time>
taggen omsluter det faktiska antalet.
<li> <time datetime="2023-01-01">1</time>
</li>
Och även om jag inte planerar att göra någon styling än, vet jag att jag kommer att vilja ha något sätt att styla datumsiffrorna på. Det är möjligt som det är, men jag vill också kunna utforma veckodagsnummer annorlunda än helgnummer om jag behöver. Så jag ska ta med data-*
attribut speciellt för det: data-weekend
och data-today
.
Veckonummer
Det finns 52 veckor på ett år, ibland 53. Även om det inte är supervanligt kan det vara trevligt att visa numret för en viss vecka i kalendern för ytterligare sammanhang. Jag gillar att ha den nu, även om jag inte slutar använda den. Men vi kommer att använda det helt i den här handledningen.
Vi använder en data-weeknumber
attribut som en stylingkrok och inkludera det i markeringen för varje datum som är veckans första dejt.
<li data-day="7" data-weeknumber="1" data-weekend=""> <time datetime="2023-01-08">8</time>
</li>
rendering
Låt oss få kalendern på en sida! Det vet vi redan <kal-el>
är namnet på vårt anpassade element. Det första vi behöver konfigurera är att ställa in firstDay
egendom på den, så att kalendern vet om söndag eller någon annan dag är den första dagen i veckan.
<kal-el data-firstday="${ config.info.firstDay }">
Vi ska använda mall bokstäver för att återge uppmärkningen. För att formatera datumen för en internationell publik använder vi Intl.DateTimeFormat
API, återigen med hjälp av locale
vi angav tidigare.
Månaden och året
När vi ringer till month
, kan vi ställa in om vi vill använda long
namn (t.ex. februari) eller short
namn (t.ex. feb.). Låt oss använda long
namn eftersom det är titeln ovanför kalendern:
<time datetime="${year}-${(pad(month))}"> ${new Intl.DateTimeFormat( locale, { month:'long'}).format(date)} <i>${year}</i>
</time>
Namn på veckodagar
För veckodagar som visas ovanför rutnätet med datum behöver vi båda long
(t.ex. "söndag") och short
(förkortade, dvs. ”Sol”) namn. På så sätt kan vi använda det "korta" namnet när det är ont om plats i kalendern:
Intl.DateTimeFormat([locale], { weekday: 'long' })
Intl.DateTimeFormat([locale], { weekday: 'short' })
Låt oss göra en liten hjälpmetod som gör det lite lättare att anropa var och en:
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;
}
Så här åberopar vi det i mallen:
<ol> ${weekdays(config.info.firstDay,locale).map(name => ` <li> <abbr title="${name.long}">${name.short}</abbr> </li>`).join('') }
</ol>
Dagsnummer
Och slutligen, dagarna, insvepta 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('')}
Låt oss bryta ner det:
- Vi skapar en "dummy"-array, baserad på variabeln "antal dagar", som vi använder för att iterera.
- Vi skapar en
day
variabel för den aktuella dagen i iterationen. - Vi fixar avvikelsen mellan
Intl.Locale
API ochgetDay()
. - Om
day
är lika medtoday
, lägger vi till endata-*
attribut. - Slutligen returnerar vi
<li>
element som en sträng med sammanslagna data. tabindex="0"
gör elementet fokuserbart, när du använder tangentbordsnavigering, efter eventuella positiva tabindex-värden (Obs: du bör aldrig lägga till positiv tabindex-värden)
Till "padda" siffrorna i datetime
attribut använder vi en liten hjälpmetod:
const pad = (val) => (val + 1).toString().padStart(2, '0');
Veckans nummer
Återigen, "veckans nummer" är där en vecka infaller i en 52-veckors kalender. Vi använder en liten hjälpmetod för det också:
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);
}
Jag skrev inte det här getWeek
-metod. Det är en rensad version av detta skript.
Och det är allt! Tack vare Intl.Locale
, Intl.DateTimeFormat
och Intl.NumberFormat
API:er kan vi nu helt enkelt ändra lang
-attribut av <html>
element för att ändra sammanhanget för kalendern baserat på den aktuella regionen:
Stylar kalendern
Du kanske minns hur alla dagar bara är en <ol>
med listobjekt. För att göra dessa till en läsbar kalender dyker vi in i den underbara världen av CSS Grid. Faktum är att vi kan återanvända samma rutnät från en startkalendermall här på CSS-Tricks, men uppdaterade lite med :is()
relationspseudo för att optimera koden.
Lägg märke till att jag definierar konfigurerbara CSS-variabler längs vägen (och prefixer dem med ---kalel-
för att undvika 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;
}
Låt oss rita gränser runt datumnumren för att separera dem visuellt:
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); }
Rutnätet med sju kolumner fungerar bra när den första dagen i månaden är också första dagen i veckan för den valda lokalen). Men det är undantaget snarare än regeln. Oftast måste vi flytta den första dagen i månaden till en annan veckodag.
Kom ihåg allt det extra data-*
attribut vi definierade när vi skrev vår uppmärkning? Vi kan koppla in dem för att uppdatera vilken rutkolumn (--kalel-li-gc
) månadens första datumnummer placeras på:
[data-firstday="1"] [data-day="3"]:first-child { --kalel-li-gc: 1 / 4;
}
I det här fallet spänner vi från den första rutkolumnen till den fjärde rutkolumnen - som automatiskt kommer att "skjuta" nästa objekt (dag 2) till den femte rutkolumnen, och så vidare.
Låt oss lägga till lite stil till det "aktuella" datumet, så att det sticker ut. Det här är bara mina stilar. Du kan göra precis vad du vill här.
[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;
}
Jag gillar tanken på att styla datumnummer för helger annorlunda än vardagar. Jag ska använda en rödaktig färg för att styla dem. Observera att vi kan nå för :not()
pseudoklass för att välja dem samtidigt som det aktuella datumet lämnas ifred:
[data-weekend]:not([data-today]) { --kalel-day-c: var(--kalel-weekend-c, hsl(0, 86%, 46%));
}
Åh, och låt oss inte glömma veckonumren som går före det första datumnumret för varje vecka. Vi använde en data-weeknumber
attribut i uppmärkningen för det, men siffrorna visas faktiskt inte om vi inte avslöjar dem med CSS, vilket vi kan göra på ::before
pseudoelement:
[data-weeknumber]::before { display: var(--kalel-weeknumber-d, inline-block); content: attr(data-weeknumber); position: absolute; inset-inline-start: 0; /* additional styles */
}
Vi är tekniskt klara vid det här laget! Vi kan rendera ett kalenderrutnät som visar datumen för den aktuella månaden, komplett med överväganden för att lokalisera data efter språk, och se till att kalendern använder korrekt semantik. Och allt vi använde var vanilj JavaScript och CSS!
Men låt oss ta det här ett steg till.
Återger ett helt år
Kanske behöver du visa ett helt år med datum! Så istället för att återge den aktuella månaden, kanske du vill visa alla månadsrutnät för det aktuella året.
Tja, det fina med tillvägagångssättet vi använder är att vi kan kalla för render
metod så många gånger vi vill och bara ändra heltal som identifierar månaden för varje instans. Låt oss kalla det 12 gånger baserat på innevarande år.
så enkelt som att ringa till render
-metod 12 gånger, och ändra bara heltal för month
- i
:
[...Array(12).keys()].map(i => render( new Date(date.getFullYear(), i, date.getDate()), config.locale, date.getMonth() )
).join('')
Det är förmodligen en bra idé att skapa ett nytt föräldraomslag för det återgivna året. Varje kalenderrutnät är en <kal-el>
element. Låt oss kalla det nya föräldraomslaget <jor-el>
Där Jor-El är namnet på Kal-Els far.
<jor-el id="app" data-year="true"> <kal-el data-firstday="7"> <!-- etc. --> </kal-el> <!-- other months -->
</jor-el>
Vi kan använda <jor-el>
att skapa ett rutnät för våra nät. 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);
}
Sista demo
Bonus: Konfettikalender
Jag läste en utmärkt bok som heter Skapa och bryta rutnätet häromdagen och snubblade på denna vackra "nyårsaffisch":
Jag tänkte att vi kunde göra något liknande utan att ändra något i HTML eller JavaScript. Jag har tagit mig friheten att inkludera fullständiga namn i månader, och siffror istället för dagnamn, för att göra det mer läsbart. Njut av!
- SEO-drivet innehåll och PR-distribution. Bli förstärkt idag.
- Platoblockchain. Web3 Metaverse Intelligence. Kunskap förstärkt. Tillgång här.
- Källa: https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/
- :är
- $UPP
- 1
- 11
- 2023
- 7
- 8
- 9
- 98
- a
- Able
- Om Oss
- om det
- ovan
- Absolut
- tillgänglighet
- exakt
- faktiskt
- Annat
- Efter
- Alla
- ensam
- redan
- och
- api
- API: er
- app
- appreciering
- tillvägagångssätt
- lämpligt
- ÄR
- runt
- array
- artiklar
- AS
- delad
- associerad
- At
- attribut
- publik
- automatiskt
- bakgrund
- baserat
- BE
- vackert
- därför att
- innan
- Fördelarna
- BÄST
- mellan
- Stor
- Bit
- boken
- Box
- Ha sönder
- Breaking
- Byggnad
- by
- Kalender
- Ring
- kallas
- anropande
- KAN
- Kan få
- Vid
- byta
- byte
- ta
- Kontroller
- koda
- färg
- Kolumn
- COM
- Gemensam
- fullborda
- komponent
- förvirrande
- överväganden
- innehåller
- innehåll
- sammanhang
- kyla
- kunde
- land
- täcka
- omfattas
- skapa
- Cross
- CSS
- Aktuella
- beställnings
- datum
- Datum
- Datum
- dag
- Dagar
- djupare
- Standard
- defaults
- definierade
- definierande
- beroende
- detaljer
- Bestämma
- utvecklare
- olika
- avvikelse
- Visa
- dokumentera
- inte
- ner
- dra
- e
- varje
- Tidigare
- lättare
- edition
- elementet
- Motorer
- säkerställa
- Hela
- etc
- Även
- exakt
- exempel
- utmärkt
- undantag
- finns
- extra
- Fallande
- Falls
- rädsla
- februari
- Februari
- figured
- Slutligen
- änden
- Förnamn
- Fast
- flöda
- För
- format
- Fjärde
- Fredag
- från
- full
- fungera
- Få
- spalt
- skaffa sig
- få
- ges
- ger
- Go
- kommer
- god
- Rutnät
- rutnät-mall-kolumner
- Arbetsmiljö
- Hård
- Har
- har
- hjälpa
- hjälper
- här.
- Markera
- hoppas
- Hur ser din drömresa ut
- html
- HTTPS
- i
- Tanken
- idealisk
- identifierar
- in
- innefattar
- info
- informationen
- inledande
- initialt
- exempel
- istället
- Internationell
- IT
- artikel
- iteration
- DESS
- Januari
- JavaScript
- bara en
- Vet
- känd
- LÅNG
- språk
- Efternamn
- Layout
- lämnar
- Li
- Liberty
- bibliotek
- tycka om
- rader
- kopplade
- Lista
- liten
- Lokalisering
- Lång
- se
- du letar
- Lot
- göra
- GÖR
- Framställning
- ledning
- många
- Marginal
- matte
- max-bredd
- endast
- Sammanfoga
- metod
- kanske
- emot
- modifierad
- ögonblick
- Måndag
- Månad
- månader
- mer
- mest
- Mozilla
- namn
- namn
- nativ
- Navigering
- Behöver
- Nya
- Nästa
- Nyans
- antal
- nummer
- objektet
- of
- on
- ONE
- nätet
- Optimera
- Övriga
- Övrigt
- annat
- egen
- vaddera
- sida
- planet
- planering
- plato
- Platon Data Intelligence
- PlatonData
- placera
- positiv
- möjlig
- pretty
- primär
- förmodligen
- process
- rätt
- ordentligt
- egenskapen
- förutsatt
- sätta
- Snabbt
- snarare
- nå
- Läsa
- rödaktig
- region
- regioner
- rendering
- Obligatorisk
- avkastning
- återgår
- avslöjar
- rot
- RAD
- Regel
- s
- Samma
- Sök
- Sökmotorer
- §
- vald
- semantik
- separat
- in
- inställning
- inställningar
- sju
- skifta
- Kort
- skall
- show
- visas
- Visar
- liknande
- Enkelt
- helt enkelt
- eftersom
- enda
- Small
- So
- fast
- några
- något
- Utrymme
- specifikt
- specificerade
- stå
- står
- starta
- Ange
- Steg
- Fortfarande
- stil
- super
- MÄRKA
- Ta
- mall
- Tack
- den där
- Smakämnen
- Dem
- Dessa
- sak
- saker
- tid
- gånger
- Titel
- till
- i dag
- tillsammans
- TOTALT
- mot
- Översätt
- sann
- Tisdag
- handledning
- typisk
- typiskt
- Uppdatering
- uppdaterad
- us
- användning
- värde
- Värden
- version
- W3
- Sätt..
- sätt
- vecka
- helgen
- veckor
- VÄL
- Vad
- Vad är
- Hjul
- om
- som
- medan
- kommer
- vind
- med
- inom
- utan
- underbart
- ord
- Arbete
- fungerar
- världen
- Världens
- skulle
- Wrapped
- skriva
- skrivning
- år
- Om er
- zephyrnet