ساخت مولفه گفتگو

یک نمای کلی از نحوه ساخت مینی و مگا مدال های سازگار با رنگ، پاسخگو و در دسترس با عنصر <dialog> .

در این پست می‌خواهم نظرات خود را در مورد نحوه ساخت مینی و مگا مدال‌های سازگار با رنگ، واکنش‌گرا و در دسترس با عنصر <dialog> به اشتراک بگذارم. نسخه ی نمایشی را امتحان کنید و منبع را مشاهده کنید !

نمایش دیالوگ های مگا و مینی در تم های روشن و تاریک آنها.

اگر ویدیو را ترجیح می دهید، در اینجا یک نسخه YouTube از این پست وجود دارد:

نمای کلی

عنصر <dialog> برای اطلاعات یا اقدامات متنی درون صفحه عالی است. زمانی را در نظر بگیرید که تجربه کاربر می تواند از یک عملکرد صفحه به جای عملکرد چند صفحه ای بهره مند شود: شاید به این دلیل که فرم کوچک است یا تنها اقدام مورد نیاز کاربر تأیید یا لغو است.

عنصر <dialog> اخیراً در بین مرورگرها پایدار شده است:

پشتیبانی مرورگر

  • کروم: 37.
  • لبه: 79.
  • فایرفاکس: 98.
  • سافاری: 15.4.

منبع

متوجه شدم که این عنصر چند چیز را از دست داده است، بنابراین در این چالش رابط کاربری گرافیکی، مواردی را که انتظار دارم تجربه توسعه دهندگان را اضافه می‌کنم: رویدادهای اضافی، حذف نور، انیمیشن‌های سفارشی، و نوع کوچک و مگا.

نشانه گذاری

ملزومات عنصر <dialog> بسیار کم است. این عنصر به طور خودکار پنهان می شود و دارای سبک هایی برای همپوشانی محتوای شما است.

<dialog>
  …
</dialog>

ما می توانیم این پایه را بهبود بخشیم.

به طور سنتی، یک عنصر گفتگو با یک مودال اشتراک‌گذاری زیادی دارد و اغلب نام‌ها قابل تعویض هستند. من در اینجا آزادی استفاده از عنصر دیالوگ را برای پنجره های کوچک گفتگو (مینی)، و همچنین دیالوگ های تمام صفحه (مگا) گرفتم. من آنها را مگا و مینی نامیدم، با هر دو دیالوگ کمی برای موارد استفاده متفاوت. من یک ویژگی modal-mode اضافه کردم تا بتوانید نوع آن را مشخص کنید:

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

اسکرین شات از دیالوگ های کوچک و مگا در هر دو تم روشن و تاریک.

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

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

عنصر محاوره ای پایه محکمی برای یک عنصر نمای کامل فراهم می کند که می تواند داده ها و تعامل کاربر را جمع آوری کند. این موارد ضروری می توانند تعاملات بسیار جالب و قدرتمندی را در سایت یا برنامه شما ایجاد کنند.

دسترسی

عنصر گفتگو دارای دسترسی داخلی بسیار خوبی است. به جای اضافه کردن این ویژگی ها مانند من معمولاً، بسیاری از آنها در حال حاضر وجود دارند.

بازیابی تمرکز

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

با عنصر گفتگو، این یک رفتار پیش فرض داخلی است:

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

به دام انداختن تمرکز

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

پشتیبانی مرورگر

  • کروم: 102.
  • لبه: 102.
  • فایرفاکس: 112.
  • سافاری: 15.5.

منبع

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

باز کردن و فوکوس خودکار یک عنصر

به طور پیش‌فرض، عنصر محاوره‌ای فوکوس را به اولین عنصر قابل فوکوس در نشانه‌گذاری گفتگو اختصاص می‌دهد. اگر این بهترین عنصری نیست که کاربر به صورت پیش‌فرض آن را انتخاب کند، از ویژگی autofocus استفاده کنید. همانطور که قبلاً توضیح داده شد، به نظر من بهترین تمرین این است که این را روی دکمه لغو قرار دهید و نه دکمه تأیید. این تضمین می کند که تأیید عمدی است و تصادفی نیست.

