إنشاء مكون مربع حوار

تقدّم هذه الصفحة نظرة عامة أساسية حول طريقة إنشاء نماذج مصغّرة ونموذجية سريعة الاستجابة تتكيّف مع الألوان وتجعلها سريعة الاستجابة ويمكن الوصول إليها، وذلك باستخدام العنصر <dialog>.

في هذه المشاركة، أود أن أشارك أفكاري حول كيفية بناء ألوان تكيفية، نماذج مصغّرة وضخمة وسريعة الاستجابة ويمكن الوصول إليها من خلال العنصر <dialog>. جرِّب العرض التوضيحي وشاهد المصدر!

عرض لمربعات الحوار الضخمة والمصغَّرة في المظهرَين الفاتح والداكن

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

نظرة عامة

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

أصبح العنصر <dialog> ثابتًا مؤخرًا في المتصفّحات:

دعم المتصفح

  • 37
  • 79
  • 98
  • 15.4

المصدر

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

Markup

أساسيات العنصر <dialog> متواضعة سيبدأ العنصر مخفيًا تلقائيًا وله أنماط مضمنة لتراكب المحتوى الخاص بك.

<dialog>
  …
</dialog>

يمكننا تحسين هذا المتوقع.

كالعادة، يشارك عنصر الحوار كثيرًا مع شكل، وغالبًا ما تتم تسمية وقابلة للتبديل. حصلت على حرية استخدام عنصر الحوار كلاً من النوافذ المنبثقة لمربعات الحوار الصغيرة (المصغَّرة)، وكذلك مربعات الحوار بملء الصفحة (الضخمة). قمت بتسمية تتميز بحجم كبير وصغير، مع تعديلهما إلى حد كبير بما يناسب حالات الاستخدام المختلفة. لقد أضفت سمة modal-mode للسماح لك بتحديد النوع:

<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>

لقطة شاشة لكل من مربّع الحوار الصغير ومربع الحوار الضخم في المظهرَين الفاتح والداكن.

ليس دائمًا، ولكن بشكل عام سيتم استخدام عناصر الحوار لجمع بعض معلومات التفاعل. إنشاء النماذج داخل عناصر مربّع الحوار معًا. من الجيد أن يكون لديك عنصر نموذج يلتف محتوى مربع الحوار لديك حتى يمكن لـ JavaScript الوصول إلى البيانات التي أدخلها المستخدم. علاوة على ذلك، تسمح الأزرار داخل يمكن لنموذج باستخدام "method="dialog"" إغلاق مربّع حوار بدون JavaScript واجتياز البيانات.

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    …
    <button value="cancel">Cancel</button>
    <button value="confirm">Confirm</button>
  </form>
</dialog>

مربع حوار ضخم

يتضمن مربع الحوار الضخم ثلاثة عناصر داخل النموذج: <header>، <article>، أو <footer> وتكون بمثابة حاويات دلالية، فضلاً عن أهداف نمطية شكل مربع الحوار. يخاطب العنوان النافذة ويعرض . هذه المقالة مخصصة لإدخالات النماذج والمعلومات. يحمل التذييل <menu> من إجمالي وأزرار الإجراءات.

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    <header>
      <h3>Dialog title</h3>
      <button onclick="this.closest('dialog').close('close')"></button>
    </header>
    <article>...</article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

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

مربّع حوار مصغّر

ويشبه مربع الحوار المصغر إلى حد كبير مربع الحوار الضخم، وهو يفتقد إلى العنصر <header>. وهذا يتيح أن يكون أصغر وأكثر شمولاً.

<dialog id="MiniDialog" modal-mode="mini">
  <form method="dialog">
    <article>
      <p>Are you sure you want to remove this user?</p>
    </article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

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

تسهيل الاستخدام

يتمتع عنصر مربع الحوار بإمكانية وصول مدمجة جيدة للغاية. وبدلاً من إضافة هذه ميزات مثل ما أفعله عادةً، والعديد منها موجود بالفعل.

جارٍ استعادة التركيز

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

