Tuỳ chỉnh lớp phủ điều khiển cửa sổ của thanh tiêu đề PWA của bạn

Sử dụng vùng thanh tiêu đề bên cạnh các chế độ điều khiển cửa sổ để PWA của bạn trông giống một ứng dụng hơn.

Nếu nhớ bài viết Giúp PWA trông giống ứng dụng hơn của tôi, có thể bạn sẽ nhớ cách tôi đề cập đến việc tuỳ chỉnh thanh tiêu đề của ứng dụng như một chiến lược để tạo trải nghiệm giống ứng dụng hơn. Sau đây là ví dụ về cách thông tin này có thể xuất hiện trong ứng dụng Podcasts trên macOS.

Thanh tiêu đề của ứng dụng Podcasts trên macOS cho thấy các nút điều khiển nội dung nghe nhìn và siêu dữ liệu về podcast đang phát.
Thanh tiêu đề tuỳ chỉnh giúp PWA của bạn trông giống một ứng dụng dành riêng cho nền tảng hơn.

Có thể bạn sẽ phản đối bằng cách nói rằng Podcasts là một ứng dụng macOS dành riêng cho nền tảng, không chạy trong trình duyệt và do đó có thể làm những gì mình muốn mà không phải tuân theo các quy tắc của trình duyệt. Đúng vậy, nhưng tin vui là tính năng Lớp phủ điều khiển cửa sổ (chủ đề của bài viết này) sẽ sớm cho phép bạn tạo giao diện người dùng tương tự cho PWA của mình.

Các thành phần Lớp phủ chế độ điều khiển cửa sổ

Lớp phủ chế độ điều khiển cửa sổ bao gồm 4 tính năng phụ:

  1. Giá trị "window-controls-overlay" cho trường "display_override" trong tệp kê khai ứng dụng web.
  2. Các biến môi trường CSS titlebar-area-x, titlebar-area-y, titlebar-area-widthtitlebar-area-height.
  3. Việc chuẩn hoá thuộc tính CSS độc quyền trước đây -webkit-app-region thành thuộc tính app-region để xác định các vùng có thể kéo trong nội dung web.
  4. Một cơ chế để truy vấn và xử lý khu vực điều khiển cửa sổ thông qua thành viên windowControlsOverlay của window.navigator.

Lớp phủ chế độ điều khiển cửa sổ là gì

Khu vực thanh tiêu đề là khoảng trống ở bên trái hoặc bên phải của các nút điều khiển cửa sổ (tức là các nút thu nhỏ, phóng to, đóng, v.v.) và thường chứa tiêu đề của ứng dụng. Lớp phủ chế độ điều khiển cửa sổ cho phép các ứng dụng web tiến bộ (PWA) mang lại cảm giác giống như ứng dụng hơn bằng cách thay thế thanh tiêu đề có chiều rộng đầy đủ hiện có bằng một lớp phủ nhỏ chứa các chế độ điều khiển cửa sổ. Điều này cho phép nhà phát triển đặt nội dung tuỳ chỉnh vào vùng thanh tiêu đề do trình duyệt kiểm soát trước đây.

Trạng thái hiện tại

Bước Trạng thái
1. Tạo video giải thích Hoàn tất
2. Tạo bản nháp ban đầu của quy cách Hoàn tất
3. Thu thập ý kiến phản hồi và lặp lại quy trình thiết kế Đang tiến hành
4. Bản dùng thử theo nguyên gốc Hoàn chỉnh
5. Ra mắt Hoàn tất (trong Chromium 104)

Cách sử dụng Lớp phủ chế độ điều khiển cửa sổ

Thêm window-controls-overlay vào tệp kê khai ứng dụng web

Ứng dụng web tiến bộ có thể chọn sử dụng lớp phủ các nút điều khiển cửa sổ bằng cách thêm "window-controls-overlay" làm thành phần "display_override" chính trong tệp kê khai ứng dụng web:

{
  "display_override": ["window-controls-overlay"]
}

