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 thành phần thông báo ngắn thích ứng và dễ 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 thử bản minh hoạ.

Bản minh hoạ

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

Tổng quan

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

Số 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; sẽ không bị loại bỏ hay duy trì. Thông báo là dành cho thông tin quan trọng hơn, thông báo đồng bộ cần tương tác, hoặc thông báo ở cấp hệ thống (thay vì thông báo ở cấp trang). Thông báo ngắn dạng thụ động hơn 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à lựa chọn phù hợp cho thông báo ngắn vì phần tử này được thông báo cho trình đọc màn hình. HTML đúng cung cấp cơ sở an toàn để chúng tôi 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ể bao hàm hơn bằng cách thêm role="status". Điều này sẽ cung cấp một quảng cáo dự phòng nếu trình duyệt không cung cấp vai trò ngầm ẩn cho các phần tử <output> theo quy cách.

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

Hộp chứa thông báo ngắn

Bạn có thể hiện nhiều thông báo ngắn cùng một lúc. Để sắp xếp nhiều thông báo ngắn, bạ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, chúng sẽ được 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 bố cục để đưa ra thông báo ngắn. Đó là fixed trong khung nhìn và sử dụng thuộc tính logic inset để chỉ định cạnh nào cần ghim vào 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ó khoảng đệm và kích thước hộp Công cụ cho nhà phát triển được phủ lên trên phần tử .gui-toast-container.

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

.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 khoảng trống và khoảng trống giữa các thành phần thông báo ngắn.

Bánh mì nướng GUI

Một thông báo ngắn riêng lẻ có một số padding, một số góc nhỏ hơn bằng border-radius và chức năng min() để hỗ trợ điều chỉnh kích thướ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 mở 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 một phần tử .gui- Account, với khoảng đệm và bán kính đường viền được hiển thị.

Kiểu

Khi đã thiết lập bố cục và vị trí, hãy thêm CSS giúp thích ứng với chế độ cài đặt và 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. Thao tác nhấn hoặc vuốt vào thông báo không có tác dụng gì, nhưng chúng hiện sử dụng các sự kiện con trỏ. Dùng CSS sau để ngăn thông báo ngắn đánh cắp các lượt nhấp.

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

Bánh mì nướng GUI

Cung cấp cho thông báo ngắn một giao diện thích ứng sáng hoặc tối bằng các thuộc tính tuỳ chỉnh, HSL và một truy vấn nội dung nghe nhì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ẽ xuất hiện cùng với một ảnh động khi xuất hiện trên màn hình. Điều chỉnh chuyển động đã giảm được thực hiện 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 phương tiện lựa chọn ưu tiên chuyển động . Mọi người đều nhận được một số ảnh động, nhưng chỉ một số người dùng có thông báo ngắn đi được một khoảng cách.

Dưới đây là các khung hình chính được dùng cho ảnh động ngắn. CSS sẽ kiểm soát lượt vào, thời gian chờ và trạng thái thoát của thông báo ngắn – tất cả trong cùng 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à sắp xếp 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à trình đọc màn hình HTML có thể truy cập, bạn cần JavaScript để sắp xếp việc tạo, thêm và huỷ thông báo ngắn dựa trên 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 tối thiểu và dễ dàng bắt đầu, như trong ví dụ sau:

import Toast from './toast.js'

Toast('My first toast')

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

Khi tải mô-đun thông báo ngắn từ JavaScript, mô-đun đó 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ề 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 dành cho tất cả các phần tử nội dung.

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ẻ đầu và thẻ nội dung.

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

const Toaster = init()

Quá trình tạo phần tử HTML thông báo nhanh được thực hiện bằng hàm createToast(). Hàm sử dụng một số văn bản cho thông báo ngắn, tạo một phần tử <output>, trang trí cho phần tử đó bằng một số lớp và thuộc tính, thiết lập 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

JavaScript hiện 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() phối hợp 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 chuyển động có ổn hay không, sau đó sử dụng thông tin này để nối thông báo ngắn hoặc thực hiện một số ảnh động đẹp mắt để các thông báo ngắn khác có vẻ "có 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 một thông báo ngắn vào trang kích hoạt ảnh động CSS: tạo ảnh động, đợi 3s, tạo hiệu ứng động. flipToast() được gọi khi có sẵn các thông báo ngắn, sử dụng kỹ thuật có tên là FLIP của Paul Lewis. Mục đích 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 xem việc này giống như việc đánh dấu vị trí hiện tại của máy nướng bánh mì, vị trí sắp tới, sau đó tạo hiệu ứng động từ vị trí hiện tại đến vị trí hiện tại của 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 thực hiện việc nâng cấp bố cục. Khi một thông báo ngắn mới được thêm, lưới sẽ đặt thông báo này ở đầu và giãn cách với các lưới 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 với nhau

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 để chứa thông báo ngắn mới), một lời hứa được trả về và thông báo ngắn đã tạo được xem để hoàn thành ảnh động CSS (3 ảnh động cho khung hình chính) để có độ phân giải hứa hẹ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à mục ánh xạ toast.getAnimations(). Vì tôi đã sử dụng nhiều ảnh động trong khung hình chính cho thông báo ngắn, nên để chắc chắn rằng tất cả các ảnh này đã hoàn tất, mỗi ảnh phải được yêu cầu qua JavaScript và mỗi finished hứa hẹn sẽ hoàn thành. allSettled phù hợp với chúng tôi, tự giải quyết sau khi đã thực hiện tất cả lời hứa của mình. 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() đáp ứng lời hứa thông báo ngắn cấp cao để nhà phát triển có thể dọn dẹp hoặc thực hiện công việc khác sau khi thông báo ngắn hiển thị.

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 của nhà phát triể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ỳ thứ gì, sau khi thông báo ngắn xuất hiện, họ có thể sử dụng chế độ không đồng bộ và await.

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 tôi làm được như thế nào, bạn sẽ làm thế nào 🙂

Hãy đa dạng hoá phương pháp tiếp cận của chúng ta và tìm hiểu tất cả các cách xây dựng trên web. Hãy tạo một bản minh hoạ, đường liên kết tweet me và tôi sẽ thêm bản phối lại đó vào phần bả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