بسته شدن با کلید فرار

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

سبک ها

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

یک ظاهر طراحی با ابزارهای باز

برای سرعت بخشیدن به رنگ‌های تطبیقی ​​و هماهنگی کلی طراحی، بی‌شرمانه کتابخانه متغیر CSS خود را Open Props آورده‌ام. علاوه بر متغیرهای رایگان ارائه شده، من یک فایل عادی و چند دکمه را نیز وارد می‌کنم، که Open Props هر دو را به عنوان واردات اختیاری ارائه می‌کند. این واردات به من کمک می‌کند تا روی سفارشی‌سازی دیالوگ و نسخه نمایشی تمرکز کنم، در حالی که نیازی به استایل‌های زیادی برای پشتیبانی از آن و خوب جلوه دادن آن ندارم.

استایل دادن به عنصر <dialog>

مالکیت نمایشگر

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

dialog {
  display: grid;
}

همانطور که در قطعه CSS بالا نشان داده شده است، با تغییر، و در نتیجه مالکیت، ارزش ویژگی نمایشگر، مقدار قابل توجهی از سبک ها به منظور تسهیل تجربه کاربری مناسب نیاز دارند. ابتدا حالت پیش فرض یک دیالوگ بسته می شود. می توانید این حالت را به صورت بصری نشان دهید و از دریافت تعامل با استایل های زیر در گفتگو جلوگیری کنید:

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

اکنون دیالوگ نامرئی است و وقتی باز نیست نمی توان با آن تعامل کرد. بعداً مقداری جاوا اسکریپت را برای مدیریت ویژگی inert در گفتگو اضافه خواهم کرد و اطمینان حاصل می کنم که کاربران صفحه کلید و صفحه خوان نیز نمی توانند به گفتگوی پنهان دسترسی پیدا کنند.

دادن یک تم رنگی تطبیقی ​​به گفتگو

دیالوگ مگا که تم روشن و تاریک را نشان می دهد و رنگ های سطح را نشان می دهد.

در حالی که color-scheme سند شما را به یک تم رنگی تطبیقی ​​که توسط مرورگر برای تنظیمات برگزیده سیستم روشن و تاریک ارائه می‌شود، انتخاب می‌کند، من می‌خواستم بیشتر از آن عنصر گفتگو را سفارشی کنم. Open Props چند رنگ سطحی را ارائه می‌کند که به طور خودکار با اولویت‌های سیستم روشن و تاریک سازگار می‌شوند، شبیه به استفاده از 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;
  }
}

تصویری از ابزارهای توسعه‌یافته که فاصله حاشیه‌ها را پوشانده‌اند    در مگا دیالوگ دسکتاپ و موبایل در حالی که باز است.

موقعیت یابی مینی دیالوگ

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

آن را پاپ کنید

در آخر، کمی استعداد به گفتگو اضافه کنید تا مانند سطح نرمی که در بالای صفحه قرار دارد به نظر برسد. نرمی با گرد کردن گوشه های دیالوگ به دست می آید. عمق با یکی از سایه بان های 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 راه حل معمول من خواهد بود، اما با توجه به مشخصات ، هیچ تاثیری در گفتگو ندارد زیرا یک پورت اسکرول نیست، یعنی یک اسکرول نیست، بنابراین چیزی برای جلوگیری از آن وجود ندارد. می‌توانم از جاوا اسکریپت برای تماشای رویدادهای جدید از این راهنما استفاده کنم، مانند "بسته" و "باز"، و 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>

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

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

نماگرفتی از Chrome Devtools که اطلاعات مربوط به اندازه و پد را برای دکمه بستن سرصفحه پوشش داده است.

