Tạo thanh tiến trình được tạo kiểu

Phần tử <progress> là cách ngữ nghĩa để thể hiện tiến trình hoàn thành của một tác vụ, chẳng hạn như tải thông tin xuống hoặc tải thông tin lên hoặc hoàn thành một phần của quy trình gồm nhiều bước. Việc sử dụng phần tử gốc giúp hỗ trợ tiếp cận, vì trình đọc màn hình có role="progressbar" ngầm ẩn và có thể thông báo các giá trị.

Hướng dẫn này triển khai một thanh tiến trình ngang được tạo kiểu đầy đủ với các tuỳ chỉnh kiểu.

Thanh tiến trình hay vòng tròn tiến trình?

  • Nếu không gian bị hạn chế hoặc bạn muốn dùng tỷ lệ khung hình vuông, hãy cân nhắc sử dụng thanh tiến trình hình tròn (vòng tròn tiến trình): Vòng tròn tiến trình
  • Ưu tiên vòng quay cho hầu hết các trường hợp tiến trình không xác định (Vòng quay đang tải). Sử dụng thanh tiến trình không xác định khi trạng thái không xác định chỉ là tạm thời và sẽ sớm được thay thế bằng thanh tiến trình xác định (ví dụ: chờ siêu dữ liệu về một tệp tải lên trước khi có thể hiển thị % tiến trình)

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

  1. Sử dụng phần tử <progress> gốc: Phần tử này cung cấp khả năng hỗ trợ tiếp cận tích hợp sẵn và hành vi nhất quán trên nền tảng.
  2. Sử dụng max nếu cần thang điểm khác với thang điểm mặc định từ 0 đến 1.
  3. Đối với một giá trị tiến trình cụ thể, hãy đặt thuộc tính value. Bỏ qua value cho trạng thái không xác định (ví dụ: trước khi bắt đầu phản hồi cụ thể hơn).
  4. Sử dụng <label for="...">, aria-labelledby hoặc aria-label để liên kết phần tử tiến trình với một nhãn.

Bạn cần tuỳ chỉnh loại nào?

Để tạo kiểu cơ bản, bạn chỉ cần các thuộc tính sau:

  • accent-color để thay đổi màu của thanh tiến trình
  • width/inline-size để thay đổi chiều rộng của thanh tiến trình
  • height/block-size áp dụng không nhất quán — hãy thử nghiệm trước
  • writing-mode: vertical-lr cho thanh dọc.
  • Hiệu ứng áp dụng sau khi vẽ: opacity, filter, clip-path, mặt nạ, phép biến đổi, box-shadow, v.v.

Nếu bạn chỉ cần tạo kiểu ở mức hạn chế, hãy bỏ qua phần tiếp theo.

CẢNH BÁO: Một số thuộc tính nhất định (ví dụ: border-radius, background, border) sẽ tự động tắt kiểu thanh tiến trình mặc định. Nếu cần tạo kiểu ngoài các thuộc tính ở trên, bạn phải triển khai kiểu tuỳ chỉnh hoàn toàn như mô tả trong phần tiếp theo. KHÔNG áp dụng kiểu mở rộng hơn "chỉ trong trường hợp kiểu đó hoạt động".

Kiểu tuỳ chỉnh hoàn toàn

1. Quy trình thiết lập cơ bản

  • BẮT BUỘC: Sử dụng appearance: none để bật kiểu tuỳ chỉnh.
  • Đặt lại đường viền UA bằng border: 0 solid
  • Để cắt thanh đang hoạt động theo bán kính đường viền của phần tử, hãy sử dụng overflow: clip HOẶC áp dụng cùng một border-radius cho thanh. KHÔNG sử dụng border-radius: inherit trên thanh, vì điều này phụ thuộc vào cấu trúc cụ thể của các phần tử trong DOM tối.
progress {
  /* Reset default browser styles */
  appearance: none;
  border: 0 solid;
  /* Clip bar to element’s border-radius  */
  overflow: clip;
}

2. Định kiểu

  • Tạo kiểu cho dải trên chính phần tử progress.
  • Sử dụng phần tử giả nhắm đến trình duyệt thích hợp để nhắm đến chính thanh tiến trình: ::-webkit-progress-value cho WebKit/Blink và ::-moz-progress-bar cho Firefox.
  • BẮT BUỘC: KHÔNG nhóm các phần tử giả -webkit- và -moz- trong cùng một quy tắc. Quy tắc này sẽ được coi là không hợp lệ và sẽ bị bỏ qua. :is() và :where() không giúp ích gì trong trường hợp này vì chúng không hỗ trợ phần tử giả.
  • Mỗi phần tử giả nhắm đến trình duyệt chỉ được xuất hiện một lần. Bạn nên định tuyến các tuỳ chỉnh theo trạng thái cụ thể (chẳng hạn như kích thước và ảnh động của phân đoạn không xác định) thông qua các thuộc tính tuỳ chỉnh được đặt trên chính phần tử progress.
  • Không bắt buộc: Chuyển đổi inline-size trên phần lấp đầy để các thay đổi về giá trị xác định sẽ tạo ảnh động thay vì nhảy. Firefox định kích thước cho phần lấp đầy trong bố cục và bỏ qua phần này, điều này không gây hại.
