יצירת רכיב של גלילת מדיה

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

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

הדגמה

אם ברצונך ליצור סרטון, הנה גרסת YouTube של הפוסט הזה:

סקירה כללית

אנחנו ניצור פריסת גלילה אופקית שנועדה לאירוח תמונות ממוזערות של מדיה או מוצרים. הרכיב מתחיל כרשימה צנועה של <ul>, אבל הוא הופך ל-CSS כדי לספק חוויית גלילה משביעת וחלקה, שמציגה את התמונות ומצמיד אותן לרשת. אנחנו מוסיפים את JavaScript כדי לאפשר אינטראקציות בין אינדקס נדידה, ועוזרים למשתמשי מקלדת לדלג על יותר מ-100 פריטים. בנוסף, שאילתת מדיה ניסיונית, prefers-reduced-data, משמשת כדי להפוך את גלילת המדיה לחוויה קלה של גלילה בכותרת.

להתחיל עם תגי עיצוב נגישים

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

העברת רשימה שכוללת רכיב <ul>:

<ul class="horizontal-media-scroller">
  <li></li>
  <li></li>
  <li></li>
  ...
<ul>

אפשר להפוך את הפריטים ברשימה לאינטראקטיביים עם רכיב <a>:

<li>
  <a href="#">
    ...
  </a>
</li>

צריך להשתמש ברכיב <figure> כדי לייצג באופן סמנטי תמונה ואת הכיתוב שלה:

<figure>
  <picture>
    <img alt="..." loading="lazy" src="https://picsum.photos/500/500?1">
  </picture>
  <figcaption>Legends</figcaption>
</figure>

שימו לב למאפיינים alt ו-loading ב-<img>. טקסט חלופי לגלילת מדיה הוא הזדמנות בחוויית המשתמש שעוזרת להוסיף הקשר לתמונה הממוזערת, או כטקסט חלופי אם התמונה לא נטענה, או לספק ממשק משתמש מוקרא למשתמשים שמסתמכים על טכנולוגיה מסייעת כמו קורא מסך. למידע נוסף עיין בחמישה כללי זהב לטקסט חלופי תואם.

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

תמיכה בהעדפה של המשתמש בערכת הצבעים

כדאי להשתמש ב-color-scheme כתג <meta> כדי לאות לדפדפן שהדף שלך מבקש להשתמש גם בסגנונות בהירים וגם בסגנונות כהים של סוכן המשתמש. זהו מצב כהה או מצב בהיר, בהתאם לאופן שבו מסתכלים עליו:

<meta name="color-scheme" content="dark light">

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

מידע נוסף מופיע באתר של תומאס סטיינר בכתובת https://web.dev/color-scheme/.

הוספת תוכן

בהתחשב במבנה התוכן של ul > li > a > figure > picture > img שלמעלה, המשימה הבאה היא להוסיף תמונות ושמות לגלילה. אירזתי את ההדגמה עם תמונות סטטיות וטקסט, אבל אתם יכולים להפעיל אותם ממקור הנתונים המועדף עליכם.

הוספת סגנון באמצעות CSS

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

יצירת הפריסה של פס הגלילה

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

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

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

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2); /* parent owned value for children to be relative to*/
  margin: 0;
}

לאחר מכן, האלמנט <picture> משתמש במאפיין המותאם אישית כדי ליצור את יחס הגובה-רוחב הבסיסי שלנו: תיבה:

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2);
  margin: 0;

  & picture {
    inline-size: var(--size);
    block-size: var(--size);
  }
}

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

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2);
  margin: 0;

  overflow-x: auto;
  overscroll-behavior-inline: contain;

  & > li {
    display: inline-block; /* removes the list-item bullet */
  }

  & picture {
    inline-size: var(--size);
    block-size: var(--size);
  }
}

המדיניות overflow מגדירה את המאפיין <ul> כך שיאפשר גלילה וניווט באמצעות המקלדת ברשימה. לאחר מכן, ::marker של כל רכיב צאצא ישיר של <li> יוסר על ידי קבלת סוג תצוגה חדש של inline-block.

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

img {
  /* smash into whatever box it's in */
  inline-size: 100%;
  block-size: 100%;

  /* don't squish but do cover the space */
  object-fit: cover;

  /* soften the edges */
  border-radius: 1ex;
  overflow: hidden;

  /* if empty, show a gradient placeholder */
  background-image:
    linear-gradient(
      to bottom,
      hsl(0 0% 40%),
      hsl(0 0% 20%)
    );
}

מרווח פנימי לגלילה

יישור לתוכן הדף, וגם שטח הפנים של גלילה מקצה לקצה, הם חיוניים ליצירת רכיב הרמוני ומינימלי.