استایل دادن به گفتگوی <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 برای حاوی دکمه های عمل برای گفتگو استفاده می شود. برای ایجاد فضای بین دکمه‌ها، از طرح‌بندی فلکس‌باکس بسته‌بندی شده با 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 را روی عناصر منوی پاورقی می پوشاند.

انیمیشن

عناصر دیالوگ اغلب متحرک هستند زیرا وارد پنجره می شوند و از آن خارج می شوند. دادن چند حرکت حمایتی به دیالوگ ها برای این ورودی و خروجی به کاربران کمک می کند تا خود را در جریان جهت دهی کنند.

معمولاً عنصر گفتگو فقط می تواند در داخل متحرک باشد، نه خارج. این به این دلیل است که مرورگر ویژگی display روی عنصر را تغییر می دهد. پیش از این، راهنما نمایشگر را روی شبکه تنظیم می کرد و هرگز آن را روی هیچ تنظیم نمی کرد. این کار توانایی انیمیشن سازی درون و بیرون را باز می کند.

Open Props با بسیاری از انیمیشن‌های فریم کلیدی برای استفاده ارائه می‌شود که هماهنگی را آسان و خوانا می‌کند. در اینجا اهداف انیمیشن و رویکرد لایه ای من در نظر گرفته شده است:

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

جاوا اسکریپت

چیزهای زیادی برای اضافه کردن با جاوا اسکریپت وجود دارد:

// 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') توجه کنید. رویداد فراخوانی می شود و یک رشته ارائه می شود. این رشته می تواند توسط جاوا اسکریپت های دیگر بازیابی شود تا بینش هایی در مورد نحوه بسته شدن گفتگو بدست آید. متوجه خواهید شد که هر بار که تابع را از دکمه‌های مختلف فراخوانی می‌کنم، رشته‌های نزدیک را نیز ارائه کرده‌ام تا زمینه را برای برنامه‌ام در مورد تعامل کاربر فراهم کنم.

افزودن رویدادهای بسته و بسته

عنصر محاوره ای با یک رویداد بسته می آید: هنگامی که تابع dialog 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 که در کامپوننت Building a toast نیز استفاده می‌شود، یک وعده مبتنی بر تکمیل انیمیشن و وعده‌های انتقال برمی‌گرداند. به همین دلیل است که dialogClose یک تابع ناهمگام است. سپس می تواند await وعده بازگشته بماند و با اطمینان به سمت رویداد بسته حرکت کند.

افزودن رویدادهای افتتاحیه و باز شده

افزودن این رویدادها به آسانی نیست زیرا عنصر گفتگوی داخلی مانند بستن رویداد باز را ارائه نمی دهد. من از 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 : این شامل رشته بسته ای است که هنگام فراخوانی رویداد dialog close() ارسال می شود. در رویداد dialogClosed بسیار مهم است که بدانید آیا گفتگو بسته، لغو یا تأیید شده است. اگر تایید شد، اسکریپت سپس مقادیر فرم را می گیرد و فرم را بازنشانی می کند. بازنشانی مفید است به طوری که وقتی دیالوگ دوباره نشان داده شد، خالی و آماده برای ارسال جدید است.

نتیجه گیری

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

بیایید رویکردهای خود را متنوع کنیم و همه راه‌های ساخت در وب را بیاموزیم.

یک نسخه نمایشی ایجاد کنید، پیوندها را برای من توییت کنید ، و من آن را به بخش ریمیکس های انجمن در زیر اضافه می کنم!

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

منابع

،

یک نمای کلی از نحوه ساخت مینی و مگا مدال های سازگار با رنگ، پاسخگو و در دسترس با عنصر <dialog> .

در این پست می‌خواهم نظرات خود را در مورد نحوه ساخت مینی و مگا مدال‌های سازگار با رنگ، واکنش‌گرا و در دسترس با عنصر <dialog> به اشتراک بگذارم. نسخه ی نمایشی را امتحان کنید و منبع را مشاهده کنید !

