ساخت یک جزء آرد سوخاری

یک مرور کلی و اساسی در مورد نحوه ساخت یک کامپوننت breadcrumbs واکنش‌گرا و قابل دسترس برای کاربران جهت پیمایش سایت شما.

در این پست می‌خواهم ایده‌هایم را در مورد روشی برای ساخت کامپوننت‌های breadcrumb با شما به اشتراک بگذارم. نسخه آزمایشی را امتحان کنید .

نسخه آزمایشی

اگر ویدیو را ترجیح می‌دهید، نسخه یوتیوب این پست در اینجا آمده است:

نمای کلی

یک کامپوننت breadcrumbs نشان می‌دهد که کاربر در کجای سلسله مراتب سایت قرار دارد. این نام از هانسل و گرتل گرفته شده است، که breadcrumbs را پشت سر خود در جنگل تاریک انداختند و توانستند با ردیابی خرده نان‌ها به سمت عقب، راه خود را به خانه پیدا کنند.

بردکرامب‌های این پست، بردکرامب‌های استاندارد نیستند، بلکه شبیه بردکرامب هستند. آن‌ها با قرار دادن صفحات خواهر و برادر مستقیماً در نوار ناوبری با استفاده از <select> ، قابلیت‌های بیشتری ارائه می‌دهند و دسترسی چندلایه را امکان‌پذیر می‌سازند.

تجربه کاربری پس‌زمینه

در ویدیوی دموی کامپوننت بالا، دسته‌بندی‌های جایگزین، ژانرهای بازی‌های ویدیویی هستند. این مسیر با پیمایش مسیر زیر ایجاد شده است: home » rpg » indie » on sale ، همانطور که در زیر نشان داده شده است.

این کامپوننت breadcrumb باید کاربران را قادر سازد تا با سرعت و دقت در این سلسله مراتب اطلاعاتی حرکت کنند؛ از شاخه‌ها بپرند و صفحات را انتخاب کنند.

معماری اطلاعات

به نظرم فکر کردن به مجموعه‌ها و اقلام مفید است.

مجموعه‌ها

یک مجموعه، مجموعه‌ای از گزینه‌ها برای انتخاب است. از صفحه اصلی نمونه اولیه breadcrumb این پست، مجموعه‌ها عبارتند از FPS، RPG، brawler، dungeon crawler، sport و puzzle.

اقلام

یک بازی ویدیویی یک آیتم است، یک مجموعه خاص نیز می‌تواند یک آیتم باشد اگر نشان‌دهنده یک مجموعه دیگر باشد. برای مثال، RPG یک آیتم و یک مجموعه معتبر است. وقتی یک آیتم باشد، کاربر در صفحه آن مجموعه قرار دارد. برای مثال، آنها در صفحه RPG هستند که لیستی از بازی‌های RPG، از جمله زیرشاخه‌های اضافی AAA، Indie و Selfpublished را نمایش می‌دهد.

به زبان علوم کامپیوتر، این کامپوننت breadcrumbs یک آرایه چندبعدی را نشان می‌دهد:

const rawBreadcrumbData = {
  "FPS": {...},
  "RPG": {
    "AAA": {...},
    "indie": {
      "new": {...},
      "on sale": {...},
      "under 5": {...},
    },
    "self published": {...},
  },
  "brawler": {...},
  "dungeon crawler": {...},
  "sports": {...},
  "puzzle": {...},
}

اپلیکیشن یا وب‌سایت شما معماری اطلاعات (IA) سفارشی خواهد داشت که یک آرایه چندبعدی متفاوت ایجاد می‌کند، اما امیدوارم مفهوم صفحات فرود مجموعه و پیمایش سلسله مراتب بتواند در بردکرامب‌های شما نیز گنجانده شود.

طرح‌بندی‌ها

نشانه‌گذاری

کامپوننت‌های خوب با HTML مناسب شروع می‌شوند. در بخش بعدی، انتخاب‌های نشانه‌گذاری و نحوه تأثیر آنها بر کل کامپوننت را بررسی خواهم کرد.

طرح تیره و روشن

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

متا تگ color-scheme در قطعه کد بالا به مرورگر اطلاع می‌دهد که این صفحه به سبک‌های مرورگر روشن و تیره نیاز دارد. breadcrumbs های مثال هیچ CSS برای این طرح‌های رنگی ندارند و بنابراین breadcrumbs از رنگ‌های پیش‌فرض ارائه شده توسط مرورگر استفاده می‌کند.

