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

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

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

דמו

אם אתם מעדיפים סרטון, הנה גרסה של הפוסט הזה ב-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 שמתואר למעלה, המשימה הבאה היא להוסיף תמונות וכותרות לגלילה. הכנסתי לדמו תמונות וטקסט סטטיים של placeholder, אבל אתם יכולים להשתמש במקור הנתונים המועדף עליכם.

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

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

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

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

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

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

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

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

הצמדה לגלילה

תוכלו להפוך מאגר גלילה עם גלישת-overflow למסך תצוגה עם הצמדה באמצעות שורת 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;
  }
}

מיקוד

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

.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. כך משתמשים בו ב-3 גלילות:

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

נכון למועד כתיבת הפוסט הזה, התמיכה ב-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. באמצעות תחביר ההטמעה של טיוטה, יחס הגובה-רוחב של כל תמונה משתנה בהתאם לשורה שבה היא מופיעה – הראשונה, השנייה או השלישית. תחביר העץ מאפשר גם להגדיר התאמות קטנות של חלון התצוגה, יחד עם לוגיק הגודל האחר.

בעזרת ה-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 של משאבים ב-131ms)

ALT_TEXT_HERE

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

ALT_TEXT_HERE

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

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

סיכום

עכשיו, אחרי שסיפרתי לך איך עשיתי את זה, איך היית עושה את זה? 🙂

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

מקור

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

אין כאן שום דבר עדיין לראות!