نمایش دیالوگ های مگا و مینی در تم های روشن و تاریک آنها.

اگر ویدیو را ترجیح می دهید، در اینجا یک نسخه YouTube از این پست وجود دارد:

نمای کلی

عنصر <dialog> برای اطلاعات یا اقدامات متنی درون صفحه عالی است. زمانی را در نظر بگیرید که تجربه کاربر می تواند از یک عملکرد صفحه به جای عملکرد چند صفحه ای بهره مند شود: شاید به این دلیل که فرم کوچک است یا تنها اقدام مورد نیاز کاربر تأیید یا لغو است.

عنصر <dialog> اخیراً در بین مرورگرها پایدار شده است:

پشتیبانی مرورگر

  • کروم: 37.
  • لبه: 79.
  • فایرفاکس: 98.
  • سافاری: 15.4.

منبع

متوجه شدم که این عنصر چند چیز را از دست داده است، بنابراین در این چالش رابط کاربری گرافیکی، مواردی را که انتظار دارم تجربه توسعه دهندگان را اضافه می‌کنم: رویدادهای اضافی، حذف نور، انیمیشن‌های سفارشی، و نوع کوچک و مگا.

نشانه گذاری

ملزومات عنصر <dialog> بسیار کم است. این عنصر به طور خودکار پنهان می شود و دارای سبک هایی برای همپوشانی محتوای شما است.

<dialog>
  …
</dialog>

ما می توانیم این پایه را بهبود بخشیم.

به طور سنتی، یک عنصر گفتگو با یک مودال اشتراک‌گذاری زیادی دارد و اغلب نام‌ها قابل تعویض هستند. من در اینجا آزادی استفاده از عنصر دیالوگ را برای پنجره های کوچک گفتگو (مینی)، و همچنین دیالوگ های تمام صفحه (مگا) گرفتم. من آنها را مگا و مینی نامیدم، با هر دو دیالوگ کمی برای موارد استفاده متفاوت. من یک ویژگی modal-mode اضافه کردم تا بتوانید نوع آن را مشخص کنید:

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

اسکرین شات از دیالوگ های کوچک و مگا در هر دو تم روشن و تاریک.

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

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

عنصر محاوره ای پایه محکمی برای یک عنصر نمای کامل فراهم می کند که می تواند داده ها و تعامل کاربر را جمع آوری کند. این موارد ضروری می توانند تعاملات بسیار جالب و قدرتمندی را در سایت یا برنامه شما ایجاد کنند.

دسترسی

عنصر گفتگو دارای دسترسی داخلی بسیار خوبی است. به جای اضافه کردن این ویژگی ها مانند من معمولاً، بسیاری از آنها در حال حاضر وجود دارند.

بازیابی تمرکز

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

با عنصر گفتگو، این یک رفتار پیش فرض داخلی است:

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

به دام انداختن تمرکز

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

پشتیبانی مرورگر

  • کروم: 102.
  • لبه: 102.
  • فایرفاکس: 112.
  • سافاری: 15.5.

منبع

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

باز کردن و فوکوس خودکار یک عنصر

به طور پیش‌فرض، عنصر محاوره‌ای فوکوس را به اولین عنصر قابل فوکوس در نشانه‌گذاری گفتگو اختصاص می‌دهد. اگر این بهترین عنصری نیست که کاربر به صورت پیش‌فرض آن را انتخاب کند، از ویژگی autofocus استفاده کنید. همانطور که قبلاً توضیح داده شد، به نظر من بهترین تمرین این است که این را روی دکمه لغو قرار دهید و نه دکمه تأیید. این تضمین می کند که تأیید عمدی است و تصادفی نیست.

بسته شدن با کلید فرار

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

سبک ها

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

یک ظاهر طراحی با ابزارهای باز

