בניית רכיב אקורדיון באמצעות jQuery + CSS וללא תוספים

בניית רכיב אקורדיון באמצעות jQuery + CSS וללא תוספים

בפוסט הזה נדבר על יצירה של אקורדיון – רשימה של כפתורים שבלחיצה על כפתור – נפתח ונסגר התוכן שלו מתחתיו. מדובר ברכיב ממש פשוט מכל בחינה – HTML, CSS וjQuery, כך שלהטמיע בשבילו תוסף JS ייעודי זה בזבוז מושלם.

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

קוד הHTML הבסיסי

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

הכפתור – יש להשתמש בתגית a או button. אני מעדיפה את התגית a, כי קל יותר לעצב אותה בדרך כלל. במידה ואכן השתמשתם בתגית a שימו לב שחובה להוסיף href על מנת שהדפדפן יכיר שזה קישור ולא span. מאחר וזה לא באמת קישור אלא כפתור – צריך להגדיר href חסר משמעות, שזה או # או (עדיף) – javascript: void(0);

זה הHTML שלנו:

<div class="accordion_parent">
    <a class="accordion_button" href="javascript: void(0);">למה השמים כחולים?</a>
    <div class="accordion_content">בגלל שבירת קרני השמש</div>
    <a class="accordion_button" href="javascript: void(0);">מה היא החיה הגדולה ביותר?</a>
    <div class="accordion_content">ליוויתן כחול. אורכו מגיע ל30 מטרים!</div>
</div>

קוד הCSS הבסיסי

הדבר היחיד שהינו חובה – הוא להגדיר שלא יראו את איזור התוכן. נעשה את זה באמצעות הגדרת display: none.

.accordion_content{
    display: none;
}

קוד הJS הבסיסי

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

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

ולמה כן להשתמש באפקט הזה:

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

ההחלטה על אופן המימוש – כן או לא לסגור את האקורדיון הפתוח בעת שאני פותחת אקורדיון – נתון לשיקול דעתו של הלקוח, המעצב או איש הUI/UX של האתר. אני כמפתחת יכולה להמליץ אבל לא להחליט בשבילם.

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

jQuery('.accordion_button').on('click', function(e){
    e.preventDefault();
    jQuery(this).next().slideToggle();
});

מה שכתבנו כאן (לפי שורות הקוד):

