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

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

ในโพสต์นี้ ฉันอยากจะแชร์แนวคิดเกี่ยวกับวิธีสร้างคอมโพเนนต์ 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;
}

ภาพหน้าจอที่มีขนาดกล่องและระยะห่างจากขอบของเครื่องมือสำหรับนักพัฒนาเว็บซ้อนทับบนองค์ประกอบ .gui-toast-container

นอกจากจะวางตำแหน่งภายในวิวพอร์ตแล้ว คอนเทนเนอร์ข้อความป๊อปอัปยังเป็น คอนเทนเนอร์กริดที่จัดแนวและกระจายข้อความป๊อปอัปได้ด้วย รายการจะอยู่ตรงกลางเป็นกลุ่มด้วย justify-content และอยู่ตรงกลางแยกกันด้วย justify-items ใส่gapเล็กน้อยเพื่อไม่ให้ขนมปังปิ้งสัมผัสกัน

.gui-toast-group {
  display: grid;
  justify-items: center;
  justify-content: center;
  gap: 1vh;
}

ภาพหน้าจอที่มีการวางซ้อนกริด CSS บนกลุ่มข้อความแจ้ง โดยคราวนี้
ไฮไลต์ช่องว่างและช่องว่างระหว่างองค์ประกอบย่อยของข้อความแจ้ง

ข้อความ Toast ของ 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 ที่ช่วยในการปรับให้เข้ากับการตั้งค่าและการโต้ตอบของผู้ใช้

คอนเทนเนอร์ข้อความป๊อปอัป

Toast ไม่มีการโต้ตอบ การแตะหรือปัด Toast จะไม่ทําอะไร แต่ ปัจจุบัน Toast ใช้เหตุการณ์ของเคอร์เซอร์ ป้องกันไม่ให้ข้อความป๊อปอัปขโมย การคลิกด้วย CSS ต่อไปนี้

.gui-toast-group {
  pointer-events: none;
}

ข้อความ Toast ของ GUI

กำหนดธีมแบบปรับได้เป็นสว่างหรือมืดให้กับข้อความแจ้งด้วยพร็อพเพอร์ตี้ที่กำหนดเอง, HSL และ Media Query ของค่ากำหนด

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

จากนั้นองค์ประกอบ Toast จะตั้งค่าตัวแปรและจัดระเบียบคีย์เฟรม

.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 ของข้อความแจ้งทำได้ด้วยฟังก์ชัน 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() ฟังก์ชันจะจัดระเบียบการจัดการข้อความแจ้งหนึ่งรายการหรือหลายรายการ โดยเริ่มจากการตรวจสอบจำนวนข้อความแจ้งและดูว่าการเคลื่อนไหวเป็นไปอย่างราบรื่นหรือไม่ จากนั้นใช้ข้อมูลนี้เพื่อต่อท้ายข้อความแจ้งหรือสร้างภาพเคลื่อนไหวที่น่าสนใจ เพื่อให้ข้อความแจ้งอื่นๆ ปรากฏขึ้นเพื่อ "หลีกทาง" ให้ข้อความแจ้งใหม่

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

การรวม 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 จะทำงานให้เรา โดยจะถือว่าเสร็จสมบูรณ์เมื่อทำตามสัญญา ทั้งหมดแล้ว การใช้ await Promise.allSettled() หมายความว่าโค้ดบรรทัดถัดไป สามารถนำองค์ประกอบออกได้อย่างมั่นใจและถือว่า Toast ได้สิ้นสุด วงจรแล้ว สุดท้าย การเรียกใช้ resolve() จะทำให้คำมั่นสัญญาของ Toast ระดับสูงเป็นจริง เพื่อให้ นักพัฒนาแอปสามารถล้างข้อมูลหรือทำงานอื่นๆ ได้เมื่อ Toast แสดงขึ้น

export default Toast

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

การใช้คอมโพเนนต์ข้อความป๊อปอัป

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

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

หากนักพัฒนาแอปต้องการล้างข้อมูลหรือทำอะไรก็ตามหลังจากที่ข้อความป๊อปอัปแสดงขึ้น นักพัฒนาแอปจะใช้ async และ await ได้

import Toast from './toast.js'

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

บทสรุป

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

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

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