<nav class="breadcrumbs" role="navigation"></nav>

استفاده از عنصر <nav> برای ناوبری سایت مناسب است، که نقش ضمنی ARIA ناوبری را دارد. در آزمایش، متوجه شدم که داشتن ویژگی role ، نحوه تعامل یک صفحه‌خوان با عنصر را تغییر می‌دهد، در واقع به عنوان ناوبری اعلام شده بود، بنابراین تصمیم گرفتم آن را اضافه کنم.

آیکن‌ها

وقتی یک آیکون در یک صفحه تکرار می‌شود، عنصر <use> در SVG به این معنی است که می‌توانید path را یک بار تعریف کنید و از آن برای همه نمونه‌های آیکون استفاده کنید. این کار از تکرار اطلاعات مسیر یکسان، که باعث ایجاد اسناد بزرگتر و احتمال ناسازگاری مسیر می‌شود، جلوگیری می‌کند.

برای استفاده از این تکنیک، یک عنصر SVG مخفی به صفحه اضافه کنید و آیکون‌ها را در یک عنصر <symbol> با یک شناسه منحصر به فرد قرار دهید:

<svg style="display: none;">

  <symbol id="icon-home">
    <title>A home icon</title>
    <path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
  </symbol>

  <symbol id="icon-dropdown-arrow">
    <title>A down arrow</title>
    <path d="M19 9l-7 7-7-7"/>
  </symbol>

</svg>

مرورگر SVG HTML را می‌خواند، اطلاعات آیکون را در حافظه قرار می‌دهد و با ارجاع بقیه صفحه به شناسه برای استفاده‌های بیشتر از آیکون، مانند این، ادامه می‌دهد:

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-home" />
</svg>

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-dropdown-arrow" />
</svg>

DevTools یک عنصر استفاده شده در SVG رندر شده را نشان می‌دهد.

یک بار تعریف کنید، هر چند بار که دوست دارید استفاده کنید، با حداقل تأثیر بر عملکرد صفحه و سبک‌دهی انعطاف‌پذیر. توجه داشته باشید که aria-hidden="true" به عنصر SVG اضافه شده است. این آیکون‌ها برای کسی که فقط محتوا را می‌شنود مفید نیستند، پنهان کردن آنها از این کاربران، از اضافه کردن نویز غیرضروری توسط آنها جلوگیری می‌کند.

اینجاست که breadcrumb سنتی و breadcrumb های موجود در این کامپوننت از هم جدا می‌شوند. معمولاً این فقط یک لینک <a> است، اما من تجربه کاربری پیمایش را با یک select پنهان اضافه کرده‌ام. کلاس .crumb مسئول چیدمان لینک و آیکون است، در حالی که .crumbicon مسئول کنار هم قرار دادن آیکون و عنصر select است. من آن را split-link نامیده‌ام زیرا عملکردهای آن بسیار شبیه به split-button است، اما برای پیمایش صفحه.

<span class="crumb">
  <a href="#sub-collection-b">Category B</a>
  <span class="crumbicon">
    <svg>...</svg>
    <select class="disguised-select" title="Navigate to another category">
      <option>Category A</option>
      <option selected>Category B</option>
      <option>Category C</option>
    </select>
  </span>
</span>

یک لینک و چند گزینه چیز خاصی نیست، اما قابلیت‌های بیشتری را به یک بردکرامب ساده اضافه می‌کند. اضافه کردن یک title به عنصر <select> برای کاربران صفحه‌خوان مفید است و به آنها اطلاعاتی در مورد عملکرد دکمه می‌دهد. با این حال، همین کمک را به همه افراد دیگر نیز ارائه می‌دهد، خواهید دید که در آیپد در جلو و مرکز قرار دارد. یک ویژگی، زمینه دکمه را برای بسیاری از کاربران فراهم می‌کند.

تصویر صفحه با عنصر انتخاب نامرئی که نشانگر ماوس روی آن قرار گرفته و راهنمای ابزار متنی آن نمایش داده می‌شود.

تزئینات جداکننده

<span class="crumb-separator" aria-hidden="true">→</span>

جداکننده‌ها اختیاری هستند، اضافه کردن فقط یکی از آنها هم عالی عمل می‌کند (به مثال سوم در ویدیوی بالا مراجعه کنید). سپس به هر کدام aria-hidden="true" می‌دهم، زیرا تزئینی هستند و چیزی نیستند که یک خواننده صفحه نمایش لازم باشد اعلام کند.