Lớp phủ điều khiển cửa sổ sẽ chỉ xuất hiện khi tất cả các điều kiện sau đây được đáp ứng:

  1. Ứng dụng không mở trong trình duyệt mà mở trong một cửa sổ PWA riêng.
  2. Tệp kê khai bao gồm "display_override": ["window-controls-overlay"]. (Sau đó, bạn có thể dùng các giá trị khác.)
  3. PWA đang chạy trên một hệ điều hành máy tính.
  4. Nguồn gốc hiện tại khớp với nguồn gốc mà PWA được cài đặt.

Kết quả là một vùng thanh tiêu đề trống với các nút điều khiển cửa sổ thông thường ở bên trái hoặc bên phải, tuỳ thuộc vào hệ điều hành.

Một cửa sổ ứng dụng có thanh tiêu đề trống và các nút điều khiển cửa sổ ở bên trái.
Thanh tiêu đề trống sẵn sàng cho nội dung tuỳ chỉnh.

Di chuyển nội dung vào thanh tiêu đề

Vì đã có khoảng trống trên thanh tiêu đề, nên bạn có thể di chuyển một mục vào đó. Đối với bài viết này, tôi đã tạo một PWA Nội dung nổi bật của Wikimedia. Một tính năng hữu ích cho ứng dụng này có thể là tìm kiếm các từ trong tiêu đề bài viết. HTML cho tính năng tìm kiếm có dạng như sau:

<div class="search">
  <img src="logo.svg" alt="Wikimedia logo." width="32" height="32" />
  <label>
    <input type="search" />
    Search for words in articles
  </label>
</div>

Để di chuyển div này lên thanh tiêu đề, bạn cần có một số CSS:

