יצירת לוחות שנה מתוך מחשבה על נגישות ובינלאומיות

יצירת לוחות שנה מתוך מחשבה על נגישות ובינלאומיות

חיפוש מהיר כאן ב-CSS-Tricks מראה כמה דרכים שונות יש לגשת ללוחות שנה. חלקם מראים איך CSS Grid יכול ליצור את הפריסה ביעילות. איזה ניסיון להביא נתונים בפועל לתוך התמהיל. כמה להסתמך על מסגרת לעזור בניהול המדינה.

ישנם שיקולים רבים בעת בניית רכיב לוח שנה - הרבה יותר ממה שמכוסה במאמרים שקישרתי. אם חושבים על זה, לוחות שנה מלאים בניואנסים, החל מטיפול באזורי זמן ופורמטים של תאריכים ועד לוקליזציה ואפילו לוודא שהתאריכים זורמים מחודש לחודש... וזה עוד לפני שנכנס לשיקולי נגישות ופריסה נוספים, תלוי איפה היומן מוצג ומה לא.

מפתחים רבים חוששים מה Date() אובייקט ולהישאר עם ספריות ישנות כמו moment.js. אבל בעוד שיש הרבה "תקלות" בכל הנוגע לתאריכים ולעיצוב, ל-JavaScript יש הרבה ממשקי API ודברים מגניבים שיעזרו!

רשת לוח השנה של ינואר 2023.
יצירת לוחות שנה מתוך מחשבה על נגישות ובינלאומיות

אני לא רוצה ליצור מחדש את הגלגל כאן, אבל אני אראה לך איך אנחנו יכולים להשיג לוח שנה טוב עם וניל JavaScript. אנחנו נבדוק נגישות, באמצעות סימון סמנטי וידידותי לקורא מסך <time> -תגים - כמו גם בינלאומי ו עיצוב, משתמש ב Intl.Locale, Intl.DateTimeFormat ו Intl.NumberFormat-ממשקי API.

במילים אחרות, אנחנו יוצרים לוח שנה... רק בלי התלות הנוספת שאתה עשוי לראות בדרך כלל בשימוש במדריך כזה, ועם כמה מהניואנסים שבדרך כלל לא תראה. ובתוך כך, אני מקווה שתקבלו הערכה חדשה לדברים חדשים יותר ש-JavaScript יכולה לעשות תוך כדי קבלת מושג על מיני דברים שעוברים לי בראש כשאני מחבר משהו כזה.

ראשית, מתן שמות

איך אנחנו צריכים לקרוא לרכיב לוח השנה שלנו? בשפת האם שלי, זה ייקרא "אלמנט קלנדר", אז בואו נשתמש בזה ונקצר את זה ל"קל-אל" - הידוע גם בשם שמו של סופרמן על כוכב קריפטון.

בואו ניצור פונקציה כדי להפעיל דברים:

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

שיטה זו תעבד חודש בודד. מאוחר יותר נקרא לשיטה זו מ [...Array(12).keys()] לעבד שנה שלמה.

נתונים ראשוניים ובינאום

אחד הדברים הנפוצים שיומן מקוון טיפוסי עושה הוא להדגיש את התאריך הנוכחי. אז בואו ניצור התייחסות לכך:

const today = new Date();

לאחר מכן, ניצור "אובייקט תצורה" שנמזג עם האופציונלי settings אובייקט השיטה העיקרית:

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

אנו בודקים, אם אלמנט השורש (<html>) מכיל א lang-תכונה עם אזור מידע; אחרת, נחזור לשימוש en-US. זה הצעד הראשון לקראת ביצוע בינלאומי של לוח השנה.

אנחנו גם צריכים לקבוע איזה חודש להציג תחילה כאשר לוח השנה יוצג. זו הסיבה שהארכנו את config אובייקט עם הראשוני date. בדרך זו, אם לא צוין תאריך ב- settings אובייקט, נשתמש ב- today התייחסות במקום:

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

אנחנו צריכים קצת יותר מידע כדי לעצב נכון את היומן על סמך המקום. לדוגמה, ייתכן שלא נדע אם היום הראשון בשבוע הוא יום ראשון או שני, בהתאם למקום. אם יש לנו את המידע, מעולה! אבל אם לא, נעדכן אותו באמצעות ה Intl.Locale API. ל-API יש א weekInfo אובייקט שמחזירה א firstDay נכס שנותן לנו בדיוק את מה שאנחנו מחפשים בלי שום טרחה. אנחנו יכולים גם לקבל אילו ימים בשבוע מוקצים ל- weekend:

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