مع عنصر مربّع الحوار، يكون هذا السلوك التلقائي مضمَّنًا:

ولكن إذا أردت إضافة تأثيرات حركية إلى مربع الحوار تضيع. في قسم JavaScript سأستعيد ذلك الوظيفة.

التركيز المستمر

يدير عنصر مربع الحوار inert لك على المستند. قبل inert، كان يتم استخدام JavaScript لتتبُّع التركيز ويترك عنصرًا، وعند هذه النقطة يعترض طريقه ويعيد إعادته.

دعم المتصفح

  • 102
  • 102
  • 112
  • 15.5

المصدر

بعد inert، يمكن "تجميد" أي أجزاء من المستند. بقدر ما لم تعد تركز على الأهداف أو تكون تفاعلية بالماوس. بدلاً من المصيدة التركيز، يتم توجيه التركيز إلى الجزء التفاعلي الوحيد من الوثيقة.

فتح عنصر والتركيز التلقائي عليه

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

يتم الإغلاق باستخدام مفتاح Escape.

من المهم تسهيل إغلاق هذا العنصر الذي يُحتمل أن يقاطع تجربة المشاهدة. لحسن الحظ، سيتعامل عنصر مربع الحوار مع مفتاح Escape نيابةً عنك، ما يحررك من عبء التنظيم.

الأنماط

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

تسريحة شعر مفتوحة

لتسريع الألوان التكيفية والاتساق العام في التصميم، أجريت بلا خجل جلبت مكتبة متغيرات CSS من Open Props. ضِمن بالإضافة إلى المتغيرات المقدمة المجانية، أقوم أيضًا باستيراد ملف normalize، على سبيل المثال buttons (الأزرار)، وكلاهما مفتوحان كعمليات استيراد اختيارية. تساعدني هذه الواردات في التركيز على تخصيص ومربع حوار وعرض توضيحي دون الحاجة إلى الكثير من الأنماط لدعمها وجعلها

تغيير نمط العنصر <dialog>

امتلاك الموقع المعروض

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

dialog {
  display: grid;
}

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

dialog:not([open]) {
  pointer-events: none;
  opacity: 0;
}

أصبح مربّع الحوار الآن غير مرئي ولا يمكن التفاعل معه عندما لا يكون مفتوحًا. لاحقًا سأضيف بعض JavaScript لإدارة السمة inert في مربّع الحوار، ما يضمن عدم تمكُّن مستخدمي لوحة المفاتيح وقارئ الشاشة من الوصول إلى مربّع الحوار المخفي

منح مربّع الحوار مظهرًا لونيًا تكيُّفيًا

مربّع حوار ضخم يعرض المظهر الفاتح والداكن، ويوضّح ألوان السطح

على الرغم من أنّ color-scheme يتيح استخدام المستند في المتصفّح مظهر الألوان المتكيف مع الإعدادات المفضّلة للنظام الفاتح والداكن، أردتُ تخصيصه عنصر مربع الحوار أكثر من ذلك. تتيح لك "التجهيزات المفتوحة" بعض المساحات الألوان التي تتكيّف تلقائيًا مع الإعدادات المفضّلة للنظام الخفيف والداكن، مماثلة لاستخدام color-scheme. هذه رائعة لإنشاء طبقات في التصميم وأحب استخدام الألوان للمساعدة ويدعم بصريًا هذا المظهر لأسطح الطبقات. لون الخلفية هو var(--surface-1); لوضعه في أعلى هذه الطبقة، استخدم var(--surface-2):

dialog {
  …
  background: var(--surface-2);
  color: var(--text-1);
}

@media (prefers-color-scheme: dark) {
  dialog {
    border-block-start: var(--border-size-1) solid var(--surface-3);
  }
}

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

حجم مربع الحوار المتجاوب