برای سرعت بخشیدن به رنگ‌های تطبیقی ​​و هماهنگی کلی طراحی، بی‌شرمانه کتابخانه متغیر CSS خود را Open Props آورده‌ام. علاوه بر متغیرهای رایگان ارائه شده، من یک فایل عادی و چند دکمه را نیز وارد می‌کنم، که Open Props هر دو را به عنوان واردات اختیاری ارائه می‌کند. این واردات به من کمک می‌کند تا روی سفارشی‌سازی دیالوگ و نسخه نمایشی تمرکز کنم، در حالی که نیازی به استایل‌های زیادی برای پشتیبانی از آن و خوب جلوه دادن آن ندارم.

استایل دادن به عنصر <dialog>

مالکیت نمایشگر

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

dialog {
  display: grid;
}

همانطور که در قطعه CSS بالا نشان داده شده است، با تغییر، و در نتیجه مالکیت، ارزش ویژگی نمایشگر، مقدار قابل توجهی از سبک ها به منظور تسهیل تجربه کاربری مناسب نیاز دارند. ابتدا حالت پیش فرض یک دیالوگ بسته می شود. می توانید این حالت را به صورت بصری نشان دهید و از دریافت تعامل با استایل های زیر در گفتگو جلوگیری کنید:

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

اکنون دیالوگ نامرئی است و وقتی باز نیست نمی توان با آن تعامل کرد. بعداً مقداری جاوا اسکریپت را برای مدیریت ویژگی inert در گفتگو اضافه خواهم کرد و اطمینان حاصل می کنم که کاربران صفحه کلید و صفحه خوان نیز نمی توانند به گفتگوی پنهان دسترسی پیدا کنند.

دادن یک تم رنگی تطبیقی ​​به گفتگو

دیالوگ مگا که تم روشن و تاریک را نشان می دهد و رنگ های سطح را نشان می دهد.

در حالی که color-scheme سند شما را به یک تم رنگی تطبیقی ​​که توسط مرورگر برای تنظیمات برگزیده سیستم روشن و تاریک ارائه می‌شود، انتخاب می‌کند، من می‌خواستم بیشتر از آن عنصر گفتگو را سفارشی کنم. Open Props چند رنگ سطحی را ارائه می‌کند که به طور خودکار با اولویت‌های سیستم روشن و تاریک سازگار می‌شوند، شبیه به استفاده از 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;
  }
}

تصویر از Devtools فاصله حاشیه ای    در هر دو گفتگوی دسک تاپ و موبایل مگا در حالی که باز است.

موقعیت یابی مینی

هنگام استفاده از نمای بزرگتر مانند رایانه رومیزی ، تصمیم گرفتم که مینی دیالوگ ها را بر روی عنصری که آنها را خوانده اند قرار دهم. برای انجام این کار به JavaScript احتیاج دارم. شما می توانید تکنیکی را که در اینجا استفاده می کنم پیدا کنید ، اما احساس می کنم فراتر از محدوده این مقاله است. بدون جاوا اسکریپت ، گفتگوی مینی در مرکز صفحه ظاهر می شود ، درست مثل گفتگوی مگا.

آن را پاپ کنید

آخر ، مقداری از استعداد را به گفتگو اضافه کنید تا به نظر می رسد یک سطح نرم بسیار بالاتر از صفحه است. نرمی با گرد کردن گوشه های گفتگو حاصل می شود. عمق با یکی از غرفه های سایه دار با دقت ساخته شده با یکی از غرفه های باز دست پیدا می کند:

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

سفارشی کردن عنصر شبه پس زمینه

من تصمیم گرفتم که با پس زمینه بسیار کم کار کنم ، فقط یک اثر تاری با backdrop-filter به گفتگوی مگا اضافه کردم:

پشتیبانی مرورگر

  • کروم: 76.
  • لبه: 79.
  • Firefox: 103.
  • سافاری: 18.

منبع

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