progress {
  --progress-bar-radius: 1e5px;
  border-radius: var(--progress-bar-radius);
  /* Apply color for custom progress track (background) */
  background: var(--progress-bar-track, var(--color-neutral-90));
  /* Subtle depth on the empty part of the track */
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
}

/* Let <progress> styling show through */
progress::-webkit-progress-bar {
  background: transparent;
}

/* Webkit, Chrome */
progress::-webkit-progress-value {
  background: var(--progress-bar-fill, var(--accent-color));
  border-radius: var(--progress-bar-radius);
  transition: inline-size 0.3s;
}

/* Firefox */
progress::-moz-progress-bar {
  background: var(--progress-bar-fill, var(--accent-color));
  border-radius: var(--progress-bar-radius);
  transition: inline-size 0.3s;
}

3. Tạo kiểu cho trạng thái không xác định

Thêm ảnh động tuỳ chỉnh cho trạng thái không xác định: Biến phần tử giả lấp đầy thành một đoạn ngắn và trượt dọc theo dải.

  • Tạo kiểu cho chính phần lấp đầy; KHÔNG tạo lại phần lấp đầy dưới dạng nền trên progress.
  • Màu nền không xác định chỉ tồn tại thông qua ảnh động: khung hình chính đặt độ dài bằng inline-size thực (giá trị này vượt trội hơn inline-size cùng dòng của UA Chromium) và di chuyển màu nền bằng translate. KHÔNG sử dụng scale cho chiều dài; nó làm biến dạng bất kỳ mẫu nào được vẽ trên phần tô.
  • Tỷ lệ phần trăm translate phân giải theo chiều rộng của chính phần lấp đầy, vì vậy, hãy biểu thị vị trí theo đơn vị theo dõi: đặt progress làm vùng chứa kích thước cùng dòng và sử dụng cqi trong khung hình chính. Thêm margin-right: auto vào from, to để Chromium cũng bố trí phần lấp đầy ở bên trái thực tế trong RTL.
  • Sử dụng phép biến đổi, không phải lề, để di chuyển phần lấp đầy.
    • Phép biến đổi giữ ảnh động trong trình kết hợp (trừ phi inline-size cũng đang được tạo ảnh động)
    • Lề bị hỏng trong RTL vì hai lý do riêng biệt:
    • Firefox định vị phần lấp đầy không xác định chỉ bằng lề trái thực tế, vì vậy, margin-inline-start không có hiệu lực trong RTL và phần lấp đầy không bao giờ di chuyển.
    • Trong Chromium và WebKit, vốn bố trí phần lấp đầy dưới dạng một khối thông thường, khi lề đẩy phần lấp đầy vượt quá điểm cuối của dải, lề sẽ cộng dồn nhiều hơn dải. CSS chuẩn giải quyết vấn đề đó bằng cách bỏ qua margin-left trong RTL, vì vậy, phân đoạn sẽ nhảy đến cạnh thay vì trượt ra khỏi cạnh.
    • translate được áp dụng sau bố cục, nên cả hai vấn đề đều không xảy ra.
  • Để phản chiếu chuyển động trong RTL (như thanh gốc của Chrome), hãy đảo ngược ảnh động trong :dir(rtl):
progress:dir(rtl) {
  --progress-bar-indeterminate-animation: progress-bar-indeterminate var(--progress-bar-indeterminate-animation-duration) infinite linear reverse;
}
  • Xác định các thuộc tính --progress-bar-indeterminate-* công khai trên progress (tên của các thuộc tính này đã xác định phạm vi của chúng cho trạng thái) để người dùng có thể ghi đè các thuộc tính đó mà không cần biết về :indeterminate.
  • Để thay đổi độ dài đoạn (ví dụ: cạnh trước chạy trước và cạnh sau bắt kịp), hãy đặt --progress-bar-indeterminate-animation thành hai ảnh động đồng thời: một ảnh động có khung hình chính tạo ảnh động translate trong cqi (vị trí) và một ảnh động có khung hình chính tạo ảnh động inline-size (độ dài), nhờ đó mỗi ảnh động có khung hình chính và hiệu ứng chuyển đổi riêng. ĐỪNG cố gắng định hình cả hai cạnh trong một danh sách khung hình chính; animation-timing-function cho mỗi khung hình chính áp dụng cho mọi thuộc tính trong khung hình chính đó.
  • --progress-bar-animation áp dụng cho cả hai trạng thái và có thể dùng cho các hiệu ứng như hiệu ứng lấp lánh xác định. revert-rule chỉ được dùng để ghi đè khi được đặt, nhằm tối đa hoá khả năng kết hợp.
  • Không bao giờ tắt ảnh động không xác định (animation: none để lại một khoảng trống có chiều rộng bằng 0 trong Chromium). Để giảm chuyển động, hãy làm chậm chuyển động.
