بناء مكون نخب

نظرة عامة أساسية حول كيفية إنشاء مكوّن مربّع حوار منبثق قابل للتكيّف وسهل الاستخدام

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

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

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

نظرة عامة

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

التفاعلات

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

Markup

يُعدّ العنصر <output> خيارًا جيدًا للرسالة المنبثقة لأنّه يتم الإعلان عنها على شاشة القارئين. توفّر لغة HTML الصحيحة قاعدة آمنة يمكننا تحسينها باستخدام JavaScript و CSS، وسيكون هناك الكثير من JavaScript.

نخب

<output class="gui-toast">Item added to cart</output>

يمكن أن يكون أكثر شمولاً من خلال إضافة role="status". يقدّم ذلك بديلاً في حال عدم منح المتصفّح عناصر <output> دورًا ضمنيًا وفقًا للمواصفات.

<output role="status" class="gui-toast">Item added to cart</output>

حاوية لفائف الخبز المحمّص

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

<section class="gui-toast-group">
  <output role="status">Wizard Rose added to cart</output>
  <output role="status">Self Watering Pot added to cart</output>
</section>

التنسيقات

اخترت تثبيت الإشعارات المنبثقة في inset-block-end مساحة العرض، وفي حال إضافة المزيد من الإشعارات المنبثقة، يتم تجميعها من تلك الحافة على الشاشة.

حاوية واجهة المستخدم الرسومية

تُجري حاوية الإشعارات المنبثقة جميع عمليات التنسيق لعرض الإشعارات المنبثقة. يتم تحديده fixed لمساحة العرض ويستخدم السمة المنطقية inset لتحديد الحواف التي سيتم تثبيتها عليها، بالإضافة إلى جزء صغير من padding من حافة block-end نفسها.

.gui-toast-group {
  position: fixed;
  z-index: 1;
  inset-block-end: 0;
  inset-inline: 0;
  padding-block-end: 5vh;
}

لقطة شاشة تعرض حجم مربّع &quot;أدوات مطوّري البرامج&quot; ومساحة الحشو التي تمّت إضافتها على عنصر ‎.gui-toast-container

بالإضافة إلى تحديد موضع حاوية مربّع المعلومات المنبثق في إطار العرض، تكون حاوية مربّع المعلومات المنبثق حاوية شبكة يمكنها محاذاة مربّعات المعلومات المنبثقة وتوزيعها. يتمّ وضع العناصر في وسط الشاشة كمجموعة باستخدام justify-content وفي وسط الشاشة بشكلٍ فردي باستخدام justify-items. أضِف القليل من gap حتى لا تلتصق الخبز المحمص ببعضها.

.gui-toast-group {
  display: grid;
  justify-items: center;
  justify-content: center;
  gap: 1vh;
}

لقطة شاشة تعرض شبكة CSS التي تمّت إضافتها على مجموعة النوافذ المنبثقة الصغيرة، وهذه المرة
يتمّ إبراز المساحة والفواصل بين العناصر الفرعية للنوافذ المنبثقة الصغيرة.

إشعار منبثق لواجهة المستخدم الرسومية

يحتوي مربّع الرسائل المنبثقة الفردي على بعض padding، وبعض الزوايا الأكثر نعومة باستخدام border-radius، ودالة min() لتحديد حجم مربّع الرسائل المنبثقة على الأجهزة الجوّالة وأجهزة الكمبيوتر المكتبي. يمنع الحجم المتوافق مع جميع الأجهزة في ملف CSS التالي تضخّم النوافذ المنبثقة إلى أكثر من% 90 من إطار العرض أو 25ch.

.gui-toast {
  max-inline-size: min(25ch, 90vw);
  padding-block: .5ch;
  padding-inline: 1ch;
  border-radius: 3px;
  font-size: 1rem;
}

لقطة شاشة لعنصر gui-toast. واحد، مع عرض الحشو وحدود
الدائرة

الأنماط

بعد ضبط التنسيق والموضع، أضِف CSS للمساعدة في التكيّف مع إعدادات المستخدم وتفاعلاته.

