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

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

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

العرض التوضيحي

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

نظرة عامة

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

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

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

Web Tactics

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

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

ملف HTML

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

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

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

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

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

<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>

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

التنسيقات التي يمكن الانتقال فيها

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

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

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

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

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

كان التصميم ذو المستوى الأعلى الذي اخترته هو flex (Flexbox). لقد ضبطتُ الاتجاه على column، لذا يتم ترتيب العنوان الرئيسي والقسم عموديًا. هذه هي نافذة التمرير الأولى، وهي تخفي كل ما يفيض عن حدودها باستخدام overflow hidden. سيتم قريبًا استخدام ميزة التمرير السريع في العنوان والقسم كمنطقتَين منفصلتَين.

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، واحتواء التمرير للحدّ من التمرير الزائد، وإخفاء أشرطة التمرير على الأجهزة التي تعمل باللمس، وأخيرًا ميزة &quot;التوافق مع التمرير السريع&quot; لقفل مساحات عرض المحتوى. يمكن الوصول إلى ترتيب علامات التبويب في لوحة المفاتيح، كما أنّ أي تفاعلات توجّه التركيز بشكل طبيعي. تتلقّى حاويات التمرير السريع أيضًا تفاعلاً جميلاً بأسلوب لوحة العرض الدوّارة من لوحة المفاتيح.

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

يجب ترتيب روابط التنقّل في سطر واحد، بدون فواصل بين الأسطر، مع توسيطها عموديًا، ويجب أن يتم محاذاة كل عنصر رابط مع حاوية scroll-snap. ‫Swift work for 2021 CSS!

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 في شريط التنقّل على تراكبات باللون الوردي الساطع، ما يوضّح المساحة التي تشغلها في المكوّن بالإضافة إلى موضع تجاوزها

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

هذا القسم هو عنصر مرن ويجب أن يكون المستهلك الرئيسي للمساحة. ويجب أيضًا إنشاء أعمدة لوضع المقالات فيها. مرة أخرى، شكراً على جهودكم السريعة في عام 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 عرض أي شيء عند تدوير الجهاز أو تغيير حجم المتصفّح. يمكنك تجربة ذلك في وضع الجهاز ضمن "أدوات مطوّري البرامج في Chromium" من خلال اختيار أي وضع آخر غير متجاوب، ثم تغيير حجم إطار الجهاز. لاحظ أنّ العنصر يظلّ ظاهرًا ومثبّتًا مع محتواه. أصبحت هذه الميزة متاحة منذ أن عدّل Chromium طريقة التنفيذ لتتوافق مع المواصفات. إليك مشاركة على المدونة حول هذا الموضوع.

Animation

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

سأربط خطًا تحت علامة التبويب بموضع التمرير في المقالة. لا يقتصر التحديد على المحاذاة الجميلة، بل يثبّت أيضًا بداية ونهاية الحركة. يؤدي ذلك إلى إبقاء <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 مع أي متصفّح. وهي مواصفات مسودّة تتضمّن عمليات تنفيذ تجريبية فقط. ومع ذلك، يتضمّن هذا المتصفّح برنامجًا احتياطيًا أستخدمه في هذا العرض التوضيحي.

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

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

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

  matchingNavItem && setActiveTab(matchingNavItem);
};

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

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، وغرِّد لي بنسختك، وسأضيفها إلى قسم ريمكسات من إنشاء المنتدى أدناه.

ريمكسات من إنشاء المنتدى