من همچنین به این امید که مرورگرها اجازه می دهند عنصر زمینه پس زمینه را در آینده انتقال دهند ، تصمیم به انتقال در backdrop-filter کردم:

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

تصویر از گفتگوی Mega که یک پس زمینه مبهم از آواتارهای رنگارنگ را پوشانده است.

لوازم جانبی

من این بخش را "اضافی" می نامم زیرا ارتباط بیشتری با نسخه ی نمایشی عنصر گفتگوی من دارد تا اینکه به طور کلی عنصر گفتگو را انجام دهد.

مهار پیمایش

وقتی گفتگو نشان داده شد ، کاربر هنوز قادر به پیمایش صفحه پشت آن است ، که من نمی خواهم:

به طور معمول ، overscroll-behavior راه حل معمول من خواهد بود ، اما طبق مشخصات ، هیچ تاثیری در گفتگو ندارد زیرا این یک درگاه پیمایش نیست ، یعنی این یک پیمایشگر نیست ، بنابراین هیچ چیز برای جلوگیری از آن وجود ندارد. من می توانم از JavaScript برای تماشای رویدادهای جدید از این راهنما استفاده کنم ، مانند "بسته" و "باز شده" ، و Toggle overflow: hidden روی سند ، یا می توانم منتظر بمانم :has() در همه مرورگرها پایدار باشد:

پشتیبانی مرورگر

  • کروم: 105.
  • لبه: 105.
  • Firefox: 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;
}

تصاویر DevTools که اطلاعات طرح شبکه را روی ردیف ها پوشانده است.

استایل گفتگوی <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);
  }
}

تصویر از Devtools Chrome اطلاعات طرح Flexbox را روی عنوان گفتگو پوشش می دهد.

دکمه بستن هدر

از آنجا که نسخه ی نمایشی از دکمه های Open Props استفاده می کند ، دکمه بستن در یک دکمه محور نماد دور مانند SO تنظیم می شود:

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

تصویر از Devtools Chrome Devtools اندازه گیری و اطلاعات بالشتک برای دکمه بستن هدر.

یک ظاهر طراحی گفتگو <article>

عنصر مقاله نقش ویژه ای در این گفتگو دارد: این فضایی است که در مورد یک گفتگوی بلند یا طولانی در نظر گرفته می شود.

برای تحقق این هدف ، عنصر فرم والدین حداکثر خود را برای خود ایجاد کرده است که در صورت بلند شدن ، محدودیت هایی را برای این عنصر مقاله فراهم می کند. تنظیم overflow-y: auto So Scrollbars فقط در صورت لزوم نشان داده می شود ، حاوی پیمایش در داخل آن با 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);
  }
}

تصویر از Devtools 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;
}

تصویر از Devtools Chrome اطلاعات Flexbox را روی عناصر منوی پاورقی قرار می دهد.

انیمیشن

عناصر گفتگو اغلب متحرک هستند زیرا از پنجره وارد و خارج می شوند. ارائه گفتگوها برخی از حرکات حمایتی برای این ورودی و خروج به کاربران کمک می کند تا خود را در جریان قرار دهند.

به طور معمول عنصر گفتگو فقط می تواند در آن متحرک شود ، نه خارج. این امر به این دلیل است که مرورگر ویژگی display را روی عنصر قرار می دهد. پیش از این ، راهنمای صفحه نمایش را به شبکه تنظیم کنید ، و هرگز آن را به هیچ وجه تنظیم نمی کند. این توانایی تحریک داخل و خارج را باز می کند.

Open Props با بسیاری از انیمیشن های KeyFrame برای استفاده همراه است که باعث می شود ارکستراسیون آسان و خوانا باشد. در اینجا اهداف انیمیشن و رویکرد لایه ای که من در نظر گرفته ام آورده شده است:

  1. کاهش حرکت ، انتقال پیش فرض است ، یک کدورت ساده در داخل و خارج محو می شود.
  2. اگر حرکت خوب باشد ، انیمیشن های اسلاید و مقیاس اضافه می شوند.
  3. طرح موبایل پاسخگو برای گفتگوی MEGA به صورت اسلاید تنظیم می شود.

