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 khu vực thanh tiêu đề bên cạnh các chế độ điều khiển cửa sổ để giúp PWA của bạn giống một ứng dụng hơn.

Nếu nhớ bài viết Tạo cảm giác PWA giống ứng dụng hơn, bạn có thể 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. Dưới đây là ví dụ về giao diện của ứng dụng Podcasts trên macOS.

Thanh tiêu đề của ứng dụng Podcasts trên macOS hiển thị 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 giống một ứng dụng dành riêng cho nền tảng hơn.

Bây giờ, bạn có thể muốn 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 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ổ (Window Controls Overlay) (chính là 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ác thành phần của Lớp phủ đ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. Tiêu chuẩn hoá thuộc tính CSS độc quyền trước đây -webkit-app-region dưới dạng thuộc tính app-region để xác định các vùng có thể kéo trong nội dung web.
  4. Cơ chế để truy vấn và xử lý khu vực điều khiển cửa sổ thông qua thành phầ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 đề đề cập đến không gian ở 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ủ đ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 ứng dụng hơn bằng cách hoán đổi thanh tiêu đề có chiều rộng đầy đủ hiện có cho 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 khu vực trước đây là thanh tiêu đề do trình duyệt kiểm soát.

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 thiết kế Đang tiến hành
4. Bản dùng thử theo nguyên gốc Hoàn chỉnh
5. Khởi chạy 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ủ đ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ỉ hiển thị khi đáp ứng tất cả các điều kiện sau:

  1. Ứng dụng không được mở trong trình duyệt, 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ể sử dụng các giá trị khác.)
  3. PWA đang chạy trên 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 đã cài đặt PWA.

Kết quả là một khu vực 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.

Cửa sổ ứng dụng có tiêu đề trống với 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 đề

Giờ đây, thanh tiêu đề đã có không gian, bạn có thể di chuyển một số nội dung 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ể xem hiệu quả của mã này trong ảnh chụp màn hình bên dưới. Thanh tiêu đề hoàn toàn thích ứng. Khi bạn đổi kích thước cửa sổ PWA, thanh tiêu đề sẽ phản ứng như thể nó được tạo thành từ nội dung HTML thông thường, thực tế là như vậy.

Cửa sổ ứng dụng có thanh tìm kiếm trong thanh tiêu đề.
Tiêu đề mới đang hoạt động và thích ứng.

Xác định những phần 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 trừ một khu vực rất nhỏ), vì các nút điều khiển cửa sổ không phải là khu vực 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ị là drag. Trong trường hợp cụ thể, bạn có thể kéo mọi thứ ngoại trừ 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 CSS này được áp dụng, 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 mới có thể tương tác để nhập cụm từ tìm kiếm.

Phát hiện tính nă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

Mã này 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, còn 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 có biểu tượng "ba dấu chấm" cũng sẽ thay đổi vị trí dựa trên nền tảng. Điều này có nghĩa là hình nền chuyển màu tuyến tính cần được điều chỉnh linh động để chạy từ #131313maroon hoặc maroon#131313maroon, để hình nền này kết hợp với 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ì trực tiếp đặt 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 hai lớp mà mã ở trên đặt một cách linh độ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ủ đ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 khu vực thanh tiêu đề trong mọi trường hợp. Mặc dù tự nhiên sẽ không có 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 lớp phủ này cũng sẽ không có khi PWA có liên quan 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 đa phương tiệ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ủ điều khiển cửa sổ bằng getTitlebarAreaRect() có thể đủ cho các thao tác một lần như đặt hình nền chính xác dựa trên vị trí của các chế độ điều khiển cửa sổ, nhưng trong các trường hợp khác, bạn cần có quyề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ủ điều khiển cửa sổ dựa trên không gian có sẵn và thêm một câu chuyện cười ngay trong lớp phủ điều khiển cửa sổ khi có đủ không gian.

Khu vực lớp phủ điều khiển cửa sổ trên một cửa sổ hẹp có văn bản được rút gọn.
Các nút đ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 thiết lập 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 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 hỗ trợ

Có hai 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 sử 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ủ đ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ị cùng dòng như nội dung HTML thông thường và các giá trị dự phòng của biến env() sẽ được áp dụng cho vị trí. Trên các trình duyệt 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ủ và nếu thuộc tính đó báo cáo false, thì ẩn nội dung HTML đó.

