การสร้างคอมโพเนนต์โทสต์

ภาพรวมพื้นฐานเกี่ยวกับวิธีสร้างคอมโพเนนต์ Toast ที่ปรับเปลี่ยนได้และเข้าถึงได้

ในโพสต์นี้ เราต้องการแชร์แนวคิดเกี่ยวกับวิธีสร้างคอมโพเนนต์ Toast ลองใช้เดโม

สาธิต

หากต้องการดูวิดีโอ โปรดดูโพสต์เวอร์ชัน YouTube ที่นี่

ภาพรวม

ข้อความแจ้งเป็นข้อความสั้นๆ ที่ไม่โต้ตอบ ไม่ได้โต้ตอบ และทำงานแบบไม่พร้อมกันสำหรับผู้ใช้ โดยทั่วไปแล้ว รูปแบบเหล่านี้จะใช้เป็นรูปแบบการแสดงผลผลลัพธ์ของอินเทอร์เฟซเพื่อแจ้งให้ผู้ใช้ทราบเกี่ยวกับผลลัพธ์ของการดำเนินการ

การโต้ตอบ

ข้อความป๊อปอัปแตกต่างจากการแจ้งเตือน การแจ้งเตือน และพรอมต์ตรงที่ข้อความป๊อปอัปเป็นแบบไม่โต้ตอบ ไม่ได้มีไว้เพื่อปิดหรือแสดงต่อไป การแจ้งเตือนมีไว้สำหรับข้อมูลที่สำคัญกว่า การรับส่งข้อความแบบซิงค์ที่ต้องมีการโต้ตอบ หรือข้อความระดับระบบ (ไม่ใช่ระดับหน้าเว็บ) ข้อความแจ้งเป็นกลยุทธ์การแจ้งเตือนแบบแสดงตัวตนน้อยกว่ากลยุทธ์อื่นๆ

Markup

องค์ประกอบ <output> เป็นตัวเลือกที่ดีสำหรับข้อความแจ้งเนื่องจากจะประกาศไปยังโปรแกรมอ่านหน้าจอ HTML ที่ถูกต้องเป็นฐานที่ปลอดภัยสำหรับเราในการปรับปรุงด้วย JavaScript และ CSS ซึ่งจะมี JavaScript จำนวนมาก

ข้อความโทสต์

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

คุณสามารถเพิ่มความหลากหลายได้ด้วยการใส่ role="status" การดำเนินการนี้จะเป็นค่าสำรองในกรณีที่เบราว์เซอร์ไม่ได้กำหนดบทบาทโดยนัยให้กับองค์ประกอบ <output> ตามข้อกำหนด

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

ภาชนะใส่ขนมปังปิ้ง

ระบบจะแสดงข้อความแจ้งได้มากกว่า 1 รายการพร้อมกัน ระบบจะใช้คอนเทนเนอร์เพื่อจัดระเบียบข้อความแจ้งหลายรายการ คอนเทนเนอร์นี้จะจัดการตำแหน่งของข้อความแจ้งบนหน้าจอด้วย

<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ของวิวพอร์ต และหากเพิ่มข้อความแจ้งอีก ข้อความแจ้งจะซ้อนกันจากขอบหน้าจอนั้น

คอนเทนเนอร์ GUI

คอนเทนเนอร์ของข้อความแจ้งจะจัดวางเลย์เอาต์ทั้งหมดเพื่อแสดงข้อความแจ้ง กับวิวพอร์ต และใช้พร็อพเพอร์ตี้ตรรกะ inset เพื่อระบุขอบที่จะปักหมุดไว้ รวมถึง padding เล็กน้อยจากขอบ block-end เดียวกันfixed

.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 วางซ้อนบนกลุ่มข้อความแจ้งเตือน ซึ่งครั้งนี้จะไฮไลต์พื้นที่ว่างและช่องว่างระหว่างองค์ประกอบย่อยของข้อความแจ้งเตือน

ข้อความแจ้ง GUI

ข้อความแจ้งแต่ละรายการมี 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;
}

ข้อความแจ้ง GUI

กำหนดธีมแบบปรับได้สว่างหรือมืดให้กับข้อความแจ้งด้วยพร็อพเพอร์ตี้ที่กำหนดเอง, 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;
  }
}

JavaScript

เมื่อเตรียมสไตล์และ HTML ที่โปรแกรมอ่านหน้าจอเข้าถึงได้แล้ว คุณต้องใช้ JavaScript เพื่อควบคุมการสร้าง การเพิ่ม และการทำลายข้อความแจ้งตามเหตุการณ์ของผู้ใช้ ประสบการณ์การใช้งานคอมโพเนนต์ Toast ของนักพัฒนาแอปควรเรียบง่ายและเริ่มต้นใช้งานได้ง่ายๆ ดังนี้

import Toast from './toast.js'

Toast('My first toast')

การสร้างกลุ่มและข้อความแสดงผล

เมื่อโมดูล Toast โหลดจาก JavaScript จะต้องสร้างคอนเทนเนอร์ 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
}

ภาพหน้าจอของกลุ่มข้อความแจ้งระหว่างแท็ก head กับแท็ก body

ฟังก์ชัน init() จะเรียกใช้ภายในโมดูล โดยเก็บองค์ประกอบไว้เป็น Toaster ดังนี้

const Toaster = init()

การสร้างองค์ประกอบ HTML ของข้อความแจ้งเป็นข้อความสั้นๆ ทำได้โดยใช้ฟังก์ชัน createToast() ฟังก์ชันนี้ต้องใช้ข้อความสำหรับข้อความแจ้ง สร้างองค์ประกอบ <output> ตกแต่งด้วยคลาสและแอตทริบิวต์บางอย่าง ตั้งค่าข้อความ และแสดงผลโหนด

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

  return node
}