يتم تعيين مربع الحوار الافتراضي لتفويض حجمه إلى محتواه، وعادةً ما عظيم. هدفي هنا هو تقييد max-inline-size بحجم يمكن قراءته (--size-content-3 = 60ch) أو بنسبة% 90 من عرض إطار العرض. هذا النمط ويضمن عدم انتقال مربع الحوار إلى الحافة على جهاز محمول، واسعة على شاشة الكمبيوتر الشخصي، ويصعب قراءتها. ثم أضيف max-block-size وبالتالي لن يتجاوز مربّع الحوار ارتفاع الصفحة. هذا يعني أيضًا أننا يجب تحديد مكان المنطقة القابلة للتمرير في مربع الحوار، في حالة ما إذا كان طويلاً .

dialog {
  …
  max-inline-size: min(90vw, var(--size-content-3));
  max-block-size: min(80vh, 100%);
  max-block-size: min(80dvb, 100%);
  overflow: hidden;
}

هل لاحظت أنّ لديّ max-block-size مرتين؟ يستخدم العنصر الأول 80vh، وحدة إطار عرض. ما أريده حقًا هو إبقاء مربع الحوار ضمن التدفق النسبي، للمستخدمين الدوليين، لذلك أستخدم الخيارات المنطقية والأحدث وجزئيًا وحدة dvb متوافقة في الإعلان الثاني عندما تصبح أكثر استقرارًا.

تحديد موضع مربع الحوار الضخم

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

تؤدي الأنماط التالية إلى تثبيت عنصر مربع الحوار في النافذة، مما يؤدي إلى توسيعه إلى كل ، وتستخدم margin: auto لتوسيط المحتوى:

dialog {
  …
  margin: auto;
  padding: 0;
  position: fixed;
  inset: 0;
  z-index: var(--layer-important);
}
أنماط مربّعات الحوار الضخمة على الأجهزة الجوّالة

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

@media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    margin-block-end: 0;
    border-end-end-radius: 0;
    border-end-start-radius: 0;
  }
}

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

تحديد موضع مربع الحوار المصغّر

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

اجعله مميزًا

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

dialog {
  …
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-6);
}

تخصيص العنصر الزائف في الخلفية

اخترتُ العمل بخفة تامّة باستخدام الصور الخلفية، مع إضافة تأثير تمويه فقط backdrop-filter للانتقال إلى مربّع الحوار الضخم:

دعم المتصفح

  • 76
  • 79
  • 103
  • 18

المصدر

dialog[modal-mode="mega"]::backdrop {
  backdrop-filter: blur(25px);
}

اخترتُ أيضًا إضافة انتقال في backdrop-filter، على أمل أن تتيح المتصفحات الانتقال إلى عنصر الصور الخلفية في المستقبل:

dialog::backdrop {
  transition: backdrop-filter .5s ease;
}

لقطة شاشة لمربّع حوار ضخم على خلفية مموَّهة لصور رمزية ملوّنة

ميزات إضافية للتصميم

أسمي هذا القسم "الإضافات" لأنّه مرتبط بعنصر مربّع الحوار أكثر من عنصر مربع الحوار بشكل عام.

احتواء التمرير

عندما يظهر مربع الحوار، يظل المستخدم قادرًا على تمرير الصفحة خلفه، التي لا أريدها:

في العادة، overscroll-behavior هو الحلّ المعتاد، ولكن وفقًا المواصفات، وليس له أي تأثير في مربع الحوار لأنه ليس منفذ تمرير، أي شريط التمرير، بحيث لا يكون هناك شيء لمنعه. يمكنني استخدام JavaScript لمراقبة الأحداث الجديدة من هذا الدليل، مثل "مغلقة" و"مفتوح"، وتبديل تمت إضافة overflow: hidden إلى المستند، أو يمكنني الانتظار حتى يصبح :has() ثابتًا. جميع المتصفحات:

دعم المتصفح

  • 105
  • 105
  • 121
  • 15.4

المصدر

html:has(dialog[open][modal-mode="mega"]) {
  overflow: hidden;
}

الآن عند فتح مربع حوار ضخم، يحتوي مستند html على overflow: hidden.

التنسيق <form>

