إنشاء مكوّن علامات التبويب

نظرة عامة أساسية حول كيفية إنشاء مكوّن علامات تبويب مشابه لتلك الموجودة في تطبيقات iOS وتطبيق Android.

في هذه المشاركة، أريد مشاركة أفكاري حول إنشاء مكوّن علامات تبويب للويب يكون متجاوبًا ويتوافق مع إدخالات الأجهزة المتعددة ويعمل على جميع المتصفّحات. جرِّب العرض التوضيحي.

عرض توضيحي

إذا كنت تفضّل مشاهدة فيديو، إليك نسخة من هذا المنشور على YouTube:

نظرة عامة

علامات التبويب هي مكوّن شائع في أنظمة التصميم، ولكن يمكن أن تتخذ أشكالاً متعددة. في البداية، كانت علامات التبويب على أجهزة الكمبيوتر المكتبي تستند إلى عنصر <frame>، والآن لدينا مكوّنات سلسة للأجهزة الجوّالة تعرض المحتوى بشكل متحرك استنادًا إلى خصائص فيزيائية. وجميعها تحاول تحقيق الهدف نفسه، وهو توفير المساحة.

في الوقت الحالي، تتمثّل أساسيات تجربة المستخدم في علامات التبويب في منطقة التنقّل بين الأزرار التي تبدّل مستوى ظهور المحتوى في إطار العرض. تشترك العديد من مناطق المحتوى المختلفة في نفس المساحة، ولكن يتم تقديمها بشكل مشروط بناءً على الزر المحدد في التنقل.

تتسم هذه المجموعة بالفوضى بسبب التنوع الهائل في الأنماط التي طبّقها الويب على مفهوم المكوّنات
صورة مجمّعة لأنماط تصميم الويب لمكوّن علامة التبويب من السنوات العشر الماضية

أساليب الويب

بشكل عام، وجدتُ أنّ إنشاء هذا المكوّن بسيط جدًا، وذلك بفضل بعض الميزات المهمة في منصة الويب:

  • scroll-snap-points لتوفير تجربة تمرير وتفاعلات لوحة مفاتيح أنيقة مع مواضع توقُّف مناسبة للتمرير
  • الروابط لصفحات في التطبيق من خلال تجزئات عناوين URL للمتصفّح الذي يتعامل مع تثبيت التمرير في الصفحة ودعم المشاركة
  • إتاحة قارئ الشاشة باستخدام ترميز العنصرَين <a> وid="#hash"
  • ‫prefers-reduced-motion لتفعيل تأثيرات الانتقال بالتلاشي التدريجي والتنقّل الفوري في الصفحة
  • ميزة الويب @scroll-timeline قيد الإعداد لوضع خط تحت علامة التبويب المحدّدة وتغيير لونها ديناميكيًا

ملف HTML

بشكل أساسي، تجربة المستخدم هنا هي: النقر على رابط، وجعل عنوان URL يمثّل حالة الصفحة المضمّنة، ثم الاطّلاع على تعديل منطقة المحتوى أثناء انتقال المتصفّح إلى العنصر المطابق.

هناك بعض عناصر المحتوى الهيكلية، مثل الروابط و:target. نحتاج إلى قائمة بالروابط، والتي تعتبر <nav> رائعة لها، وقائمة بعناصر <article>، والتي تعتبر <section> رائعة لها. سيتطابق كل تجزئة رابط مع قسم، مما يسمح للمتصفح بالتمرير عبر الارتساء.

يتم النقر على زر رابط، ما يؤدي إلى ظهور محتوى مركّز

على سبيل المثال، يؤدي النقر على رابط إلى التركيز تلقائيًا على مقالة :target في Chrome 89، بدون الحاجة إلى استخدام JS. يمكن للمستخدم بعد ذلك التمرير في محتوى المقالة باستخدام جهاز الإدخال الخاص به كالمعتاد. وهو محتوى تكميلي، كما هو موضّح في الترميز.

لقد استخدمتُ الترميز التالي لتنظيم علامات التبويب:

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