ویژگی gap که در ادامه به آن پرداخته می‌شود، فاصله‌گذاری بین این موارد را ساده می‌کند.

سبک‌ها

از آنجایی که این رنگ از رنگ‌های سیستمی استفاده می‌کند، بیشتر برای استایل‌ها از فاصله‌ها و پشته‌ها استفاده می‌شود!

جهت و جریان طرح‌بندی

ابزارهای توسعه (DevTools) ترازبندی ناوبری breadcrumb را با ویژگی پوشش flexbox آن نشان می‌دهند.

عنصر ناوبری اصلی nav.breadcrumbs یک ویژگی سفارشی با دامنه مشخص برای استفاده عناصر فرزند تعیین می‌کند و در غیر این صورت یک طرح‌بندی افقی-عمودی ایجاد می‌کند. این امر تضمین می‌کند که خرده‌ها، جداکننده‌ها و آیکون‌ها تراز شوند.

.breadcrumbs {
  --nav-gap: 2ch;

  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: calc(var(--nav-gap) / 2);
}

یک breadcrumb که به صورت عمودی با لایه‌های flexbox تراز شده است.

هر .crumb همچنین یک طرح‌بندی افقی-عمودی با مقداری فاصله ایجاد می‌کند، اما به‌طور ویژه لینک‌های فرزند خود را هدف قرار می‌دهد و استایل white-space: nowrap را مشخص می‌کند. این برای breadcrumbهای چندکلمه‌ای بسیار مهم است زیرا نمی‌خواهیم چندخطی شوند. بعداً در این پست، استایل‌هایی را برای مدیریت سرریز افقی ناشی از این ویژگی white-space اضافه خواهیم کرد.

.crumb {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--nav-gap) / 4);

  & > a {
    white-space: nowrap;

    &[aria-current="page"] {
      font-weight: bold;
    }
  }
}

aria-current="page" اضافه شده است تا لینک صفحه فعلی از بقیه متمایز شود. نه تنها کاربران صفحه‌خوان به وضوح متوجه می‌شوند که لینک مربوط به صفحه فعلی است، بلکه ما این عنصر را به صورت بصری استایل‌بندی کرده‌ایم تا به کاربران بینا نیز کمک کند تجربه کاربری مشابهی داشته باشند.

کامپوننت .crumbicon از grid برای قرار دادن یک آیکون SVG با یک عنصر <select> «تقریباً نامرئی» استفاده می‌کند.

ابزارهای توسعه شبکه (Grid DevTools) دکمه‌ای را نشان می‌دهند که روی آن ردیف و ستون هر دو پشته (stack) نامیده می‌شوند.

.crumbicon {
  --crumbicon-size: 3ch;

  display: grid;
  grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
  place-items: center;

  & > * {
    grid-area: stack;
  }
}

عنصر <select> در DOM آخرین عنصر است، بنابراین در بالای پشته قرار دارد و تعاملی است. یک استایل opacity: .01 اضافه کنید تا عنصر همچنان قابل استفاده باشد و نتیجه یک کادر انتخاب است که کاملاً با شکل آیکون مطابقت دارد. این یک روش خوب برای سفارشی کردن ظاهر یک عنصر <select> در عین حفظ عملکرد داخلی آن است.

.disguised-select {
  inline-size: 100%;
  block-size: 100%;
  opacity: .01;
  font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}

سرریز

بردکرامب‌ها باید بتوانند یک مسیر بسیار طولانی را نشان دهند. من طرفدار این هستم که در صورت لزوم، همه چیز به صورت افقی از صفحه نمایش خارج شود و احساس کردم این کامپوننت بردکرامب به خوبی واجد شرایط است.

.breadcrumbs {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: calc(var(--nav-gap) / 2);

  & > .crumb:last-of-type {
    scroll-snap-align: end;
  }

  @supports (-webkit-hyphens:none) { & {
    scroll-snap-type: none;
  }}
}