.search {
  /* Make sure the `div` stays there, even when scrolling. */
  position: fixed;
  /**
   * Gradient, because why not. Endless opportunities.
   * The gradient ends in `#36c`, which happens to be the app's
   * `<meta name="theme-color" content="#36c">`.
   */
  background-image: linear-gradient(90deg, #36c, #131313, 33%, #36c);
  /* Use the environment variable for the left anchoring with a fallback. */
  left: env(titlebar-area-x, 0);
  /* Use the environment variable for the top anchoring with a fallback. */
  top: env(titlebar-area-y, 0);
  /* Use the environment variable for setting the width with a fallback. */
  width: env(titlebar-area-width, 100%);
  /* Use the environment variable for setting the height with a fallback. */
  height: env(titlebar-area-height, 33px);
}

Bạn có thể thấy hiệu ứng của mã này trong ảnh chụp màn hình bên dưới. Thanh tiêu đề có khả năng thích ứng hoàn toàn. Khi bạn đổi kích thước cửa sổ PWA, thanh tiêu đề sẽ phản ứng như thể được tạo thành từ nội dung HTML thông thường.

Cửa sổ ứng dụng có thanh tìm kiếm trong thanh tiêu đề.
Thanh tiêu đề mới đang hoạt động và phản hồi.

Xác định những phần nào của thanh tiêu đề có thể kéo

Mặc dù ảnh chụp màn hình ở trên cho thấy bạn đã hoàn tất, nhưng bạn vẫn chưa hoàn tất. Cửa sổ PWA không còn kéo được nữa (ngoài một vùng rất nhỏ), vì các nút điều khiển cửa sổ không phải là vùng kéo và phần còn lại của thanh tiêu đề bao gồm tiện ích tìm kiếm. Khắc phục vấn đề này bằng cách sử dụng thuộc tính CSS app-region với giá trị drag. Trong trường hợp cụ thể, bạn có thể kéo mọi thứ ngoài phần tử input.

/* The entire search `div` is draggable… */
.search {
  -webkit-app-region: drag;
  app-region: drag;
}

/* …except for the `input`. */
input {
  -webkit-app-region: no-drag;
  app-region: no-drag;
}

Khi có CSS này, người dùng có thể kéo cửa sổ ứng dụng như bình thường bằng cách kéo div, img hoặc label. Chỉ phần tử input là có thể tương tác để bạn có thể nhập cụm từ tìm kiếm.

Phát hiện đối tượng

Bạn có thể phát hiện tính năng hỗ trợ Lớp phủ chế độ điều khiển cửa sổ bằng cách kiểm tra sự tồn tại của windowControlsOverlay:

if ('windowControlsOverlay' in navigator) {
  // Window Controls Overlay is supported.
}

Truy vấn vùng điều khiển cửa sổ bằng windowControlsOverlay

Đoạn mã cho đến nay có một vấn đề: trên một số nền tảng, các nút điều khiển cửa sổ nằm ở bên phải, trên các nền tảng khác thì nằm ở bên trái. Tệ hơn nữa, trình đơn Chrome "ba dấu chấm" cũng sẽ thay đổi vị trí tuỳ theo nền tảng. Điều này có nghĩa là hình nền có hiệu ứng chuyển màu tuyến tính cần được điều chỉnh linh hoạt để chạy từ #131313maroon hoặc maroon#131313maroon, sao cho hình nền này hoà vào màu nền maroon của thanh tiêu đề do <meta name="theme-color" content="maroon"> xác định. Bạn có thể thực hiện việc này bằng cách truy vấn API getTitlebarAreaRect() trên thuộc tính navigator.windowControlsOverlay.

if ('windowControlsOverlay' in navigator) {
  const { x } = navigator.windowControlsOverlay.getTitlebarAreaRect();
  // Window controls are on the right (like on Windows).
  // Chrome menu is left of the window controls.
  // [ windowControlsOverlay___________________ […] [_] [■] [X] ]
  if (x === 0) {
    div.classList.add('search-controls-right');
  }
  // Window controls are on the left (like on macOS).
  // Chrome menu is right of the window controls overlay.
  // [ [X] [_] [■] ___________________windowControlsOverlay [⋮] ]
  else {
    div.classList.add('search-controls-left');
  }
} else {
  // When running in a non-supporting browser tab.
  div.classList.add('search-controls-right');
}

Thay vì có hình nền trong các quy tắc CSS của lớp .search (như trước đây), mã đã sửa đổi hiện sử dụng 2 lớp mà mã ở trên đặt động.

/* For macOS: */
.search-controls-left {
  background-image: linear-gradient(90deg, #36c, 45%, #131313, 90%, #36c);
}

/* For Windows: */
.search-controls-right {
  background-image: linear-gradient(90deg, #36c, #131313, 33%, #36c);
}

Xác định xem lớp phủ chế độ điều khiển cửa sổ có hiển thị hay không

Lớp phủ chế độ điều khiển cửa sổ sẽ không xuất hiện trong vùng thanh tiêu đề trong mọi trường hợp. Mặc dù không xuất hiện trên các trình duyệt không hỗ trợ tính năng Lớp phủ điều khiển cửa sổ, nhưng nút này cũng sẽ không xuất hiện khi PWA đang đề cập chạy trong một thẻ. Để phát hiện trường hợp này, bạn có thể truy vấn thuộc tính visible của windowControlsOverlay:

if (navigator.windowControlsOverlay.visible) {
  // The window controls overlay is visible in the title bar area.
}

Ngoài ra, bạn cũng có thể sử dụng truy vấn nội dung nghe nhìn display-mode trong JavaScript và/hoặc CSS:

// Create the query list.
const mediaQueryList = window.matchMedia('(display-mode: window-controls-overlay)');

// Define a callback function for the event listener.
function handleDisplayModeChange(mql) {
  // React on display mode changes.
}

// Run the display mode change handler once.
handleDisplayChange(mediaQueryList);

// Add the callback function as a listener to the query list.
mediaQueryList.addEventListener('change', handleDisplayModeChange);
@media (display-mode: window-controls-overlay) { 
  /* React on display mode changes. */ 
}

Nhận thông báo về các thay đổi về hình học

Việc truy vấn vùng lớp phủ chế độ điều khiển cửa sổ bằng getTitlebarAreaRect() có thể đủ cho những việc chỉ cần thực hiện một lần như đặt hình nền chính xác dựa trên vị trí của chế độ điều khiển cửa sổ, nhưng trong các trường hợp khác, bạn cần kiểm soát chi tiết hơn. Ví dụ: một trường hợp sử dụng có thể là điều chỉnh lớp phủ chế độ điều khiển cửa sổ dựa trên không gian có sẵn và thêm một câu đùa ngay trong lớp phủ chế độ điều khiển cửa sổ khi có đủ không gian.

Vùng lớp phủ chế độ điều khiển cửa sổ trên một cửa sổ hẹp có văn bản rút gọn.
Các chế độ điều khiển trên thanh tiêu đề được điều chỉnh cho phù hợp với cửa sổ hẹp.

Bạn có thể nhận được thông báo về các thay đổi về hình học bằng cách đăng ký navigator.windowControlsOverlay.ongeometrychange hoặc bằng cách thiết lập một trình nghe sự kiện cho sự kiện geometrychange. Sự kiện này sẽ chỉ kích hoạt khi lớp phủ điều khiển cửa sổ hiển thị, tức là khi navigator.windowControlsOverlay.visibletrue.

const debounce = (func, wait) => {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
};

if ('windowControlsOverlay' in navigator) {
  navigator.windowControlsOverlay.ongeometrychange = debounce((e) => {
    span.hidden = e.titlebarAreaRect.width < 800;
  }, 250);
}

Thay vì chỉ định một hàm cho ongeometrychange, bạn cũng có thể thêm một trình nghe sự kiện vào windowControlsOverlay như bên dưới. Bạn có thể đọc về sự khác biệt giữa hai loại này trên MDN.

navigator.windowControlsOverlay.addEventListener(
  'geometrychange',
  debounce((e) => {
    span.hidden = e.titlebarAreaRect.width < 800;
  }, 250),
);

Khả năng tương thích khi chạy trong thẻ và trên các trình duyệt không được hỗ trợ

Có 2 trường hợp có thể xảy ra mà bạn cần cân nhắc:

  • Trường hợp ứng dụng đang chạy trong một trình duyệt hỗ trợ Lớp phủ chế độ điều khiển cửa sổ, nhưng ứng dụng được dùng trong một thẻ trình duyệt.
  • Trường hợp ứng dụng đang chạy trong một trình duyệt không hỗ trợ Lớp phủ chế độ điều khiển cửa sổ.

Trong cả hai trường hợp, theo mặc định, HTML được tạo cho lớp phủ điều khiển cửa sổ sẽ hiển thị nội tuyến như nội dung HTML thông thường và các giá trị dự phòng của biến env() sẽ có hiệu lực cho việc định vị. Trên các trình duyệt được hỗ trợ, bạn cũng có thể quyết định không hiển thị HTML được chỉ định cho lớp phủ điều khiển cửa sổ bằng cách kiểm tra thuộc tính visible của lớp phủ. Nếu thuộc tính này báo cáo false, thì bạn có thể ẩn nội dung HTML đó.

Một PWA đang chạy trong thẻ trình duyệt có lớp phủ điều khiển cửa sổ hiển thị trong phần nội dung.
Các nút điều khiển dành cho thanh tiêu đề có thể dễ dàng xuất hiện trong nội dung trên các trình duyệt cũ.

Xin lưu ý rằng các trình duyệt không hỗ trợ sẽ không xem xét thuộc tính tệp kê khai ứng dụng web "display_override" hoặc không nhận dạng "window-controls-overlay" và do đó sử dụng giá trị có thể có tiếp theo theo chuỗi dự phòng, ví dụ: "standalone".

Một PWA đang chạy ở chế độ độc lập với lớp phủ các nút điều khiển cửa sổ xuất hiện trong phần nội dung.
Các nút điều khiển dành cho thanh tiêu đề có thể dễ dàng xuất hiện trong nội dung trên các trình duyệt cũ.

Những điều cần cân nhắc về giao diện người dùng

Mặc dù có thể hấp dẫn, nhưng bạn không nên tạo trình đơn thả xuống thông thường trong vùng Lớp phủ điều khiển cửa sổ. Làm như vậy sẽ vi phạm nguyên tắc thiết kế trên macOS, một nền tảng mà người dùng mong đợi các thanh trình đơn (cả thanh do hệ thống cung cấp và thanh tuỳ chỉnh) ở đầu màn hình.

Nếu ứng dụng của bạn mang đến trải nghiệm toàn màn hình, hãy cân nhắc kỹ xem có nên đưa Lớp phủ điều khiển cửa sổ vào chế độ xem toàn màn hình hay không. Có thể bạn muốn sắp xếp lại bố cục khi sự kiện onfullscreenchange kích hoạt.

Bản minh hoạ

Tôi đã tạo một bản minh hoạ mà bạn có thể chơi trong nhiều trình duyệt hỗ trợ và không hỗ trợ, cũng như ở trạng thái đã cài đặt và chưa cài đặt. Để có trải nghiệm thực tế với Lớp phủ điều khiển cửa sổ, bạn cần cài đặt ứng dụng. Bạn có thể xem hai ảnh chụp màn hình về những gì bạn có thể mong đợi bên dưới. Mã nguồn của ứng dụng có trên Glitch.

Ứng dụng minh hoạ Nội dung nổi bật của Wikimedia có Lớp phủ chế độ điều khiển cửa sổ.
Bạn có thể chạy thử nghiệm trên ứng dụng minh hoạ.

Tính năng tìm kiếm trong lớp phủ chế độ điều khiển cửa sổ hoạt động đầy đủ:

Ứng dụng minh hoạ Nội dung nổi bật của Wikimedia có Lớp phủ điều khiển cửa sổ và tính năng tìm kiếm đang hoạt động cho cụm từ &quot;cleopa…&quot;, làm nổi bật một trong các bài viết có cụm từ trùng khớp &quot;Cleopatra&quot;.
Một tính năng tìm kiếm sử dụng Lớp phủ chế độ điều khiển cửa sổ.

Lưu ý về bảo mật

Nhóm Chromium đã thiết kế và triển khai API Lớp phủ chế độ điều khiển cửa sổ dựa trên các nguyên tắc cốt lõi được xác định trong Kiểm soát quyền truy cập vào các tính năng mạnh mẽ của nền tảng web, bao gồm quyền kiểm soát của người dùng, tính minh bạch và tính công thái học.

Giả mạo

Việc cho phép các trang web kiểm soát một phần thanh tiêu đề sẽ tạo điều kiện cho nhà phát triển giả mạo nội dung trong khu vực trước đây do trình duyệt kiểm soát và được tin cậy. Hiện tại, trong các trình duyệt Chromium, chế độ độc lập có một thanh tiêu đề. Khi khởi chạy lần đầu, thanh này sẽ hiển thị tiêu đề của trang web ở bên trái và nguồn gốc của trang ở bên phải (tiếp theo là nút "cài đặt và tuỳ chọn khác" cùng các nút điều khiển cửa sổ). Sau vài giây, văn bản gốc sẽ biến mất. Nếu trình duyệt được đặt thành ngôn ngữ từ phải sang trái (RTL), bố cục này sẽ bị đảo ngược sao cho văn bản gốc nằm ở bên trái. Thao tác này sẽ mở lớp phủ các nút điều khiển cửa sổ để giả mạo nguồn nếu không có đủ khoảng đệm giữa nguồn và cạnh phải của lớp phủ. Ví dụ: nguồn "evil.ltd" có thể được thêm vào một trang web đáng tin cậy "google.com", khiến người dùng tin rằng nguồn này đáng tin cậy. Kế hoạch là giữ nguyên văn bản gốc này để người dùng biết nguồn gốc của ứng dụng và có thể đảm bảo rằng nguồn gốc đó đáp ứng được kỳ vọng của họ. Đối với các trình duyệt được định cấu hình theo hướng từ phải sang trái, phải có đủ khoảng đệm ở bên phải văn bản gốc để ngăn một trang web độc hại nối nguồn gốc không an toàn với một nguồn gốc đáng tin cậy.

Tạo vân tay số

Việc bật lớp phủ chế độ điều khiển cửa sổ và các vùng có thể kéo không gây ra những lo ngại đáng kể về quyền riêng tư ngoài việc phát hiện tính năng. Tuy nhiên, do kích thước và vị trí khác nhau của các nút điều khiển cửa sổ trên các hệ điều hành, phương thức navigator.windowControlsOverlay.getTitlebarAreaRect() sẽ trả về một DOMRect có vị trí và kích thước cho biết thông tin về hệ điều hành mà trình duyệt đang chạy. Hiện tại, nhà phát triển có thể phát hiện hệ điều hành từ chuỗi tác nhân người dùng, nhưng do lo ngại về việc lấy dấu vân tay, nên có cuộc thảo luận về việc đóng băng chuỗi UA và hợp nhất các phiên bản hệ điều hành. Cộng đồng trình duyệt đang nỗ lực tìm hiểu tần suất thay đổi kích thước của lớp phủ điều khiển cửa sổ trên các nền tảng, vì giả định hiện tại là các lớp phủ này khá ổn định trên các phiên bản hệ điều hành và do đó sẽ không hữu ích cho việc quan sát các phiên bản hệ điều hành phụ. Mặc dù đây là một vấn đề tiềm ẩn về việc nhận dạng thiết bị, nhưng vấn đề này chỉ áp dụng cho các PWA đã cài đặt sử dụng tính năng thanh tiêu đề tuỳ chỉnh và không áp dụng cho việc sử dụng trình duyệt nói chung. Ngoài ra, API navigator.windowControlsOverlay sẽ không dùng được cho iframe được nhúng bên trong một PWA.

Việc chuyển đến một nguồn gốc khác trong PWA sẽ khiến PWA quay lại thanh tiêu đề độc lập thông thường, ngay cả khi PWA đáp ứng các tiêu chí nêu trên và được khởi chạy bằng lớp phủ chế độ điều khiển cửa sổ. Điều này là để điều chỉnh thanh màu đen xuất hiện trên thanh điều hướng khi chuyển đến một nguồn gốc khác. Sau khi quay lại nguồn gốc ban đầu, lớp phủ điều khiển cửa sổ sẽ được dùng lại.

Thanh URL màu đen cho hoạt động điều hướng bên ngoài nguồn gốc.
Một thanh màu đen sẽ xuất hiện khi người dùng chuyển đến một nguồn khác.

Phản hồi

Nhóm Chromium muốn biết trải nghiệm của bạn khi sử dụng Window Controls Overlay API.

Hãy cho chúng tôi biết về thiết kế API

Có vấn đề gì về API khiến bạn không hài lòng không? Hoặc có phương thức hay thuộc tính nào bị thiếu mà bạn cần triển khai ý tưởng của mình không? Bạn có câu hỏi hoặc bình luận về mô hình bảo mật? Báo cáo vấn đề về quy cách trên kho lưu trữ GitHub tương ứng hoặc thêm ý kiến của bạn vào một vấn đề hiện có.

Báo cáo vấn đề về việc triển khai

Bạn có phát hiện thấy lỗi trong quá trình triển khai Chromium không? Hoặc việc triển khai có khác với quy cách không? Báo cáo lỗi tại new.crbug.com. Nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, hướng dẫn đơn giản để tái hiện và nhập UI>Browser>WebAppInstalls vào hộp Thành phần.

Thể hiện sự ủng hộ đối với API

Bạn có dự định sử dụng Window Controls Overlay API không? Sự ủng hộ công khai của bạn giúp nhóm Chromium ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác thấy tầm quan trọng của việc hỗ trợ các tính năng này.

Gửi một tweet đến @ChromiumDev kèm theo thẻ bắt đầu bằng #WindowControlsOverlay và cho chúng tôi biết bạn đang sử dụng tính năng này ở đâu và như thế nào.

Đường liên kết hữu ích

Lời cảm ơn

Lớp phủ chế độ điều khiển cửa sổ được Amanda Baker trong nhóm Microsoft Edge triển khai và chỉ định. Bài viết này được Joe MedleyKenneth Rohde Christiansen xem xét. Hình ảnh chính của Sigmund trên Unsplash.