يمكنني إنشاء روابط بين العنصرَين <a> و<article> باستخدام السمتَين href وid على النحو التالي:

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

بعد ذلك، ملأت المقالات بكميات مختلطة من لوريم، والروابط بمجموعة مختلطة من العناوين والصور. بعد توفّر المحتوى، يمكننا البدء بالتخطيط.

التنسيقات القابلة للتمرير

هناك 3 أنواع مختلفة من مناطق الانتقال في هذا المكوّن:

  • يمكن التنقّل بشكلٍ أفقي في شريط التنقّل (الوردي)
  • يمكن الانتقال أفقيًا في مساحة المحتوى (باللون الأزرق)
  • يمكن التمرير عموديًا في كل عنصر من عناصر المقالة (باللون الأخضر).
3 مربّعات ملوّنة مع أسهم اتجاهية مطابقة للألوان تحدّد مناطق الانتقال وتوضّح اتجاه الانتقال.

هناك نوعان مختلفان من العناصر المرتبطة بالتمرير:

  1. النافذة
    مربّع بأبعاد محدّدة يتضمّن نمط السمة overflow.
  2. مساحة عرض كبيرة جدًا
    في هذا التنسيق، تكون حاويات القائمة هي: روابط التنقّل ومقالات الأقسام ومحتوى المقالات.

التنسيق <snap-tabs>

كان التخطيط ذو المستوى الأعلى الذي اخترته هو flex (Flexbox). لقد ضبطت الاتجاه على column، لذا يتم ترتيب العنوان والقسم عموديًا. هذه هي نافذة التمرير الأولى، وهي تخفي كل شيء مع إخفاء الفائض. سيتم قريبًا استخدام تأثير تجاوز حد التمرير في العنوان وقسم المحتوى كمناطق فردية.

HTML
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
CSS
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

بالعودة إلى المخطّط البياني الملوّن الذي يتضمّن 3 أشرطة تمرير:

  • <header> جاهز الآن ليكون حاوية التمرير (الوردية).
  • تم إعداد <section> ليكون حاوية التمرير (الزرقاء).

تساعدنا الإطارات التي حدّدتها أدناه باستخدام VisBug في رؤية النوافذ التي أنشأتها حاويات الانتقال.

يحتوي عنصرَي الرأس والقسم على تراكبات باللون الوردي الفاقع، ما يحدّد المساحة التي يشغلانها في المكوّن

تنسيق علامات التبويب <header>

التنسيق التالي هو نفسه تقريبًا: أستخدم flex لإنشاء ترتيب عمودي.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

يجب أن ينتقل .snap-indicator أفقيًا مع مجموعة الروابط، ويساعد تخطيط العنوان هذا في إعداد هذه المرحلة. لا توجد عناصر ذات موضع مطلق هنا!

يحتوي العنصران nav وspan.indicator على تراكبات باللون الوردي الفاقع، ما يحدّد المساحة التي يشغلانها في المكوّن

بعد ذلك، أنماط الانتقال. اتّضح أنّه يمكننا مشاركة أنماط التنقّل بين منطقتَي التنقّل الأفقي (العنوان والقسم)، لذا أنشأتُ فئة مساعدة باسم .scroll-snap-x.

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

يحتاج كل منها إلى تجاوز حد التمرير على المحور x، واحتواء التمرير لتجنّب تجاوز حد التمرير، وأشرطة تمرير مخفية للأجهزة التي تعمل باللمس، وأخيرًا محاذاة مواضع التمرير لقفل مناطق عرض المحتوى. يمكن الوصول إلى ترتيب علامات التبويب في لوحة المفاتيح وأي تفاعلات توجه التركيز بشكل طبيعي. تتلقّى حاويات محاذاة مواضع التمرير أيضًا تفاعلاً رائعًا بنمط لوحة العرض الدوّارة من لوحة المفاتيح.

تنسيق عنوان علامات التبويب <nav>