Một PWA đang chạy trong thẻ trình duyệt với lớp phủ điều khiển cửa sổ hiển thị trong phần nội dung.
Các chế độ điều khiển dành cho thanh tiêu đề có thể dễ dàng hiển thị trong phần 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", do đó sẽ sử dụng giá trị có thể có tiếp theo theo chuỗi dự phòng, ví dụ: "standalone".

Một PWA chạy ở chế độ độc lập với lớp phủ điều khiển cửa sổ hiển thị trong phần nội dung.
Các chế độ điều khiển dành cho thanh tiêu đề có thể dễ dàng hiển thị trong phần 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 kiểu cũ trong khu vực Lớp phủ điều khiển cửa sổ. Việc nà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 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 cung cấp trải nghiệm toàn màn hình, hãy cân nhắc kỹ xem Lớp phủ điều khiển cửa sổ có nên là một phần của chế độ xem toàn màn hình hay không. Có thể bạn sẽ 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ề trải nghiệm dự kiến bên dưới. Bạn có thể xem mã nguồn của ứng dụng này trên Glitch.

Ứ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ổ.
Ứng dụng minh hoạ có sẵn để thử nghiệm.

Tính năng tìm kiếm trong lớp phủ đ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ừ khớp &quot;Cleopatra&quot;.
Một tính năng tìm kiếm sử dụng Lớp phủ đ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ổ bằng các nguyên tắc cốt lõi được xác định trong bài viết 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 cả 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 cấp cho các trang web quyền 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 là khu vực đáng tin cậy, do trình duyệt kiểm soát. Hiện tại, trong trình duyệt Chromium, chế độ độc lập bao gồm một thanh tiêu đề. Khi khởi chạy lần đầu, thanh tiêu đề 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 (theo sau là nút "cài đặt và các tuỳ chọn khác" và các chế độ đ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ẽ được lật để văn bản gốc nằm ở bên trái. Thao tác này sẽ mở lớp phủ điều khiển cửa sổ để giả mạo nguồn gốc nếu không có đủ khoảng đệm giữa nguồn gốc 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 đó đáng tin cậy. Kế hoạch là giữ nguyên văn bản nguồn 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 ứng dụng đó phù hợp với kỳ vọng của họ. Đối với trình duyệt được định cấu hình RTL, phải có đủ khoảng đệm ở bên phải văn bản nguồn để ngăn một trang web độc hại thêm nguồn không an toàn vào nguồn đá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 vấn đề đá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í của các nút điều khiển cửa sổ khác nhau 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ể khám phá hệ điều hành từ chuỗi tác nhân người dùng, nhưng do lo ngại về vân tay số, 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 không ngừng để tìm hiểu tần suất kích thước của lớp phủ điều khiển cửa sổ thay đổi trên các nền tảng, vì giả định hiện tại là các kích thước 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 khi quan sát các phiên bản hệ điều hành nhỏ. Mặc dù đây là vấn đề tiềm ẩn về việc tạo vân tay kỹ thuật số, 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 thông thường. Ngoài ra, API navigator.windowControlsOverlay sẽ không dùng được cho các iframe được nhúng bên trong PWA.

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

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

Phản hồi

Nhóm Chromium muốn biết trải nghiệm của bạn với Window Controls Overlay API.

Giới thiệu cho chúng tôi về thiết kế API

API có hoạt động như mong đợi không? Hay có phương thức hoặc 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 nhận xét về mô hình bảo mật không? Gửi vấn đề về thông số kỹ thuật 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ó tìm thấy lỗi khi triển khai Chromium không? Hay cách triển khai có khác với thông số kỹ thuật không? Gửi lỗi tại new.crbug.com. Hãy 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 lỗi và nhập UI>Browser>WebAppInstalls vào hộp Components (Thành phần). Glitch rất hữu ích để chia sẻ các bản tái hiện nhanh chóng và dễ dàng.

Hỗ trợ API

Bạn có dự định sử dụng API Lớp phủ chế độ điều khiển cửa sổ 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 đó.

Hãy gửi một Tweet đến @ChromiumDev kèm theo hashtag #WindowControlsOverlay để 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ổ do Amanda Baker thuộc 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.