Tạo thành phần thông báo ngắn

Thông tin tổng quan cơ bản về cách tạo một thành phần thông báo ngắn thích ứng và hỗ trợ tiếp cận.

Trong bài đăng này, tôi muốn chia sẻ suy nghĩ về cách tạo thành phần thông báo ngắn. Xem bản minh hoạ.

Bản minh hoạ

Nếu bạn thích xem video, hãy xem phiên bản video của bài đăng này trên YouTube:

Tổng quan

Thông báo ngắn là thông báo ngắn không tương tác, thụ động và không đồng bộ dành cho người dùng. Thông thường, chúng được dùng làm mẫu phản hồi giao diện để thông báo cho người dùng về kết quả của một hành động.

Lượt tương tác

Thông báo ngắn không giống như thông báo, cảnh báolời nhắc vì chúng không có tính tương tác, đồng thời không bị loại bỏ hay duy trì. Thông báo dành cho thông tin quan trọng hơn, thông báo đồng bộ yêu cầu tương tác hoặc thông báo cấp hệ thống (thay vì cấp trang). Thông báo ngắn mang tính thụ động hơn so với các chiến lược thông báo khác.

Markup (note: đây là tên ứng dụng)

Phần tử <output> là một lựa chọn phù hợp cho thông báo ngắn vì thông báo này được thông báo cho trình đọc màn hình. HTML chính xác cung cấp một nền tảng an toàn để chúng ta nâng cao bằng JavaScript và CSS, và sẽ có rất nhiều JavaScript.

Một thông báo ngắn

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

Bạn có thể thêm nhiều thành phần hơn bằng cách thêm role="status". Điều này cung cấp một phương án dự phòng nếu trình duyệt không cung cấp cho các phần tử <output> vai trò ngầm ẩn theo thông số kỹ thuật.

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

Vùng chứa thông báo ngắn

Có thể hiển thị nhiều thông báo ngắn cùng một lúc. Để điều phối nhiều thông báo ngắn, bạn cần sử dụng một vùng chứa. Vùng chứa này cũng xử lý vị trí của thông báo ngắn trên màn hình.

<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>

Bố cục

Tôi đã chọn ghim thông báo ngắn vào inset-block-end của khung nhìn và nếu thêm thông báo ngắn khác, chúng sẽ xếp chồng từ cạnh màn hình đó.

Vùng chứa GUI

Vùng chứa thông báo ngắn sẽ thực hiện toàn bộ bố cục để trình bày thông báo ngắn. Thuộc tính này là fixed đối với khung nhìn và sử dụng thuộc tính logic inset để chỉ định cạnh nào sẽ được ghim, cùng với một chút padding từ cùng một cạnh block-end.

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

Ảnh chụp màn hình có kích thước hộp và khoảng đệm DevTools phủ lên phần tử .gui-toast-container.

Ngoài việc tự định vị trong khung nhìn, vùng chứa thông báo ngắn là một vùng chứa lưới có thể căn chỉnh và phân phối thông báo ngắn. Các mục được căn giữa dưới dạng một nhóm bằng justify-content và được căn giữa riêng lẻ bằng justify-items. Cho một chút gap để thông báo ngắn không chạm vào.

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

Ảnh chụp màn hình có lớp phủ lưới CSS trên nhóm thông báo ngắn, lần này làm nổi bật không gian và khoảng trống giữa các phần tử con của thông báo ngắn.

Thông báo ngắn trên GUI

Một thông báo ngắn riêng lẻ có một số padding, một số góc mềm hơn với border-radius và hàm min() để hỗ trợ việc điều chỉnh kích thước trên thiết bị di động và máy tính. Kích thước thích ứng trong CSS sau đây ngăn thông báo ngắn hiển thị rộng hơn 90% khung nhìn hoặc 25ch.

.gui-toast {
  max-inline-size: min(25ch, 90vw);
  padding-block: .5ch;
  padding-inline: 1ch;
  border-radius: 3px;
  font-size: 1rem;
}