يجب وضع روابط التنقّل في سطر واحد، بدون فواصل أسطر، مع توسيطها عموديًا، ويجب أن يتم محاذاة كل عنصر رابط مع حاوية محاذاة مواضع التمرير. عمل سريع لخدمة مقارنة الأسعار (CSS) لعام 2021

HTML
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
CSS
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

يتم تحديد نمط كل رابط وحجمه، لذا يحتاج تخطيط التنقل فقط إلى تحديد الاتجاه والتدفق. تضفي العروض الفريدة لعناصر التنقل على الانتقال بين علامات التبويب طابعًا ممتعًا، حيث يضبط المؤشر عرضه ليتناسب مع الهدف الجديد. بناءً على عدد العناصر الموجودة هنا، سيعرض المتصفح شريط تمرير أم لا.

تحتوي عناصر a في nav على تراكبات باللون الوردي الفاقع، وتحدّد المساحة التي تشغلها في المكوّن بالإضافة إلى مكان تجاوزها

تنسيق علامات التبويب <section>

هذا القسم هو عنصر مرن ويجب أن يكون المستهلك المهيمن للمساحة. ويجب أيضًا إنشاء أعمدة لوضع المقالات فيها. مرة أخرى، عمل سريع لـ CSS 2021! يؤدي استخدام block-size: 100% إلى تمديد هذا العنصر لملء العنصر الرئيسي قدر الإمكان، ثم بالنسبة إلى تنسيقه الخاص، يتم إنشاء سلسلة من الأعمدة التي يبلغ عرضها 100% من عرض العنصر الرئيسي. تعمل النسب المئوية بشكل رائع هنا لأننا كتبنا قيودًا قوية على العنصر الرئيسي.

HTML
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
CSS
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

وكأنّنا نقول "وسِّع عموديًا قدر الإمكان بطريقة قوية" (تذكَّر العنوان الذي ضبطناه على flex-shrink: 0: إنّه دفاع ضد هذا التوسيع)، ما يضبط ارتفاع الصف لمجموعة من الأعمدة ذات الارتفاع الكامل. يخبر النمط auto-flow الشبكة بوضع العناصر الفرعية دائمًا في خط أفقي، بدون التفاف، وهو بالضبط ما نريده؛ لتجاوز النافذة الرئيسية.

تتضمّن عناصر المقالة تراكبات باللون الوردي الفاقع، وتحدّد المساحة التي تشغلها في المكوّن ومكان تجاوزها

أجد صعوبة في فهم هذه الأمور أحيانًا. يتناسب عنصر القسم هذا مع المربع، ولكنه أنشأ أيضًا مجموعة من المربعات. آمل أن تكون العناصر المرئية والتفسيرات مفيدة.

تنسيق علامات التبويب <article>

يجب أن يتمكّن المستخدم من الانتقال إلى الأعلى أو الأسفل في محتوى المقالة، ويجب ألا تظهر أشرطة التمرير إلا إذا كان هناك فائض في المحتوى. عناصر المقالة هذه في موضع أنيق. وهي في الوقت نفسه عنصر رئيسي للتمرير وعنصر فرعي للتمرير. يتعامل المتصفح حقًا مع بعض تفاعلات اللمس والماوس ولوحة المفاتيح الصعبة بالنسبة لنا هنا.

HTML
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
CSS
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

اخترت أن يتم تثبيت المقالات داخل شريط التمرير الرئيسي. يعجبني حقًا كيف يتم محاذاة عناصر روابط التنقل وعناصر المقالة مع بداية السطر في حاويات التمرير الخاصة بها. يبدو الأمر وكأنه علاقة متناغمة.

يحتوي عنصر المقالة وعناصره الفرعية على تراكبات باللون الوردي الفاقع، تحدد المساحة التي تشغلها في المكوّن والاتجاه الذي تفيض فيه