یک انتقال پیش فرض ایمن و معنی دار

در حالی که غرفه های باز با کلیدهای کلیدی برای محو شدن در داخل و خارج همراه هستند ، من این رویکرد لایه بندی شده انتقال را به عنوان پیش فرض با انیمیشن های KeyFrame به عنوان به روزرسانی های بالقوه ترجیح می دهم. پیش از این ما قبلاً دیدگاه گفتگو را با کدورت طراحی کرده ایم ، بسته به ویژگی [open] 1 یا 0 را ارکستر می کند. برای انتقال بین 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;
  }
}

تطبیق انیمیشن خروجی برای موبایل

در اوایل بخش یک ظاهر طراحی شده ، سبک گفتگوی Mega سازگار است تا دستگاه های تلفن همراه بیشتر شبیه به یک برگه عمل باشند ، گویی یک کاغذ کوچک از پایین صفحه نمایش داده شده است و هنوز هم به پایین وصل شده است. Animation Scale Out Exit با این طرح جدید مناسب نیست و ما می توانیم این را با یک نمایش داده های رسانه ای زن و شوهر و برخی از غرفه های باز تطبیق دهیم:

@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 وجود دارد:

// 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')
}

NOTE dialog.close('dismiss') . این رویداد نامیده می شود و یک رشته ارائه می شود. این رشته را می توان توسط سایر جاوا اسکریپت بازیابی کرد تا بینش در مورد نحوه بسته شدن گفتگو دریافت شود. خواهید فهمید که من هر بار که از دکمه های مختلف استفاده می کنم ، رشته های نزدیک را ارائه داده ام تا زمینه ای را برای برنامه خود در مورد تعامل کاربر فراهم کنم.

اضافه کردن رویدادهای بسته و بسته

عنصر دیالوگ با یک رویداد نزدیک همراه است: با 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 یک تابع async است. سپس می تواند await وعده بازگشت و با اطمینان به سمت رویداد بسته حرکت کند.

اضافه کردن رویدادهای افتتاح و افتتاحیه

این رویدادها به راحتی اضافه نمی شوند زیرا عنصر گفتگوی داخلی یک رویداد باز مانند آن را با نزدیک فراهم نمی کند. من از یک MutationObserver استفاده می کنم تا بینش در مورد تغییر ویژگی های گفتگو را ارائه دهم. در این ناظر ، من به دنبال تغییر در ویژگی باز هستم و بر این اساس رویدادهای سفارشی را مدیریت می کنم.

مشابه نحوه شروع وقایع بسته شدن و بسته ، دو رویداد جدید به نام opening and 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')
}

در مورد مشکل جلوگیری از انیمیشن های Keyframe در بار صفحه در اینجا بیشتر بدانید.

همه با هم

در اینجا 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()
  }
}

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

NOTE dialog.returnValue : این شامل رشته نزدیک است که هنگام close() که از آن خوانده می شود ، عبور می کند. در رویداد dialogClosed بسیار مهم است که بدانید آیا این گفتگو بسته شده ، لغو شده یا تأیید شده است. در صورت تأیید ، اسکریپت سپس مقادیر فرم را می گیرد و فرم را دوباره تنظیم می کند. تنظیم مجدد مفید است به گونه ای که وقتی دوباره گفتگو نشان داده می شود ، خالی و آماده برای ارسال جدید است.

نتیجه گیری

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

بیایید رویکردهای خود را متنوع کنیم و همه راه‌های ساخت در وب را بیاموزیم.

یک نسخه نمایشی ایجاد کنید، پیوندها را برای من توییت کنید ، و من آن را به بخش ریمیکس های انجمن در زیر اضافه می کنم!

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

منابع