بالإضافة إلى كونها عنصرًا مهمًا للغاية في جمع التفاعل المعلومات من المستخدم، فأنا أستخدمها هنا لتخطيط الرأس والتذييل وعناصر المقالة. أنوي باستخدام هذا التنسيق توضيح المقالة الثانوية منطقة قابلة للتمرير. أحقق ذلك من خلال grid-template-rows يتم تحديد 1fr لعنصر المقالة ويكون للنموذج نفسه الحد الأقصى نفسه الارتفاع كعنصر مربع الحوار. إن تعيين هذا الارتفاع الثابت وحجم الصف الثابت هو يسمح بتقييد عنصر المقالة والتمرير عند تجاوزه:

dialog > form {
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: start;
  max-block-size: 80vh;
  max-block-size: 80dvb;
}

لقطة شاشة لأدوات مطوري البرامج التي تتراكب على معلومات تنسيق الشبكة على الصفوف.

تصميم مربع الحوار <header>

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

dialog > form > header {
  display: flex;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  background: var(--surface-2);
  padding-block: var(--size-3);
  padding-inline: var(--size-5);
}

@media (prefers-color-scheme: dark) {
  dialog > form > header {
    background: var(--surface-1);
  }
}

لقطة شاشة لـ Chrome Devtools أثناء تراكب معلومات تنسيق flexbox على رأس مربع الحوار.

نمط زر إغلاق العنوان

بما أنّ العرض التوضيحي يستخدم أزرار Open Props، تم تخصيص زر الإغلاق في أيقونة دائرية ترتكز على زر مثل هذا:

dialog > form > header > button {
  border-radius: var(--radius-round);
  padding: .75ch;
  aspect-ratio: 1;
  flex-shrink: 0;
  place-items: center;
  stroke: currentColor;
  stroke-width: 3px;
}

لقطة شاشة تعرض معلومات الحجم والمساحة المتروكة في &quot;أدوات مطوّري البرامج في Chrome&quot; لزر إغلاق العنوان.

تصميم مربع الحوار <article>

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

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

dialog > form > article {
  overflow-y: auto; 
  max-block-size: 100%; /* safari */
  overscroll-behavior-y: contain;
  display: grid;
  justify-items: flex-start;
  gap: var(--size-3);
  box-shadow: var(--shadow-2);
  z-index: var(--layer-1);
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: light) {
  dialog > form > article {
    background: var(--surface-1);
  }
}

يتمثل دور التذييل في أن يحتوي على قوائم من أزرار الإجراءات. تُستخدم Flexbox من أجل محاذاة المحتوى إلى نهاية محور التذييل المضمّن، ثم بعض المسافات لإعطاء الأزرار مساحة.

dialog > form > footer {
  background: var(--surface-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: dark) {
  dialog > form > footer {
    background: var(--surface-1);
  }
}

لقطة شاشة لـ Chrome Devtools أثناء تركيب معلومات تنسيق flexbox على عنصر التذييل.

menu يُستخدم لتضمين أزرار الإجراءات لمربع الحوار. إنه يستخدم التفاف تنسيق flexbox مع gap لتوفير مسافة بين الأزرار. عناصر القائمة تحتوي على مساحة متروكة مثل <ul>. أنا أيضًا أزلت هذا النمط لأنّه لا أحتاج إليه.

dialog > form > footer > menu {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  padding-inline-start: 0;
}

dialog > form > footer > menu:only-child {
  margin-inline-start: auto;
}

لقطة شاشة لـ Chrome Devtools أثناء تراكب معلومات مربع flexbox على عناصر قائمة التذييل.

Animation

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

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

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

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

انتقال تلقائي آمن ومفيد

على الرغم من أنّ ميزة Open Props تأتي مع إطارات رئيسية لإظهار الصور أو تكبيرها، أفضّلها. نهج متعدد الطبقات للانتقالات كإعداد افتراضي مع الرسوم المتحركة في الإطارات الرئيسية والترقيات المحتملة. في السابق، صممنا بالفعل رؤية مربع الحوار باستخدام التعتيم، من خلال تنسيق 1 أو 0 اعتمادًا على سمة [open]. إلى التي تنتقل بين% 0 و%100، فأخبر المتصفح بالمدة ونوع التخفيف الذي تريده:

dialog {
  transition: opacity .5s var(--ease-3);
}

إضافة حركة إلى الانتقال

إذا كان المستخدم مناسبًا للحركة، يجب أن يتم تمرير كل من مربع الحوار الضخم ومربع الحوار المصغّر ويصبح مدخلاً لهم، ويصبح مخرجًا لهم. يمكنك تحقيق ذلك باستخدام استعلام عن الوسائط (prefers-reduced-motion) وبعض المبادئ المفتوحة:

@media (prefers-reduced-motion: no-preference) {
  dialog {
    animation: var(--animation-scale-down) forwards;
    animation-timing-function: var(--ease-squish-3);
  }

  dialog[open] {
    animation: var(--animation-slide-in-up) forwards;
  }
}

تكييف الصورة المتحركة عند الخروج مع الأجهزة الجوّالة

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

@media (prefers-reduced-motion: no-preference) and @media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    animation: var(--animation-slide-out-down) forwards;
    animation-timing-function: var(--ease-squish-2);
  }
}