حاوية الخبز المحمص

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

.gui-toast-group {
  pointer-events: none;
}

إشعار منبثق لواجهة المستخدم الرسومية

يمكنك منح الإشعارات المنبثقة مظهرًا متوافقًا فاتحًا أو داكنًا باستخدام خصائص مخصّصة ونظام الألوان HSL و query media preference.

.gui-toast {
  --_bg-lightness: 90%;

  color: black;
  background: hsl(0 0% var(--_bg-lightness) / 90%);
}

@media (prefers-color-scheme: dark) {
  .gui-toast {
    color: white;
    --_bg-lightness: 20%;
  }
}

Animation

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

في ما يلي الإطارات الرئيسية المستخدَمة في الصورة المتحركة للشريط المنبثق. ستتحكّم لغة CSS في موعد ظهور إشعار التلميح ومدة ظهوره ووقت اختفائه، وكل ذلك في صورة متحركة واحدة.

@keyframes fade-in {
  from { opacity: 0 }
}

@keyframes fade-out {
  to { opacity: 0 }
}

@keyframes slide-in {
  from { transform: translateY(var(--_travel-distance, 10px)) }
}

بعد ذلك، يُعدّ عنصر مربّع الرسائل المنبثقة المتغيّرات وينظّم الإطارات الرئيسية.

.gui-toast {
  --_duration: 3s;
  --_travel-distance: 0;

  will-change: transform;
  animation: 
    fade-in .3s ease,
    slide-in .3s ease,
    fade-out .3s ease var(--_duration);
}

@media (prefers-reduced-motion: no-preference) {
  .gui-toast {
    --_travel-distance: 5vh;
  }
}

JavaScript

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

import Toast from './toast.js'

Toast('My first toast')

إنشاء مجموعة الإشعارات المنبثقة والإشعارات المنبثقة

عند تحميل وحدة مربّع الرسائل المنبثقة من JavaScript، يجب أن تنشئ حاوية مربّع رسائل منبثقة وتضيفها إلى الصفحة. اخترت إضافة العنصر قبل body، ما سيقلّل من احتمالية حدوث مشاكل في تجميع z-index لأنّ الحاوية فوق الحاوية الخاصة بجميع عناصر النصّ.

const init = () => {
  const node = document.createElement('section')
  node.classList.add('gui-toast-group')

  document.firstElementChild.insertBefore(node, document.body)
  return node
}

لقطة شاشة لمجموعة النوافذ المنبثقة بين علامتَي head وbody

يتمّ استدعاء الدالة init() داخليًا في الوحدة، ما يؤدي إلى إخفاء العنصر بصفته Toaster:

const Toaster = init()

يتم إنشاء عنصر HTML للرسالة المنبثقة باستخدام الدالة createToast(). تتطلّب الدالة بعض النصوص للرسالة المنبثقة، وتُنشئ عنصر <output> وتزيّنه ببعض الفئات والسمات، وتضبط النص، وتُعيد العقدة.

const createToast = text => {
  const node = document.createElement('output')
  
  node.innerText = text
  node.classList.add('gui-toast')
  node.setAttribute('role', 'status')

  return node
}

إدارة إشعار منبثق واحد أو عدّة إشعارات

تضيف JavaScript الآن حاوية إلى المستند لاحتواء الإشعارات المنبثقة وتكون جاهزة لإضافة الإشعارات المنبثقة التي تم إنشاؤها. تنظِّم الدالة addToast() معالجة رسائل Toast واحدة أو عدّة رسائل. أولاً، التحقّق من عدد الإشعارات المنبثقة وما إذا كانت الحركة مقبولة، ثم استخدام هذه المعلومات لإلحاق الإشعار المنبثق أو إنشاء بعض الرسوم المتحرّكة الأنيقة لكي تظهر الإشعارات المنبثقة الأخرى "لإفساح المجال" للإشعار المنبثق الجديد

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

  Toaster.children.length && motionOK
    ? flipToast(toast)
    : Toaster.appendChild(toast)
}