שוב, אנו יוצרים נפילות. "היום הראשון" בשבוע עבור en-US הוא יום ראשון, כך שהוא כברירת מחדל לערך של 7. זה קצת מבלבל, כמו ה getDay שיטה ב-JavaScript מחזיר את הימים כ [0-6], שם 0 זה יום ראשון... אל תשאל אותי למה. סופי השבוע הם שבת וראשון, ומכאן [6, 7].

לפני שהיה לנו את Intl.Locale API ושלו weekInfo בשיטה, היה די קשה ליצור לוח שנה בינלאומי בלי הרבה **אובייקטים ומערכים עם מידע על כל אזור או אזור. כיום, זה קל-פיזי. אם נעבור פנימה en-GB, השיטה מחזירה:

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

במדינה כמו ברוניי (ms-BN), סוף השבוע הוא שישי וראשון:

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

אתה יכול לתהות מה זה minimalDays רכוש הוא. זה ה מספר הימים המועט ביותר שנדרש בשבוע הראשון של חודש כדי להיחשב כשבוע שלם. באזורים מסוימים, זה עשוי להיות רק יום אחד. עבור אחרים, זה עשוי להיות שבעה ימים שלמים.

לאחר מכן, ניצור א render שיטה בתוך שלנו kalEl-שיטה:

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

אנחנו עדיין צריכים עוד כמה נתונים לעבוד איתם לפני שנעבד משהו:

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

האחרון הוא א Boolean שבודק אם today קיים בחודש שאנו עומדים לעבד.

סימון סמנטי

אנחנו הולכים להעמיק בעיבוד בעוד רגע. אבל ראשית, אני רוצה לוודא שלפרטים שהגדרנו יש תגי HTML סמנטיים המשויכים אליהם. הגדרה של זה ישירות מהקופסה מעניקה לנו יתרונות נגישות מההתחלה.

עטיפת לוח שנה

ראשית, יש לנו את העטיפה הלא סמנטית: <kal-el>. זה בסדר כי אין סמנטיקה <calendar> תג או משהו כזה. אם לא היינו יוצרים אלמנט מותאם אישית, <article> עשוי להיות הרכיב המתאים ביותר שכן לוח השנה יכול לעמוד על עמוד משלו.

שמות החודשים

השמיים <time> אלמנט הולך להיות אחד גדול עבורנו מכיוון שהוא עוזר לתרגם תאריכים לפורמט שקוראי מסך ומנועי חיפוש יכולים לנתח בצורה מדויקת ועקבית יותר. לדוגמה, כך נוכל להעביר את "ינואר 2023" בסימון שלנו:

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

שמות ימים

השורה מעל תאריכי היומן המכילה את שמות ימות השבוע יכולה להיות מסובכת. זה אידיאלי אם נוכל לכתוב את השמות המלאים עבור כל יום - למשל יום ראשון, שני, שלישי וכו' - אבל זה יכול לתפוס הרבה מקום. אז בואו נקצר את השמות לעת עתה בתוך an <ol> שבו כל יום הוא א <li>:

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

אנחנו יכולים להסתבך עם CSS כדי לקבל את הטוב משני העולמות. לדוגמה, אם שינינו את הסימון קצת כך:

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

... אנו מקבלים את השמות המלאים כברירת מחדל. לאחר מכן נוכל "להסתיר" את השם המלא כשנגמר המקום ולהציג את title תכונה במקום:

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

אבל, אנחנו לא הולכים לכיוון הזה כי Intl.DateTimeFormat API יכול לעזור גם כאן. נגיע לזה בסעיף הבא כשנסקור את הרינדור.

מספרי ימים

כל תאריך ברשת לוח השנה מקבל מספר. כל מספר הוא פריט רשימה (<li>) ברשימה מסודרת (<ol>), והמוטבע <time> תג עוטף את המספר האמיתי.

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

ולמרות שאני לא מתכננת לעשות שום סטיילינג כרגע, אני יודעת שארצה דרך כלשהי לעצב את מספרי התאריכים. זה אפשרי כפי שהוא, אבל אני גם רוצה להיות מסוגל לעצב את מספרי ימי השבוע בצורה שונה ממספרי סוף השבוע אם אצטרך. אז, אני הולך לכלול data-* תכונות במיוחד בשביל זה: data-weekend ו data-today.

מספרי שבוע