المقالة هي عنصر فرعي في الشبكة، ويتم تحديد حجمها مسبقًا لتكون مساحة إطار العرض التي نريد توفير تجربة مستخدم للتمرير فيها. هذا يعني أنّني لست بحاجة إلى أي أنماط للارتفاع أو العرض هنا، بل أحتاج فقط إلى تحديد كيفية تجاوزه. أضبط overflow-y على auto، ثم أضبط أيضًا تفاعلات التمرير باستخدام السمة overscroll-behavior المفيدة.

ملخّص عن 3 مناطق للتمرير

في ما يلي، اخترت في إعدادات النظام "إظهار أشرطة التمرير دائمًا". أعتقد أنّ من المهم جدًا أن يعمل التنسيق مع تفعيل هذا الإعداد، كما هو الحال بالنسبة إليّ لمراجعة التنسيق وتنظيم الانتقال.

تم ضبط أشرطة التمرير الثلاثة لتظهر، وهي تستهلك الآن مساحة التنسيق، ولا يزال المكوّن يبدو رائعًا

أعتقد أنّ رؤية مساحة شريط التمرير في هذا المكوّن تساعد في توضيح أماكن مناطق التمرير والاتجاه الذي تتيحه وكيفية تفاعلها مع بعضها البعض. فكِّر في كيفية استخدام كل من إطارات نوافذ التمرير هذه كعناصر رئيسية مرنة أو شبكية للتنسيق.

يمكن أن تساعدنا "أدوات مطوري البرامج" في تصوّر ذلك:

تحتوي مناطق الانتقال على تراكبات أدوات الشبكة ونموذج flexbox، والتي تحدد المساحة التي تشغلها في المكوِّن والاتجاه الذي تفيض فيه
لقطة شاشة لأدوات مطوّري البرامج في Chromium تعرض تخطيط عنصر التنقّل في نموذج flexbox المليء بعناصر الارتساء، وتخطيط قسم الشبكة المليء بعناصر المقالات، وعناصر المقالات المليئة بالفقرات وعنصر العنوان.

اكتملت تنسيقات التمرير: المحاذاة، والربط بصفحات في التطبيق، وإمكانية الوصول باستخدام لوحة المفاتيح. أساس قوي لتحسين تجربة المستخدم والأسلوب والبهجة.

تسليط الضوء على الميزات

تحافظ العناصر الفرعية التي تم تثبيتها على موضعها الثابت أثناء تغيير الحجم. وهذا يعني أنّ JavaScript لن تحتاج إلى إظهار أي شيء عند تدوير الجهاز أو تغيير حجم المتصفّح. يمكنك تجربة ذلك في وضع الجهاز ضمن &quot;أدوات مطوّري البرامج في Chromium&quot; من خلال اختيار أي وضع آخر غير متجاوب، ثم تغيير حجم إطار الجهاز. لاحظ أن العنصر يظل في العرض ومغلقًا بمحتواه. تتوفّر هذه الميزة منذ أن عدّل Chromium طريقة التنفيذ لتتوافق مع المواصفات. إليك منشور مدوّنة حول هذا الموضوع.

الصور المتحركة

الهدف من عمل الرسوم المتحركة هنا هو ربط التفاعلات بوضوح مع ملاحظات واجهة المستخدم. يساعد ذلك في توجيه المستخدم أو مساعدته في اكتشاف كل المحتوى بسلاسة (على أمل). سأضيف الحركة لغرض معيّن وبشكل مشروط. يمكن للمستخدمين الآن تحديد إعدادات الحركة المفضّلة لديهم في نظام التشغيل، وأنا أستمتع كثيرًا بالاستجابة لإعداداتهم المفضّلة في واجهاتي.

سأربط الخط السفلي لعلامة التبويب بموضع الانتقال في المقالة. لا تقتصر المحاذاة على الترتيب الجميل، بل تشمل أيضًا تثبيت بداية الحركة ونهايتها. يؤدي ذلك إلى إبقاء <nav>، الذي يعمل كـ خريطة مصغّرة، مرتبطًا بالمحتوى. سنتحقّق من الإعداد المفضّل للحركة لدى المستخدم من خلال كل من CSS وJS. هناك بعض الأماكن الرائعة التي يجب مراعاتها!