progress {
  /* Lets the fill position itself in track units (cqi) */
  container-type: inline-size;
  /* Segment length relative to the track */
  --progress-bar-indeterminate-size: 30%;
  --progress-bar-indeterminate-animation-duration: 1.5s;

  /* Respect prefers-reduced-motion */
  @media (prefers-reduced-motion: reduce) {
    --progress-bar-indeterminate-animation-duration: 10s;
  }
}

progress:indeterminate {
  --progress-bar-animation: var(
      --progress-bar-indeterminate-animation,
      progress-bar-indeterminate var(--progress-bar-indeterminate-animation-duration) infinite linear
    );
}

/* Add to the existing pseudo-element rules */
progress::-webkit-progress-value {
  animation: var(--progress-bar-animation, revert-rule);
}

progress::-moz-progress-bar {
  animation: var(--progress-bar-animation, revert-rule);
}

/* Physical: left to right regardless of direction (reverse under :dir(rtl) to mirror). */
@keyframes progress-bar-indeterminate {
  from, to {
    inline-size: var(--progress-bar-indeterminate-size);
    /* Pin to the physical left; Chromium would otherwise lay the fill out at the inline start */
    margin-right: auto;
  }
  from { translate: -100%; } /* Own width: fully outside the left edge */
  to { translate: 100cqi; }  /* Track width: fully outside the right edge */
}

Các yếu tố khác cần cân nhắc về khả năng hỗ trợ tiếp cận

Ngoài các nguyên tắc nêu trên:

  • Thông báo hoàn tất mà không làm mất tiêu điểm: Đừng đặt tabindex hoặc gọi .focus() trên <progress>, vì đây là thành phần không tương tác và việc di chuyển tiêu điểm đến thành phần này sẽ làm gián đoạn người dùng bàn phím. Để thông báo khi một tác vụ trong nền hoàn tất, hãy cập nhật một phần tử trạng thái lịch sự (role="status" hoặc aria-live="polite").
  • Không thêm văn bản dự phòng vào bên trong <progress>: Văn bản bên trong <progress> bị các trình duyệt hiện đại bỏ qua và không được các công nghệ hỗ trợ sử dụng.
  • Tránh aria-busy + aria-describedby trên <div> chung chung không được gắn nhãn. aria-busy có khả năng hỗ trợ trình đọc màn hình không nhất quán và aria-describedby trên <div> không có tên và không có vai trò sẽ bị các công nghệ hỗ trợ bỏ qua. Nếu nội dung của một khu vực chưa sẵn sàng trong khi tải, hãy ẩn nội dung đó bằng hidden hoặc aria-hidden="true".
  • NÊN đảm bảo độ tương phản phù hợp giữa thanh tiến trình và dải tiến trình khi thêm kiểu tuỳ chỉnh.
  • Cân nhắc các phương án thay thế để thể hiện trạng thái không xác định: Ảnh động thanh tiến trình không xác định hiển thị song song với nội dung khác có thể khiến một số người dùng bị phân tâm.

Chiến lược dự phòng

Dự phòng và hỗ trợ trình duyệt cho màu nhấn

Thuộc tính accent-color là cải tiến tăng dần. Những trình duyệt không hỗ trợ thuộc tính này sẽ bỏ qua thuộc tính này và sử dụng màu giao diện người dùng mặc định.

Nếu accent-color không được hỗ trợ và bạn cần tạo kiểu cho thanh tiến trình, hãy sử dụng phương thức tạo kiểu tuỳ chỉnh để áp dụng kiểu ưu tiên cho thanh tiến trình.

Phương án dự phòng và khả năng hỗ trợ của trình duyệt cho revert-rule

Nếu revert-rule không được hỗ trợ, animation: var(--progress-bar-animation, revert-rule) sẽ quay lại animation-name: revert-rule với thời lượng 0s khi --progress-bar-animation không được đặt, đây là một thao tác không gây hại khi không có @keyframes revert-rule.