Ảnh chụp màn hình của một phần tử .gui-toast, với khoảng đệm và bán kính đường viền hiển thị.

Kiểu

Khi đã thiết lập bố cục và vị trí, hãy thêm CSS để giúp điều chỉnh các chế độ cài đặt và hoạt động tương tác của người dùng.

Dụng cụ đựng bánh mì

Thông báo ngắn không có tính tương tác, việc nhấn hoặc vuốt vào thông báo ngắn sẽ không làm gì cả, nhưng hiện tại, thông báo ngắn sẽ sử dụng các sự kiện con trỏ. Ngăn thông báo ngắn đánh cắp lượt nhấp bằng CSS sau.

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

Thông báo ngắn trên GUI

Đặt giao diện thích ứng sáng hoặc tối cho thông báo ngắn bằng các thuộc tính tuỳ chỉnh, HSL và truy vấn nội dung đa phương tiện ưu tiên.

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

Hoạt ảnh

Một thông báo ngắn mới sẽ hiển thị bằng ảnh động khi xuất hiện trên màn hình. Để điều chỉnh chuyển động được thực hiện, bạn có thể đặt giá trị translate thành 0 theo mặc định, nhưng cập nhật giá trị chuyển động thành độ dài trong truy vấn phương tiện ưu tiên chuyển động . Mọi người đều thấy một số ảnh động, nhưng chỉ một số người dùng thấy thông báo ngắn di chuyển một khoảng cách.

Sau đây là các khung hình chính dùng cho ảnh động thông báo ngắn. CSS sẽ kiểm soát việc vào, chờ và thoát của thông báo ngắn, tất cả trong một ảnh động.

@keyframes fade-in {
  from { opacity: 0 }
}

@keyframes fade-out {
  to { opacity: 0 }
}

@keyframes slide-in {
  from { transform: translateY(var(--_travel-distance, 10px)) }
}

Sau đó, phần tử thông báo ngắn sẽ thiết lập các biến và điều phối các khung hình chính.

.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

Khi đã có sẵn các kiểu và HTML hỗ trợ tiếp cận trình đọc màn hình, bạn cần có JavaScript để điều phối việc tạo, thêm và huỷ thông báo ngắn dựa trên các sự kiện của người dùng. Trải nghiệm của nhà phát triển về thành phần thông báo ngắn phải ở mức tối thiểu và dễ bắt đầu, như sau:

import Toast from './toast.js'

Toast('My first toast')

Tạo nhóm thông báo ngắn và thông báo ngắn

Khi mô-đun thông báo ngắn tải từ JavaScript, mô-đun thông báo ngắn phải tạo một vùng chứa thông báo ngắn và thêm vào trang. Tôi đã chọn thêm phần tử trước body, điều này sẽ khiến các vấn đề về việc xếp chồng z-index khó xảy ra vì vùng chứa nằm phía trên vùng chứa của tất cả phần tử body.

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

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

Ảnh chụp màn hình nhóm thông báo ngắn giữa thẻ head và body.

Hàm init() được gọi nội bộ cho mô-đun, lưu trữ phần tử dưới dạng Toaster:

const Toaster = init()

Bạn có thể tạo phần tử HTML của thông báo ngắn bằng hàm createToast(). Hàm này yêu cầu một số văn bản cho thông báo ngắn, tạo một phần tử <output>, trang trí phần tử đó bằng một số lớp và thuộc tính, đặt văn bản và trả về nút.

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

  return node
}

Quản lý một hoặc nhiều thông báo ngắn

Giờ đây, JavaScript sẽ thêm một vùng chứa vào tài liệu để chứa thông báo ngắn và sẵn sàng thêm thông báo ngắn đã tạo. Hàm addToast() điều phối việc xử lý một hoặc nhiều thông báo ngắn. Trước tiên, hãy kiểm tra số lượng thông báo ngắn và xem liệu chuyển động có ổn không, sau đó sử dụng thông tin này để thêm thông báo ngắn hoặc tạo một số ảnh động bắt mắt để các thông báo ngắn khác xuất hiện "dành chỗ" cho thông báo ngắn mới.

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

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