سلوك الانتقال

هناك فرصة لتحسين سلوك الحركة لكل من :target وelement.scrollIntoView(). ويكون هذا الخيار فوريًا تلقائيًا. يضبط المتصفّح موضع الانتقال فقط. ماذا لو أردنا الانتقال إلى موضع الانتقال هذا، بدلاً من الانتقال إليه مباشرةً؟

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

بما أنّنا نقدّم الحركة هنا، والحركة التي لا يتحكّم فيها المستخدم (مثل الانتقال للأعلى أو للأسفل)، لا نطبّق هذا النمط إلا إذا لم يكن لدى المستخدم أي إعدادات مفضّلة في نظام التشغيل الخاص به بشأن تقليل الحركة. بهذه الطريقة، نقدّم فقط حركة التمرير للأشخاص الذين يوافقون عليها.

مؤشر علامات التبويب

الغرض من هذه الحركة هو المساعدة في ربط المؤشر بحالة المحتوى. قررتُ استخدام أنماط التلاشي المتداخل للألوان border-bottom للمستخدمين الذين يفضّلون الحركة المنخفضة، ورسوم متحركة منزلقة مرتبطة بالتمرير مع تلاشي الألوان للمستخدمين الذين لا يمانعون الحركة.

في "أدوات مطوّري البرامج في Chromium"، يمكنني تبديل الإعدادات المفضّلة وعرض نمطَي الانتقال المختلفَين. لقد استمتعتُ كثيرًا بإنشاء هذا.

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

أخفي .snap-indicator عندما يفضّل المستخدم تقليل الحركة لأنّني لم أعد بحاجة إليه. بعد ذلك، أستبدلها بأنماط border-block-end وtransition. لاحظ أيضًا في تفاعل علامات التبويب أن عنصر التنقل النشط لا يحتوي فقط على تمييز تسطير العلامة التجارية، ولكن لون النص أيضًا أغمق. يتميز العنصر النشط بتباين أعلى في لون النص وإضاءة سفلية ساطعة.

بضعة أسطر إضافية من CSS ستجعل المستخدم يشعر بأنّه مرئي (بمعنى أنّنا نحترم تفضيلاته المتعلقة بالحركة). رائع!

@scroll-timeline

في القسم أعلاه، شرحتُ كيفية التعامل مع أنماط التلاشي المتقاطع للحركة المنخفضة، وفي هذا القسم سأشرح كيفية ربط المؤشر ومنطقة التمرير معًا. هذه بعض الأشياء التجريبية الممتعة في المستقبل. آمل أن تكونوا متحمسين مثلي.

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
);

أتحقّق أولاً من إعدادات الحركة المفضّلة لدى المستخدم من JavaScript. إذا كانت نتيجة هذا هي false، أي أنّ المستخدم يفضّل تقليل الحركة، لن نشغّل أيًا من تأثيرات الحركة المرتبطة بالتمرير.

if (motionOK) {
  // motion based animation code
}

في وقت كتابة هذا المقال، لم يكن هناك أي متصفّح متوافق مع @scroll-timeline. إنّها مواصفات أولية مع عمليات تنفيذ تجريبية فقط. ومع ذلك، فإنّها تحتوي على polyfill، والذي أستخدمه في هذا العرض التوضيحي.

ScrollTimeline

على الرغم من أنّ CSS وJavaScript يمكنهما إنشاء مخططات زمنية للتمرير، اخترت استخدام JavaScript لأتمكّن من استخدام قياسات العناصر المباشرة في الصور المتحركة.

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

أريد أن يتبع عنصر معيّن موضع الانتقال لعنصر آخر، ومن خلال إنشاء ScrollTimeline، أحدّد العنصر الذي يتحكّم في رابط الانتقال، وهو scrollSource. عادةً ما يتم تشغيل صورة متحركة على الويب مقابل علامة إطار زمني عام، ولكن باستخدام sectionScrollTimeline مخصّص في الذاكرة، يمكنني تغيير كل ذلك.

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