استایل‌های overflow، تجربه کاربری زیر را ایجاد می‌کنند:

  • پیمایش افقی با قابلیت مهار پیمایش بیش از حد.
  • پدینگ اسکرول افقی.
  • یک نقطه snap روی آخرین خرده نان. این به این معنی است که در بارگذاری صفحه، اولین خرده نان snap شده و در دید قرار می‌گیرد.
  • نقطه‌ی ضربه محکم و ناگهانی را از سافاری حذف می‌کند، که با ترکیب‌های پیمایش افقی و جلوه‌ی ضربه محکم و ناگهانی مشکل دارد.

پرس‌وجوهای رسانه‌ای

یک تنظیم ظریف برای نماهای کوچکتر، پنهان کردن برچسب "خانه" و باقی گذاشتن فقط آیکون آن است:

@media (width <= 480px) {
  .breadcrumbs .home-label {
    display: none;
  }
}

برای مقایسه، پودر سوخاری با برچسب خانگی و بدون برچسب خانگی را در کنار هم قرار دهید.

دسترسی‌پذیری

حرکت

حرکت زیادی در این کامپوننت وجود ندارد، اما با قرار دادن گذار در یک بررسی prefers-reduced-motion ، می‌توانیم از حرکت ناخواسته جلوگیری کنیم.

@media (prefers-reduced-motion: no-preference) {
  .crumbicon {
    transition: box-shadow .2s ease;
  }
}

هیچ‌کدام از استایل‌های دیگر نیازی به تغییر ندارند، افکت‌های hover و focus بدون transition هم عالی و معنادار هستند، اما اگر حرکت مشکلی نداشته باشد، یک transition ظریف به تعامل اضافه خواهیم کرد.

جاوا اسکریپت

اول، صرف نظر از نوع روتری که در سایت یا برنامه خود استفاده می‌کنید، وقتی کاربر breadcrumbs را تغییر می‌دهد، URL باید به‌روزرسانی شود و صفحه مناسب به کاربر نشان داده شود. دوم، برای عادی‌سازی تجربه کاربری، مطمئن شوید که وقتی کاربران فقط گزینه‌های <select> را مرور می‌کنند، هیچ پیمایش غیرمنتظره‌ای رخ نمی‌دهد.

دو معیار مهم تجربه کاربری که باید توسط جاوا اسکریپت مدیریت شوند: انتخاب تغییر کرده است و جلوگیری از فعال شدن رویداد تغییر در <select> با استفاده از eager.

جلوگیری از رویداد eager به دلیل استفاده از عنصر <select> ضروری است. در ویندوز اج و احتمالاً سایر مرورگرها، رویداد select changed زمانی فعال می‌شود که کاربر با صفحه کلید گزینه‌ها را مرور می‌کند. به همین دلیل است که من آن را eager نامیدم، زیرا کاربر فقط گزینه را به صورت کاذب انتخاب کرده است، مانند شناور یا فوکوس، اما انتخاب را با enter یا click تأیید نکرده است. رویداد eager این ویژگی تغییر دسته کامپوننت را غیرقابل دسترس می‌کند، زیرا باز کردن کادر انتخاب و مرور ساده یک مورد، رویداد را فعال کرده و صفحه را قبل از اینکه کاربر آماده باشد تغییر می‌دهد.

یک رویداد <select> تغییر یافته بهتر

const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])

// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
  let ignoreChange = false

  nav.addEventListener('change', e => {
    if (ignoreChange) return
    // it's actually changed!
  })

  nav.addEventListener('keydown', ({ key }) => {
    if (preventedKeys.has(key))
      ignoreChange = true
    else if (allowedKeys.has(key))
      ignoreChange = false
  })
})

استراتژی این کار این است که رویدادهای فشردن کلید کیبورد را روی هر عنصر <select> زیر نظر بگیریم و تعیین کنیم که آیا کلید فشرده شده، تأیید ناوبری ( Tab یا Enter ) بوده یا ناوبری فضایی ( ArrowUp یا ArrowDown ). با این تشخیص، کامپوننت می‌تواند تصمیم بگیرد که هنگام اجرای رویداد برای عنصر <select> ، منتظر بماند یا ادامه دهد.

نتیجه‌گیری

حالا که فهمیدی چطور انجامش دادم، تو چطور‽ 🙂

بیایید رویکردهایمان را متنوع کنیم و تمام روش‌های ساخت در وب را یاد بگیریم. یک نسخه آزمایشی ایجاد کنید، لینک‌ها را برای من توییت کنید ، و من آن را به بخش ریمیکس‌های انجمن در زیر اضافه خواهم کرد!

ریمیکس‌های انجمن