Khi thêm thông báo ngắn đầu tiên, Toaster.appendChild(toast) sẽ thêm thông báo ngắn vào trang kích hoạt ảnh động CSS: ảnh động vào, đợi 3s, ảnh động ra. flipToast() được gọi khi có sẵn thông báo ngắn, sử dụng kỹ thuật có tên là FLIP của Paul Lewis. Ý tưởng là tính toán sự khác biệt về vị trí của vùng chứa, trước và sau khi thêm thông báo ngắn mới. Hãy coi đó là việc đánh dấu vị trí hiện tại của Toaster, vị trí sẽ xuất hiện, sau đó tạo ảnh động từ vị trí hiện tại đến vị trí sẽ xuất hiện.

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',
  })
}

Lưới CSS sẽ nâng bố cục lên. Khi thêm một thông báo ngắn mới, lưới sẽ đặt thông báo đó vào đầu và không gian thông báo ngắn với các thông báo ngắn khác. Trong khi đó, ảnh động trên web được dùng để tạo ảnh động cho vùng chứa từ vị trí cũ.

Kết hợp tất cả JavaScript

Khi Toast('my first toast') được gọi, một thông báo ngắn sẽ được tạo, thêm vào trang (thậm chí có thể là vùng chứa được tạo ảnh động cho phù hợp với thông báo ngắn mới), một hứa hẹn sẽ được trả về và thông báo ngắn đã tạo sẽ được xem để hoàn thành ảnh động CSS (3 ảnh động khung hình chính) để có độ phân giải như dự kiến.

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

Tôi cảm thấy phần khó hiểu của mã này nằm trong hàm Promise.allSettled() và ánh xạ toast.getAnimations(). Vì tôi đã sử dụng nhiều ảnh động khung hình chính cho thông báo ngắn, để chắc chắn rằng tất cả ảnh động đó đã hoàn tất, mỗi ảnh động phải được yêu cầu từ JavaScript và mỗi lời hứa finished của chúng được quan sát để hoàn tất. allSettled sẽ thực hiện việc đó cho chúng ta, tự giải quyết khi tất cả các lời hứa của nó đã được thực hiện. Việc sử dụng await Promise.allSettled() có nghĩa là dòng mã tiếp theo có thể tự tin xoá phần tử và giả định thông báo ngắn đã hoàn tất vòng đời. Cuối cùng, việc gọi resolve() sẽ thực hiện lời hứa cấp cao của Toast để nhà phát triển có thể dọn dẹp hoặc làm công việc khác sau khi thông báo ngắn xuất hiện.

export default Toast

Cuối cùng, hàm Toast được xuất từ mô-đun để các tập lệnh khác nhập và sử dụng.

Sử dụng thành phần Thông báo ngắn

Bạn có thể sử dụng thông báo ngắn hoặc trải nghiệm dành cho nhà phát triển của thông báo ngắn bằng cách nhập hàm Toast và gọi hàm đó bằng một chuỗi thông báo.

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

Nếu nhà phát triển muốn dọn dẹp công việc hoặc bất kỳ việc gì khác, sau khi thông báo ngắn xuất hiện, họ có thể sử dụng tính năng không đồng bộ và chờ.

import Toast from './toast.js'

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

Kết luận

Giờ bạn đã biết cách tôi thực hiện điều đó, bạn sẽ làm cách nào‽ 🙂

Hãy đa dạng hoá các phương pháp và tìm hiểu tất cả các cách xây dựng trên web. Hãy tạo bản minh hoạ, gửi đường liên kết cho tôi trên Twitter và tôi sẽ thêm bản minh hoạ đó vào phần phối lại của cộng đồng ở bên dưới!

Bản phối lại của cộng đồng