יש 52 שבועות בשנה, לפעמים 53. למרות שזה לא סופר נפוץ, זה יכול להיות נחמד להציג את המספר עבור שבוע נתון בלוח השנה להקשר נוסף. אני אוהב את זה עכשיו, גם אם אני לא בסופו של דבר לא להשתמש בו. אבל נשתמש בזה לחלוטין במדריך זה.

נשתמש ב- a data-weeknumber תכונה כוו סטיילינג וכללו אותו בסימון עבור כל תאריך שהוא הדייט הראשון של השבוע.

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

טיוח

בואו להעלות את לוח השנה על דף! אנחנו כבר יודעים את זה <kal-el> הוא השם של האלמנט המותאם אישית שלנו. הדבר הראשון שאנחנו צריכים להגדיר אותו הוא להגדיר את firstDay נכס על זה, כך שהלוח יודע אם יום ראשון או יום אחר הוא היום הראשון בשבוע.

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

אנו נשתמש מילולי תבנית כדי להציג את הסימון. כדי לעצב את התאריכים עבור קהל בינלאומי, נשתמש ב- Intl.DateTimeFormat ממשק API, שוב משתמש ב- locale פירטנו קודם.

החודש והשנה

כאשר אנו מתקשרים ל month, נוכל להגדיר אם ברצוננו להשתמש ב- long שם (למשל פברואר) או ה short שם (למשל פברואר). בואו נשתמש ב long שם מכיוון שזו הכותרת מעל היומן:

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

שמות ימי חול

עבור ימי חול המוצגים מעל לרשת התאריכים, אנו זקוקים לשניהם long (למשל "יום ראשון") ו short (בקיצור, כלומר "שמש") שמות. בדרך זו, נוכל להשתמש בשם "קצר" כאשר בלוח השנה חסר מקום:

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

בואו נעשה שיטת עוזר קטנה שמקלה מעט לקרוא לכל אחד:

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

כך אנו מפעילים זאת בתבנית:

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

מספרי ימים

ולבסוף, הימים, עטופים ב <ol> אֵלֵמֶנט:

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

בוא נפרק את זה:

  1. אנו יוצרים מערך "דמה", המבוסס על משתנה "מספר הימים", שבו נשתמש כדי לחזור על הפעולות.
  2. אנו יוצרים a day משתנה עבור היום הנוכחי באיטרציה.
  3. אנו מתקנים את הפער בין ה Intl.Locale API ו- getDay().
  4. אם day שווה ל today, נוסיף א data-* תכונה.
  5. לבסוף, אנו מחזירים את <li> רכיב כמחרוזת עם נתונים ממוזגים.
  6. tabindex="0" הופך את האלמנט למיקוד, בעת שימוש בניווט במקלדת, לאחר ערכי tabindex חיוביים (הערה: כדאי לעולם לא להוסיף חיובי tabindex-values)

ל "רפד" את המספרים ב datetime תכונה, אנו משתמשים בשיטת עוזר קטנה:

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

מספר שבוע

שוב, "מספר השבוע" הוא המקום שבו שבוע נופל בלוח של 52 שבועות. אנחנו משתמשים בשיטת עוזר קטנה גם בשביל זה:

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

אני לא כתבתי את זה getWeek-שיטה. זו גרסה נקייה של התסריט הזה.

וזה הכל! הודות ל Intl.Locale, Intl.DateTimeFormat ו Intl.NumberFormat ממשקי API, כעת אנו יכולים פשוט לשנות את lang-תכונה של <html> רכיב לשינוי ההקשר של לוח השנה בהתבסס על האזור הנוכחי:

רשת לוח השנה של ינואר 2023.
de-DE
רשת לוח השנה של ינואר 2023.
fa-IR
רשת לוח השנה של ינואר 2023.
zh-Hans-CN-u-nu-hanidec

עיצוב לוח השנה

אתה אולי זוכר איך כל הימים הם רק אחד <ol> עם פריטי רשימה. כדי לעצב אותם ללוח שנה קריא, אנו צוללים לתוך העולם המופלא של CSS Grid. למעשה, אנו יכולים ליישם מחדש את אותה רשת תבנית יומן מתחיל ממש כאן ב-CSS-Tricks, אבל עודכן קצת עם ה :is() פסאודו יחסי כדי לייעל את הקוד.

שים לב שאני מגדיר משתני CSS הניתנים להגדרה לאורך הדרך (והקדימה אותם עם ---kalel- כדי למנוע קונפליקטים).

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;
}
רשת לוח שנה בת שבע עמודות עם קווי רשת מוצגים.
יצירת לוחות שנה מתוך מחשבה על נגישות ובינלאומיות