בלחיצה על הכפתור
בטל את הפעולה הדיפולטיבית (צריך את השורה הזו במידה ובhref של הכפתורים הגדרתם #, על מנת לבטל את הפעולה הדיפולטיבית שהיא קפיצה לראש העמוד). אין צורך לכתוב שורה זו אם השתמשתם בתגית button עבור הכפתור, מאחר ואין לו פעולה דיפולטיבית.
לאלמנט שאחרי הכפתור שעליו לחצו (jQuery(this).next() – תבצע הצגה/הסתרה במאצעות אפקט של סלייד. עוד על slideToggle ואיך זה עובד תוכלו לקרוא במדריך שלנו לjQuery.

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

    jQuery(this).next().slideToggle().siblings('.accordion_content').slideUp();

הוספנו כאן שהאחים עם הקלאס accordion_content יסגרו (slideUp, ולא slideToggle).

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

jQuery(this).next().slideToggle().parent().siblings().find('.accordion_content').slideUp();

הגדרנו שמהכפתור (this) – תעלה לאבא שעוטף אותך (parent), תעבור לאחים שלו (siblings), ותאתר את הבנים (find), אלה עם הקלאס accordion_content. אם לא נגיד את שם הקלאס – ייסגרו גם הכפתורים, שזה משהו שאנחנו לא ממש רוצים…

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

קצת עיצוב מינימלי…

קודם כל – ברור שצריך להגדיר לכפתורים שכל אחד יעמוד בשורה נפרדת. נוסיף בCSS לקלאס של הכפתור את ההגדרה display: block;

בנוסף, בדרך כלל רוצים לתת איזשהו חיווי לכפתור עצמו אם הוא סגור או פתוח. לכן נוסיף לJS שלנו, בתוך הפונקציה (מיד אחרי e.preventDefault() את שורת הקוד הבאה:

jQuery(this).toggleClass('active').

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

רכיבי אקורדיון משני סוגים בcodepen.

ניתן לשחק עם before + after על מנת להציג לדוגמה סימן + כשהכפתור סגור, ואז כשהוא פתוח (.active) – ה+ מקבל סיבוב (transform: rotate()) והופך לX. וכן הלאה כיד הדמיון הטובה עליכם או על המעצב שאיתו אתם עובדים..

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

הנגשת האקורדיון

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

jQuery(this).next().focus();

שימו לב שכדי שהתוכן יוכל לקבל פוקוס – צריך להוסיף לו הגדרת tabindex="0" באלמנט הHTMLי. עוד על הנגשה לניווט מקלדת תוכלו לראות בפוסט שעוסק בהנגשת האתר לניווט מקלדת.

ההנגשה לקורא מסך מורכבת מעט יותר.

שלב הראשון – role

עלינו להגדיר שהכפתור הוא כפתור (ולא קישור), והתוכן הוא תוכן של אקורדיון. זה מתבצע באמצעות הגדרת role לכפתור ולתוכן.

לכפתור נגדיר role="button" – תקף רק במידה והשתמשתם באלמנט a ולא באלמנט button
לתוכן נגדיר role="region".

כעת קורא המסך יודע מה המשמעות של האלמנטים הללו.

שלב השני – הגדרת הקשר בין כל כפתור ותוכן עבור קורא המסך

נגדיר את הקשר בין כל כפתור לתוכן שלו. את זה נעשה באמצעות מתן ID ייעודי לכל כפתור ולכל איזור תוכן. זה לא חייב להיות משהו בעל משמעות גדולה, אלא מספיק שיהיה מונה (i), משהו בסגנון #accordion_btn_1, #accordion_btn_2 וכו', כשאת המספר משרשרים בלולאה שמציירת את הHTML, ומקדמים אותו בכל פעימה של הלולאה. כנ"ל לגבי איזור התוכן. לדוגמה accordion_content_1, accordion_content_2 וכן הלאה.

כעת כשלכל רכיב יש לנו ID – נוכל להגדיר מי שולט על מי ומי נשלט על ידי מי. הכל באמצעות הגדרת aria, שהיא עבור קורא מסך בלבד, ולא משפיעה כלל למי שגולש באופן רגיל.

אם נניח שהכפתור שלנו אמור לפתוח ולסגור את התוכן שנקרא accordion_content_1, הרי שנגדיר לאלמנט הכפתור (בHTML) באופן הבא:

aria-controls="accordion_content_1"

ולאיזור התוכן, במידה והוא נשלט על ידי כפתור בשם accordion_btn_1, הרי שנגדיר לו כך:

aria-labeledby="accordion_btn_1"

ההגדרה aria-controls אומרת לקורא המסך על איזה אלמנט בDOM שלנו שולט אותו כפתור, וההגדרה aria-labeledby אומרת לקורא המסך שהכותרת של הרכיב היא מה שמכיל הרכיב שהID שלו מצוין בתוך המרכאות.

שימו לב שאין להגדיר את התו # לפני הID בשתי המקרים.

שלב שלישי ואחרון – הגדרת מצב פתוח/סגור לאקורדיון

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

כאשר הרכיב פתוח – הכפתור יקבל aria-expanded עם true, והתוכן יקבל aria-hidden עם false. לאמור – האיזור פתוח (aria-expanded), והתוכן אינו נסתר (aria-hidden).
כאשר הרכיב סגור – הכפתור מקבל aria-expanded עם false (לאמור – אני במצב סגור כרגע), והתוכן יקבל כמובן aria-hidden עם true, כלומר – אני מוסתר כרגע.

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

אקורדיונים משני סוגים לאחר ההנגשה

סיימנו.

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

כתוב/כתבי תגובה