عند إضافة أول رسالة مصغّرة، يضيف Toaster.appendChild(toast) رسالة مصغّرة إلى الصفحة التي تشغّل صور CSS المتحركة: عرض الصورة المتحركة، الانتظار 3s، إخفاء الصورة المتحركة. يتمّ استدعاء flipToast() عندما تكون هناك إشعارات Toast حالية، باستخدام أسلوب يُسمّى FLIP من قِبل Paul Lewis. والفكرة هي احتساب الفرق في مواضع الحاوية، قبل إضافة النافذة المنبثقة الجديدة وبعدها. يمكنك اعتبار ذلك مثل وضع علامة على مكان "محمّصة الخبز" الآن، ومكانها المستقبلي، ثم إضافة مؤثرات متحركة من مكانها السابق إلى مكانها الحالي.

const flipToast = toast => {
  // FIRST
  const first = Toaster.offsetHeight

  // add new child to change container size
  Toaster.appendChild(toast)

  // LAST
  const last = Toaster.offsetHeight

  // INVERT
  const invert = last - first

  // PLAY
  const animation = Toaster.animate([
    { transform: `translateY(${invert}px)` },
    { transform: 'translateY(0)' }
  ], {
    duration: 150,
    easing: 'ease-out',
  })
}

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

تجميع كل JavaScript معًا

عند استدعاء Toast('my first toast')، يتم إنشاء رسالة مصغّرة وإضافتها إلى الصفحة (ربما يتم أيضًا إنشاء صورة متحرّكة للحاوية لاستيعاب الرسالة المصغّرة الجديدة)، ويتم عرض وعد ويتم مراقبة الرسالة المصغّرة التي تم إنشاؤها عند اكتمال المحتوى المتحرّك في CSS (الصور المتحرّكة الثلاث للإطارات الرئيسية) لحلّ الوعد.

const Toast = text => {
  let toast = createToast(text)
  addToast(toast)

  return new Promise(async (resolve, reject) => {
    await Promise.allSettled(
      toast.getAnimations().map(animation => 
        animation.finished
      )
    )
    Toaster.removeChild(toast)
    resolve() 
  })
}

أعتقد أنّ الجزء المربك في هذا الرمز هو وظيفة Promise.allSettled() وتعيين toast.getAnimations(). بما أنّني استخدمت عدّة صور متحرّكة للإطارات الرئيسية لرسالة Toast، لمعرفة ما إذا كانت قد اكتملت جميعها، يجب أن تتم أولاً طلب كل منها من JavaScript، ثم يجب مراقبة كل من finished الوعد الذي يُقدّمه كل منها لإكمال العملية. allSettled يناسبنا ذلك، حيث يتم حلّ المشكلة تلقائيًا بعد تنفيذ كل الوعود التي تقدّمها. يعني استخدام await Promise.allSettled() أنّ السطر التالي من الرمز البرمجي يمكنه إزالة العنصر بثقة وافتراض أنّ النافذة المنبثقة قد أكملت دورة حياتها. أخيرًا، يؤدي استدعاء resolve() إلى الوفاء بالوعد العالي المستوى لعرض رسالة Toast حتى تتمكّن المطوّرون من تنظيف الرمز البرمجي أو تنفيذ عمل آخر بعد ظهور رسالة Toast.

export default Toast

أخيرًا، يتم تصدير الدالة Toast من الوحدة لنصوص برمجية أخرى تتمكّن من استيرادها واستخدامها.

استخدام مكوّن Toast

يتم استخدام مربّع التنبيه المنبثق أو تجربة المطوّر لمربّع التنبيه المنبثق من خلال استيراد الدالة Toast واستدعائها باستخدام سلسلة رسالة.

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

إذا أراد المطوّر إجراء عملية تنظيف أو أيّ إجراء آخر، يمكنه استخدام async وawait بعد عرض مربّع التلميح.

import Toast from './toast.js'

async function example() {
  await Toast('Wizard Rose added to cart')
  console.log('toast finished')
}

الخاتمة

الآن بعد أن عرفت كيف فعلت ذلك، كيف ستفعل ذلك؟ 🙂

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

الريمكسات التي أنشأها المستخدمون