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. Hãy dùng thử 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; chúng không được đóng hoặc tồn tại. 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 cơ sở an toàn để chúng ta nâng cao bằng JavaScript và CSS, và sẽ có rất nhiều JavaScript.

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 thực hiện mọi công việc liên quan đến bố cục để hiển thị thông báo ngắn. Thuộc tính này là fixed cho 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. Thêm 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à một hàm min() để hỗ trợ định cỡ cho 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.

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

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

Thông báo ngắn mới sẽ xuất hiện cùng với ảnh động khi vào màn hình. Bạn có thể điều chỉnh chuyển động giảm bằng cách đặt giá trị translate thành 0 theo mặc định, nhưng cập nhật giá trị chuyển động thành một độ dài trong truy vấn nội dung đa 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.

Dưới đâ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 nhập, 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 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ùng chứa đó 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ó thông báo ngắn hiện có, sử dụng một 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ắp tới, sau đó tạo ảnh động từ vị trí hiện tại đến vị trí sắp tới.

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 đó ở đầu và tạo khoảng trống với các thông báo 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í vùng chứa có thể được tạo ảnh động để chứa thông báo ngắn mới), một lời hứa sẽ được trả về và thông báo ngắn đã tạo sẽ được theo dõi để hoàn tất ảnh động CSS (3 ảnh động khung hình chính) cho độ phân giải lời hứa.

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 gây nhầm lẫn 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 hàm này đã đượ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 để các 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ờ thì bạn đã biết cách tôi làm, còn bạn thì sao‽ 🙂

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