การจัดการข้อความแจ้งแบบรายการเดียวหรือหลายรายการ

ตอนนี้ JavaScript จะเพิ่มคอนเทนเนอร์ลงในเอกสารเพื่อเก็บข้อความแจ้งและพร้อมที่จะเพิ่มข้อความแจ้งที่สร้างขึ้น ฟังก์ชัน addToast() จะจัดการการแสดงข้อความแจ้ง 1 รายการหรือหลายรายการ ก่อนอื่นให้ตรวจสอบจำนวนข้อความแจ้งและดูว่าภาพเคลื่อนไหวใช้ได้ไหม จากนั้นใช้ข้อมูลนี้เพื่อเพิ่มข้อความแจ้งต่อท้ายหรือทำภาพเคลื่อนไหวเก๋ๆ เพื่อให้ข้อความแจ้งอื่นๆ ปรากฏขึ้นเพื่อ "เพิ่มพื้นที่" สำหรับข้อความแจ้งใหม่

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

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

เมื่อเพิ่มข้อความแจ้งแบบกลมแรก Toaster.appendChild(toast) จะเพิ่มข้อความแจ้งแบบกลมลงในหน้าเว็บเพื่อเรียกให้ภาพเคลื่อนไหว CSS ทำงาน โดยเริ่มจากภาพเคลื่อนไหวเข้ามา รอ 3s แล้วภาพเคลื่อนไหวออกไป flipToast() จะเรียกใช้เมื่อมีข้อความแจ้งอยู่แล้ว โดยใช้เทคนิคที่เรียกว่า FLIP โดย Paul Lewis แนวคิดคือการคำนวณความแตกต่างของตําแหน่งคอนเทนเนอร์ก่อนและหลังเพิ่มข้อความแจ้งแบบใหม่ ลองนึกภาพว่าเป็นการทําเครื่องหมายตําแหน่งของที่ปิ้งขนมปัง ณ ปัจจุบัน ตําแหน่งที่กําลังจะไป แล้วแสดงภาพเคลื่อนไหวจากตําแหน่งที่กําลังจะไปไปยังตําแหน่งที่กําลังอยู่

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

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

  // LAST
  const last = Toaster.offsetHeight

  // INVERT
  const invert = last - first

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

ตาราง CSS จะยกระดับเลย์เอาต์ เมื่อเพิ่มข้อความแจ้งใหม่ ตารางกริดจะวางข้อความแจ้งนั้นไว้ที่จุดเริ่มต้นและเว้นวรรคไว้กับข้อความแจ้งอื่นๆ ในระหว่างนี้ ระบบจะใช้ภาพเคลื่อนไหวบนเว็บเพื่อแสดงภาพเคลื่อนไหวของคอนเทนเนอร์จากตําแหน่งเดิม

รวม JavaScript ทั้งหมดเข้าด้วยกัน

เมื่อเรียก Toast('my first toast') ระบบจะสร้างและเพิ่มข้อความแจ้งไปยังหน้าเว็บ (อาจมีการทำให้คอนเทนเนอร์เคลื่อนไหวเพื่อรองรับข้อความแจ้งใหม่) ระบบจะแสดงสัญญาและตรวจสอบข้อความแจ้งที่สร้างขึ้นเพื่อดูว่าภาพเคลื่อนไหว CSS เสร็จสมบูรณ์หรือไม่ (ภาพเคลื่อนไหวคีย์เฟรม 3 รายการ) เพื่อแก้ไขสัญญา

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() เนื่องจากเราใช้ภาพเคลื่อนไหวคีย์เฟรมหลายรายการสำหรับข้อความแจ้ง ให้ขอแต่ละรายการจาก JavaScript และตรวจสอบfinished สัญญาของรายการแต่ละรายการเพื่อให้เสร็จสมบูรณ์ allSettled does that work for us, resolving itself as complete once all of its promises have been fulfilled. การใช้ await Promise.allSettled() หมายความว่าบรรทัดถัดไปของโค้ดจะนำองค์ประกอบออกได้อย่างมั่นใจและถือว่า Toast ทำงานตามวงจรเสร็จสมบูรณ์แล้ว สุดท้าย การเรียก resolve() จะเป็นไปตามสัญญาระดับสูงของ Toast เพื่อให้นักพัฒนาแอปสามารถล้างข้อมูลหรือทํางานอื่นๆ ได้เมื่อ Toast แสดงแล้ว

export default Toast

สุดท้าย ระบบจะส่งออกฟังก์ชัน Toast จากโมดูลเพื่อให้สคริปต์อื่นๆ นําเข้าและใช้

การใช้คอมโพเนนต์ Toast

การใช้ Toast หรือประสบการณ์การใช้งาน Toast ของนักพัฒนาแอปทำได้โดยการนําเข้าฟังก์ชัน Toast และเรียกใช้ด้วยสตริงข้อความ

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

หากนักพัฒนาแอปต้องการทํางานทำความสะอาดหรือดำเนินการอื่นๆ หลังจากแสดงข้อความ Toast แล้ว ก็สามารถใช้ async และ await ได้

import Toast from './toast.js'

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

บทสรุป

ตอนนี้คุณรู้วิธีที่เราทำแล้ว คุณจะทำอย่างไรบ้าง 🙂

มาลองใช้แนวทางที่หลากหลายและดูวิธีทั้งหมดในการสร้างบนเว็บกัน สร้างเดโม แล้วทวีตลิงก์มาหาเรา เราจะเพิ่มลงในส่วนรีมิกซ์ของชุมชนด้านล่าง

รีมิกซ์ของชุมชน