قبل أن أتناول الإطارات الرئيسية للصورة المتحركة، أعتقد أنّه من المهم الإشارة إلى أنّ العنصر الذي يتبع التمرير، tabindicator، سيتم تحريكه استنادًا إلى مخطط زمني مخصّص، وهو تمرير القسم. يكتمل الربط، ولكن ينقصه المكوّن الأخير، وهو النقاط التي يتم التنقّل بينها، والمعروفة أيضًا باسم الإطارات الرئيسية.

الإطارات الرئيسية الديناميكية

هناك طريقة فعّالة جدًا لإضافة حركة باستخدام CSS التعريفية البحتة من خلال @scroll-timeline، ولكن الحركة التي اخترت تنفيذها كانت ديناميكية للغاية. لا توجد طريقة للانتقال بين عرض auto، ولا توجد طريقة لإنشاء عدد من الإطارات الرئيسية ديناميكيًا استنادًا إلى طول العناصر الفرعية.

ومع ذلك، يعرف JavaScript كيفية الحصول على هذه المعلومات، لذا سنكرر العناصر الفرعية بأنفسنا ونحصل على القيم المحسوبة في وقت التشغيل:

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

لكل tabnavitem، يتم تفكيك موضع offsetLeft وإرجاع سلسلة تستخدمه كقيمة translateX. يؤدي ذلك إلى إنشاء 4 إطارات مفتاحية للتحويل للرسوم المتحركة. يتم تنفيذ الإجراء نفسه للعرض، حيث يتم سؤال كل عنصر عن عرضه الديناميكي ثم يتم استخدامه كقيمة إطار مفتاحي.

في ما يلي مثال على النتائج، استنادًا إلى الخطوط والإعدادات المفضّلة في المتصفّح:

الإطارات الرئيسية لـ TranslateX:

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

الإطارات الرئيسية للعرض:

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

باختصار، سيتم الآن تحريك مؤشر علامة التبويب عبر 4 إطارات رئيسية بناءً على موضع محاذاة مواضع التمرير في محتوى القسم القابل للتمرير. تنشئ نقاط المحاذاة تحديدًا واضحًا بين الإطارات الرئيسية وتضيف حقًا إلى الشعور المتزامن للرسوم المتحركة.

علامة تبويب نشطة وأخرى غير نشطة مع تراكبات VisBug التي تعرض نتائج التباين الناجحة لكليهما

يتحكّم المستخدم في الحركة من خلال تفاعله، فيرى عرض المؤشر وموضعه يتغيّران من قسم إلى آخر، ويتتبّع ذلك بشكل مثالي مع التمرير.

ربما لم تلاحظ، لكنني فخور جدًا بانتقال اللون حيث يتم تحديد عنصر التنقل المميز.

يظهر اللون الرمادي الفاتح غير المحدد أكثر تراجعًا عندما يكون للعنصر المميز تباين أكبر. من الشائع تغيير لون النص، مثلاً عند تمرير مؤشر الماوس فوقه وعند اختياره، ولكن من الأفضل تغيير هذا اللون عند الانتقال للأسفل، بالتزامن مع مؤشر التسطير.

إليك كيفية إجراء ذلك:

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

يحتاج كل رابط تنقّل في علامة التبويب إلى هذه الحركة الجديدة للألوان، مع تتبُّع المخطط الزمني نفسه للتمرير مثل مؤشر التسطير. أستخدم المخطط الزمني نفسه كما في السابق: بما أنّ دوره هو إصدار إشارة عند الانتقال، يمكننا استخدام هذه الإشارة في أي نوع من الصور المتحركة التي نريدها. كما فعلتُ سابقًا، أنشئ 4 إطارات رئيسية في الحلقة، وأعيد الألوان.

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