כדי ליצור פריסת גלילה מקצה לקצה שתואמת לשורות הטיפוגרפיה והפריסה שלנו, צריך להשתמש ב-padding שתואם ל-scroll-padding:

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2);
  margin: 0;

  overflow-x: auto;
  overscroll-behavior-inline: contain;

  padding-inline: var(--gap);
  scroll-padding-inline: var(--gap);
  padding-block: calc(var(--gap) / 2); /* make space for scrollbar and focus outline */
}

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

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

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

.horizontal-media-scroller > li:last-of-type figure {
  position: relative;

  &::after {
    content: "";
    position: absolute;

    inline-size: var(--gap);
    block-size: 100%;

    inset-block-start: 0;
    inset-inline-end: calc(var(--gap) * -1);
  }
}

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

הצמדת גלילה

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

.horizontal-media-scroller {
  --size: 150px;

  display: grid;
  grid-auto-flow: column;
  gap: calc(var(--gap) / 2);
  margin: 0;

  overflow-x: auto;
  overscroll-behavior-inline: contain;

  padding-inline: var(--gap);
  scroll-padding-inline: var(--gap);
  padding-block-end: calc(var(--gap) / 2);

  scroll-snap-type: inline mandatory;

  & figure {
    scroll-snap-align: start;
  }
}

מיקוד

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

.horizontal-media-scroller a {
  outline-offset: 12px;

  &:focus {
    outline-offset: 7px;
  }

  @media (prefers-reduced-motion: no-preference) {
    & {
      transition: outline-offset .25s ease;
    }
  }
}

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

אינדקס נדידה

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

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

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

import {rovingIndex} from 'roving-ux';

rovingIndex({
  element: someElement
});

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

document.querySelectorAll('.horizontal-media-scroller')
  .forEach(scroller =>
    rovingIndex({
      element: scroller,
      target: 'a',
}))

למידע נוסף על האפקט הזה, קראו את ספריית הקוד הפתוח roving-ux.

יחס גובה-רוחב

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

תיבה עם יחס גובה-רוחב של 4:4 מוצגת לצד יחסי עיצוב אחרים, של 16:9 ו-4:3.

@supports (aspect-ratio: 1) {
  .horizontal-media-scroller figure > picture {
    inline-size: auto; /* for a block-size driven ratio */
    aspect-ratio: 1; /* boxes by default */

    @nest section:nth-child(2) & {
      aspect-ratio: 16/9;
    }

    @nest section:nth-child(3) & {
      /* double the size of the others */
      block-size: calc(var(--size) * 2);
      aspect-ratio: 4/3;

      /* adjust size to fit more items into the viewport */
      @media (width <= 480px) {
        block-size: calc(var(--size) * 1.5);
      }
    }
  }
}

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

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

העדפה לצמצום נתונים

השיטה הזו זמינה רק מאחורי הדגל ב-Canary, אבל רציתי להסביר איך אפשר לחסוך כמות משמעותית של זמן טעינת דף ושימוש בנתונים באמצעות כמה שורות CSS. שאילתת המדיה prefers-reduced-data מרמה 5 מאפשרת לשאול אם המכשיר נמצא במצבי נתונים מופחתים, כמו מצב חוסך הנתונים (Data Saver). במקרה שכן, אפשר לשנות את המסמך ובמקרה כזה להסתיר את התמונות.

ALT_TEXT_HERE

figure {
  @media (prefers-reduced-data: reduce) {
    & {
      min-inline-size: var(--size);

      & > picture {
        display: none;
      }
    }
  }
}

עדיין ניתן לנווט בתוכן, אבל ללא העלות של התמונות הכבדות שמורידים. הנה האתר לפני ההוספה של שירות ה-CSS prefers-reduced-data:

(7 בקשות, 100kb של משאבים ב-131 אלפיות השנייה)

ALT_TEXT_HERE

אלה ביצועי האתר אחרי הוספת ה-CSS prefers-reduced-data:

ALT_TEXT_HERE

(71 בקשות, 1.2MB של משאבים ב-1.07 שניות)

64 בקשות פחות – כ-60 תמונות באזור התצוגה (בדיקות שייערכו בתצוגת מסך רחב) של הכרטיסייה הזו בדפדפן, עלייה של כ-80% בעומס הדפים ו-10% מהנתונים שמתקבלים בחיבור לרשת. CSS חזק למדי.

סיכום

עכשיו, אחרי שאת יודעת איך עשיתי את זה, איך היית רוצה?! 🙂

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

מקור

רמיקסים של הקהילה

עדיין אין מה לראות כאן!