ساختن یک جزء نان تست

یک نمای کلی از نحوه ساخت یک جزء نان تست سازگار و در دسترس.

در این پست می‌خواهم فکری در مورد نحوه ساخت یک جزء نان تست به اشتراک بگذارم. نسخه ی نمایشی را امتحان کنید.

نسخه ی نمایشی

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

بررسی اجمالی

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

فعل و انفعالات

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

نشانه گذاری

عنصر <output> انتخاب خوبی برای نان تست است زیرا به صفحه خوان ها اعلام شده است. HTML صحیح پایه ای امن را برای ما فراهم می کند تا با جاوا اسکریپت و CSS آن را تقویت کنیم و جاوا اسکریپت زیادی وجود خواهد داشت.

یک نان تست

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

ظرف رابط کاربری گرافیکی

ظرف نان تست تمام کارهای چیدمان را برای ارائه نان تست انجام می دهد. این در viewport fixed است و از inset ویژگی منطقی استفاده می‌کند تا مشخص کند به کدام لبه‌ها پین شود، به‌علاوه کمی padding از همان لبه block-end .

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

عکس صفحه با اندازه جعبه DevTools و بالشتک روی یک عنصر .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 و یک درخواست رسانه ترجیحی بدهید.

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

انیمیشن

یک نان تست جدید با ورود به صفحه باید خود را با یک انیمیشن نشان دهد. تطبیق کاهش حرکت با تنظیم مقادیر 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;
  }
}

جاوا اسکریپت

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

import Toast from './toast.js'

Toast('My first toast')

ایجاد گروه نان تست و نان تست

هنگامی که ماژول نان تست از جاوا اسکریپت بارگیری می شود، باید یک ظرف نان تست ایجاد کرده و آن را به صفحه اضافه کند. من انتخاب کردم که عنصر را قبل از body اضافه کنم، این امر باعث می‌شود مشکلات انباشتگی z-index بعید باشد زیرا ظرف برای همه عناصر بدنه بالای ظرف قرار دارد.

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

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

اسکرین شات از گروه نان تست بین تگ های سر و بدن.

تابع init() به صورت داخلی به ماژول فراخوانی می شود و عنصر را به عنوان Toaster ذخیره می کند:

const Toaster = init()

ایجاد عنصر HTML Toast با تابع createToast() انجام می شود. این تابع به مقداری متن برای نان تست نیاز دارد، یک عنصر <output> ایجاد می کند، آن را با برخی کلاس ها و ویژگی ها تزئین می کند، متن را تنظیم می کند و گره را برمی گرداند.

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

  return node
}

مدیریت یک یا چند نان تست

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

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 اضافه می کند: animate in, 3s صبر کنید, animate out. flipToast() زمانی فراخوانی می شود که نان تست های موجود وجود داشته باشد، با استفاده از تکنیکی به نام 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 طرح را ارتقا می دهد. هنگامی که یک نان تست جدید اضافه می شود، شبکه آن را در ابتدا قرار می دهد و آن را با بقیه فاصله می دهد. در همین حال، یک انیمیشن وب برای متحرک سازی ظرف از موقعیت قدیمی استفاده می شود.

قرار دادن تمام جاوا اسکریپت با هم

هنگامی که 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() است. از آنجایی که من از چندین انیمیشن فریم کلیدی برای نان تست استفاده کردم، برای اینکه مطمئن باشم همه آنها به پایان رسیده اند، باید هر کدام از آنها از جاوا اسکریپت درخواست شود و هر یک از وعده های finished آنها برای تکمیل رعایت شود. allSettled این کار را برای ما انجام می دهد و پس از تحقق همه وعده هایش، خود را کامل حل می کند. استفاده از await Promise.allSettled() به این معنی است که خط بعدی کد می تواند با اطمینان عنصر را حذف کند و فرض کند که toast چرخه عمر خود را کامل کرده است. در نهایت، resolve() تعهد سطح بالای Toast را برآورده می‌کند، بنابراین توسعه‌دهندگان می‌توانند پس از نمایش نان تست، آن را پاکسازی کنند یا کارهای دیگری انجام دهند.

export default Toast

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

با استفاده از مؤلفه Toast

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

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

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

import Toast from './toast.js'

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

نتیجه

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

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

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