JavaScript

هناك عدة عناصر يمكنك إضافتها باستخدام JavaScript:

// dialog.js
export default async function (dialog) {
  // add light dismiss
  // add closing and closed events
  // add opening and opened events
  // add removed event
  // removing loading attribute
}

وتنبع هذه الإضافات من الرغبة في الإغلاق الفاتح (النقر على مربّع الحوار. والصور المتحركة وبعض الأحداث الإضافية للحصول على توقيت أفضل بيانات النموذج.

جارٍ إضافة إغلاق الإضاءة

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

export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
}

const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

الإشعار dialog.close('dismiss'). يتم استدعاء الحدث وتوفير سلسلة. ويمكن استرداد هذه السلسلة بواسطة JavaScript آخر للحصول على إحصاءات حول كيفية تم إغلاق مربع الحوار. ستجد أنني قدمت أيضًا سلاسل إغلاق في كل مرة أتصل فيها من أزرار مختلفة، لتوفير سياق لتطبيقي حول تفاعل المستخدم.

إضافة أحداث الإغلاق والأحداث المغلقة

يأتي عنصر مربع الحوار مع حدث إغلاق: ينبعث على الفور عندما لمربع الحوار close(). ونظرًا لأننا نحرك هذا العنصر، فإنه من من الجيد أن يكون لديك أحداث قبل الرسوم المتحركة وبعدها، لإجراء تغيير البيانات أو إعادة تعيين نموذج مربع الحوار. أستخدمه هنا لإدارة عملية إضافة inert في مربّع الحوار المغلق، وفي الإصدار التجريبي أستخدمها لتعديلها قائمة الصور الرمزية إذا أرسل المستخدم صورة جديدة.

لتحقيق ذلك، أنشِئ حدثَين جديدَين باسم closing وclosed. بَعْدَ ذَلِكْ استمع إلى الحدث المغلق المدمج في مربع الحوار. من هنا، اضبط مربع الحوار على inert وإرسال حدث closing. المهمة التالية هي انتظار والرسوم المتحركة والانتقالات لإنهاء تشغيلها في مربع الحوار، ثم إرسال حدث واحد (closed).

const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')

export default async function (dialog) {
  …
  dialog.addEventListener('close', dialogClose)
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

الدالة animationsComplete، والتي تستخدم أيضًا في إنشاء نخب ، يعرض وعدًا استنادًا إلى إكمال الرسوم المتحركة ووعود الانتقال. لذلك dialogClose هو غير متزامن الدالة، يمكنه حينئذٍ await عاد الوعد والمضي قدمًا بثقة إلى الحدث المغلق.

إضافة أحداث الفتح والفتح

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

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

…
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')

export default async function (dialog) {
  …
  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })
}

const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

