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

ภาพรวมพื้นฐานเกี่ยวกับวิธีสร้างคอมโพเนนต์ 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

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

ข้อความแจ้ง 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 เพื่อจัดระเบียบการสร้าง การเพิ่ม และการทำลายข้อความโทสต์ตามเหตุการณ์ของผู้ใช้ ประสบการณ์ของนักพัฒนาซอฟต์แวร์คอมโพเนนต์ข้อความโทสต์ควรมีเพียงเล็กน้อยและเริ่มต้นใช้งานได้ง่าย ดังนี้

import Toast from './toast.js'

Toast('My first toast')

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

เมื่อโมดูลข้อความแจ้งแสดงผลจาก JavaScript จะต้องสร้างคอนเทนเนอร์ข้อความแจ้งแสดงผลและเพิ่มลงในหน้า เราเลือกที่จะเพิ่มองค์ประกอบก่อน body ซึ่งจะทำให้z-indexไม่มีปัญหาการซ้อนกันเนื่องจากคอนเทนเนอร์อยู่เหนือคอนเทนเนอร์ขององค์ประกอบเนื้อหาทั้งหมด

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

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

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

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

const Toaster = init()

การสร้างองค์ประกอบ HTML ของ 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
}

การจัดการข้อความโทสต์ 1 หรือหลายรายการ

ตอนนี้ 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 แนวคิดคือการคำนวณความแตกต่างของตําแหน่งคอนเทนเนอร์ก่อนและหลังเพิ่มข้อความแจ้งแบบใหม่ ลองคิดว่าเหมือนกับการทำเครื่องหมายตำแหน่งของ Toaster ในปัจจุบัน ตำแหน่งของที่จะนำไป แล้วแสดงภาพภาพเคลื่อนไหวจากจุดที่ Toaster อยู่

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() หมายความว่าโค้ดบรรทัดถัดไปจะนำองค์ประกอบออกได้อย่างมั่นใจ และจะถือว่าข้อความโทสต์สิ้นสุดวงจรแล้ว สุดท้าย การเรียกใช้ 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')
}

บทสรุป

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

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

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