ภาพรวมพื้นฐานเกี่ยวกับวิธีสร้างคอมโพเนนต์ 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;
}
นอกจากการจัดตําแหน่งภายในวิวพอร์ตแล้ว คอนเทนเนอร์ของข้อความแจ้งเตือนยังถือเป็นคอนเทนเนอร์ตารางกริดที่สามารถจัดแนวและจัดวางข้อความแจ้งเตือนได้ รายการจะอยู่ตรงกลางเป็นกลุ่มด้วย justify-content
และแยกแต่ละรายการให้อยู่กึ่งกลางด้วย justify-items
ใส่ gap
เล็กน้อยเพื่อไม่ให้ขนมปังประกบกัน
.gui-toast-group {
display: grid;
justify-items: center;
justify-content: center;
gap: 1vh;
}
ข้อความแจ้ง 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;
}
รูปแบบ
เมื่อตั้งค่าเลย์เอาต์และตำแหน่งแล้ว ให้เพิ่ม 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
}
ฟังก์ชัน 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')
}
บทสรุป
ตอนนี้คุณรู้วิธีที่เราทำแล้ว คุณจะทำอย่างไรบ้าง 🙂
มาลองใช้แนวทางที่หลากหลายและดูวิธีทั้งหมดในการสร้างบนเว็บกัน สร้างเดโม แล้วทวีตลิงก์มาหาเรา เราจะเพิ่มลงในส่วนรีมิกซ์ของชุมชนด้านล่าง
รีมิกซ์ของชุมชน
- @_developit สำหรับ HTML/CSS/JS: เดโมและโค้ด
- Joost van der Schee กับ HTML/CSS/JS: เดโมและโค้ด