בואו נצייר גבולות סביב מספרי התאריכים כדי לעזור להפריד ביניהם ויזואלית:

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

הרשת בת שבע העמודות עובדת מצוין כשהיום הראשון של החודש הוא גם היום הראשון בשבוע עבור המקום שנבחר). אבל זה היוצא מן הכלל ולא הכלל. ברוב הפעמים, נצטרך להעביר את היום הראשון של החודש ליום חול אחר.

מציג את היום הראשון של החודש שחל ביום חמישי.
יצירת לוחות שנה מתוך מחשבה על נגישות ובינלאומיות

זכור את כל התוספת data-* תכונות שהגדרנו בעת כתיבת הסימון שלנו? נוכל להתחבר לאלו כדי לעדכן איזו עמודת רשת (--kalel-li-gc) מספר התאריך הראשון של החודש ממוקם ב:

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

במקרה זה, אנו מתפרשים מעמודת הרשת הראשונה לעמודת הרשת הרביעית - אשר "ידחוף" באופן אוטומטי את הפריט הבא (יום 2) לעמוד הרשת החמישית, וכן הלאה.

בואו נוסיף קצת סגנון לתאריך ה"נוכחי", כך שהוא בולט. אלו רק הסגנונות שלי. אתה לגמרי יכול לעשות מה שאתה רוצה כאן.

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

אני אוהב את הרעיון לעצב את מספרי התאריכים לסופי שבוע בצורה שונה מאשר בימי חול. אני הולך להשתמש בצבע אדמדם כדי לעצב אותם. שימו לב שאנחנו יכולים להגיע ל :not() פסאודו מחלקה כדי לבחור אותם תוך השארת התאריך הנוכחי לבד:

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

אה, ובואו לא נשכח את מספרי השבוע שלפני מספר הדייט הראשון של כל שבוע. השתמשנו ב- a data-weeknumber תכונה בסימון לכך, אבל המספרים לא יוצגו בפועל אלא אם כן נחשוף אותם באמצעות CSS, מה שנוכל לעשות ב- ::before אלמנט פסאודו:

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

סיימנו טכנית בשלב זה! אנו יכולים להציג רשת לוח שנה המציגה את התאריכים של החודש הנוכחי, עם שיקולים לגבי לוקליזציה של הנתונים לפי מיקום, ולהבטיח שהלוח משתמש בסמנטיקה נכונה. וכל מה שהשתמשנו היה וניל JavaScript ו-CSS!

אבל בואו ניקח את זה עוד צעד אחד...

עיבוד שנה שלמה

אולי אתה צריך להציג שנה שלמה של תאריכים! לכן, במקום להציג את החודש הנוכחי, ייתכן שתרצה להציג את כל רשתות החודש של השנה הנוכחית.

ובכן, הדבר היפה בגישה בה אנו משתמשים הוא שאנו יכולים לקרוא ל- render שיטה כמה פעמים שנרצה ופשוט שנה את המספר השלם שמזהה את החודש בכל מופע. בואו נקרא לזה 12 פעמים על סמך השנה הנוכחית.

פשוט כמו להתקשר ל render-שיטה 12 פעמים, ופשוט שנה את המספר השלם עבור month - i:

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

זה כנראה רעיון טוב ליצור עטיפת הורה חדשה לשנת העיבוד. כל רשת לוח שנה היא א <kal-el> אֵלֵמֶנט. בואו נקרא לעטיפת ההורים החדשה <jor-el>, שם ג'ור-אל הוא שמו של אביו של קל-אל.

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

אנו יכולים להשתמש <jor-el> ליצור רשת עבור הרשתות שלנו. אז מטא!

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

הדגמה סופית

בונוס: לוח שנה קונפטי

קראתי ספר מצוין בשם יצירה ושבירת הרשת לפני כמה ימים ונתקל ב"כרזת השנה החדשה" היפה הזו:

יצירת לוחות שנה עם נגישות ובינלאומיות במוח PlatoBlockchain Data Intelligence. חיפוש אנכי. איי.
מקור: ליצור ולשבור את הרשת (מהדורה שנייה) מאת טימותי סמארה

הבנתי שנוכל לעשות משהו דומה מבלי לשנות שום דבר ב-HTML או ב-JavaScript. לקחתי את החופש לכלול שמות מלאים במשך חודשים, ומספרים במקום שמות ימים, כדי שיהיה יותר קריא. תהנה!

בול זמן:

עוד מ טריקים של CSS