يبرز الإطار الرئيسي باللون var(--text-active-color) الرابط، ويكون لونه عاديًا في الحالات الأخرى. تسهّل الحلقة المتداخلة الأمر نسبيًا، لأنّ الحلقة الخارجية هي كل عنصر تنقّل، والحلقة الداخلية هي الإطارات الرئيسية الشخصية لكل عنصر تنقّل. أتحقّق مما إذا كان عنصر الحلقة الخارجية هو نفسه عنصر الحلقة الداخلية، وأستخدم ذلك لمعرفة وقت اختياره.

لقد استمتعتُ كثيرًا بكتابة هذا المقال. كثيرًا جدًا.

تحسينات إضافية على JavaScript

تجدر الإشارة إلى أنّ جوهر ما أعرضه لك هنا يعمل بدون JavaScript. بعد أن أوضحنا ذلك، لِنرَ كيف يمكننا تحسينه عند توفّر JavaScript.

الروابط المؤدية إلى صفحات في التطبيق هي مصطلح خاص بالأجهزة الجوّالة، ولكن أعتقد أنّ الغرض من الرابط المؤدي إلى صفحة في التطبيق يتحقق هنا من خلال علامات التبويب، حيث يمكنك مشاركة عنوان URL مباشرةً إلى محتوى علامة التبويب. سينتقل المتصفّح في الصفحة إلى المعرّف الذي يتطابق مع تجزئة عنوان URL. وجدنا أنّ onloadمعالج هذا أحدث التأثير على جميع المنصات.

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

مزامنة نهاية الانتقال

لا ينقر المستخدمون دائمًا أو يستخدمون لوحة المفاتيح، وأحيانًا يقومون فقط بالتمرير الحر، كما ينبغي أن يكونوا قادرين على ذلك. عندما يتوقف شريط تمرير القسم عن التمرير، يجب أن يتطابق أي مكان يهبط فيه في شريط التنقل العلوي.

إليك كيفية انتظار انتهاء الانتقال: js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

Whenever the sections are being scrolled, clear the section timeout if there, and start a new one. عند توقّف التمرير في الأقسام، لا تمحُ المهلة، بل شغِّلها بعد 100 ملي ثانية من التوقّف. عندما يتم تشغيلها، يتم استدعاء الدالة التي تسعى إلى معرفة المكان الذي توقف فيه المستخدم.

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

بافتراض أنّ محاذاة مواضع التمرير قد تمّت، من المفترض أن يؤدي تقسيم موضع التمرير الحالي على عرض منطقة التمرير إلى الحصول على عدد صحيح وليس عددًا عشريًا. أحاول بعد ذلك الحصول على عنصر تنقل من ذاكرة التخزين المؤقت عبر هذا الفهرس المحسوب، وإذا عثر على شيء ما، فأنا أرسل المطابقة ليتم تعيينها نشطة.

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

يبدأ ضبط علامة التبويب النشطة بمسح أي علامة تبويب نشطة حاليًا، ثم منح عنصر التنقل الوارد سمة الحالة النشطة. يتضمن استدعاء scrollIntoView() تفاعلًا ممتعًا مع CSS يستحق الملاحظة.

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

في CSS لأداة محاذاة مواضع التمرير الأفقي، دمجنا استعلامًا عن الوسائط يطبّق التمرير smooth إذا كان المستخدم لا يعاني من حساسية تجاه الحركة. يمكن لـ JavaScript إجراء طلبات بحرية للتمرير إلى العناصر في العرض، ويمكن لـ CSS إدارة تجربة المستخدم بشكل تعريفي. إنّهما يشكّلان ثنائيًا رائعًا في بعض الأحيان.

الخاتمة

بعد أن عرفتم كيف فعلتُ ذلك، كيف ستفعلونه أنتم؟ وهذا يجعل بعض بنية المكونات ممتعة! مَن سيصنع الإصدار الأول مع فتحات في إطاره المفضل؟ 🙂

لنتعرّف على الطرق المختلفة لإنشاء المواقع الإلكترونية. يمكنك إنشاء Glitch، وإرسال تغريدة إليّ تتضمّن نسختك، وسأضيفها إلى قسم الريمكسات التي أنشأها المنتدى أدناه.

ريمكسات من المنتدى