سيتم استدعاء دالة استدعاء مراقب التغيير عندما يظهر مربع الحوار تغيير التصنيفات، حيث يتم تقديم قائمة التغييرات كمصفوفة. التكرار خلال تتغير السمة، بحثًا عن علامة attributeName لتكون مفتوحة. بعد ذلك، تحقَّق من إذا كان العنصر يتضمن السمة أم لا: فإن هذا يحدد ما إذا كان مربع الحوار تم فتحه. إذا تم فتح التطبيق، أزِل السمة inert واضبط التركيز. إلى أي عنصر يطلب autofocus أو أول عنصر button موجود في مربع الحوار. أخيرًا، على غرار علامة الإغلاق والحدث المغلق، إرسال الحدث الافتتاحي على الفور، وانتظار الرسوم المتحركة لإنهاء الحدث، ثم إرسال الحدث الذي تم افتتاحه.

إضافة حدث تمت إزالته

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

ويمكنك تحقيق ذلك باستخدام مراقِب آخر للطفرات. هذه المرة، بدلاً من ملاحظة السمات على عنصر مربع الحوار، سنلاحظ العناصر الثانوية للجسم ويراقب عناصر مربع الحوار التي تتم إزالتها.

…
const dialogRemovedEvent = new Event('removed')

export default async function (dialog) {
  …
  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })
}

const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

يتم استدعاء استدعاء مراقب التغيُّر عند إضافة عناصر ثانوية أو إزالتها من نص المستند. التغييرات المحددة التي تتم مراقبتها مخصصة removedNodes التي تتوفّر فيها nodeName من مربع حوار. إذا تمت إزالة مربّع حوار، ستتم إزالة أحداث النقر والإغلاق إلى إخلاء مساحة في الذاكرة، وإرسال الحدث المخصص الذي تمت إزالته.

إزالة سمة التحميل

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

export default async function (dialog) {
  …
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

مزيد من المعلومات عن مشكلة منع الصور المتحركة في الإطارات الرئيسية عند تحميل الصفحة هنا.

معًا

ها هو dialog.js بالكامل، بعد أن أوضحنا كل قسم كل على حدة:

// custom events to be added to <dialog>
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')
const dialogRemovedEvent = new Event('removed')

// track opening
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

// track deletion
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

// wait for all dialog animations to complete their promises
const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

// click outside the dialog handler
const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

// page load dialogs setup
export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
  dialog.addEventListener('close', dialogClose)

  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })

  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })

  // remove loading attribute
  // prevent page load @keyframes playing
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

استخدام وحدة dialog.js

تتوقع الدالة التي تم تصديرها من الوحدة أن يتم استدعاءها واجتياز مربع حوار الذي يريد إضافة هذه الأحداث والوظائف الجديدة:

import GuiDialog from './dialog.js'

const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')

GuiDialog(MegaDialog)
GuiDialog(MiniDialog)

تمامًا مثل ذلك، تتم ترقية مربّعَي الحوار من خلال الإغلاق الفاتح والرسوم المتحركة. وإصلاحات التحميل والمزيد من الأحداث للعمل بها.

الاستماع إلى الأحداث المخصّصة الجديدة

يمكن الآن لكل عنصر مربّع حوار تمت ترقيته الاستماع إلى خمسة أحداث جديدة، مثل ما يلي:

MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)

MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)

MegaDialog.addEventListener('removed', dialogRemoved)

في ما يلي مثالان على التعامل مع هذه الأحداث:

const dialogOpening = ({target:dialog}) => {
  console.log('Dialog opening', dialog)
}

const dialogClosed = ({target:dialog}) => {
  console.log('Dialog closed', dialog)
  console.info('Dialog user action:', dialog.returnValue)

  if (dialog.returnValue === 'confirm') {
    // do stuff with the form values
    const dialogFormData = new FormData(dialog.querySelector('form'))
    console.info('Dialog form data', Object.fromEntries(dialogFormData.entries()))

    // then reset the form
    dialog.querySelector('form')?.reset()
  }
}

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

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

الخاتمة

الآن بعد أن تعرّفت على كيفية إجراء ذلك، كيف يمكنك‽ 🙂

يمكننا تنويع أساليبنا وتعلُّم جميع طرق إنشاء المحتوى على الويب.

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

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

الموارد