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 trông giống một ứng dụng hơn.

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

Thanh tiêu đề của ứng dụng Podcast 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 trông giống như một ứng dụng dành riêng cho nền tảng.

Giờ đây, có thể bạn sẽ phản đối khi cho 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ì ứng dụng muốn mà không cần phải chạy theo các quy tắc của trình duyệt. Đúng, nhưng tin vui là tính năng Lớp phủ chế độ điều khiển cửa sổ (là chủ đề của bài viết này) sẽ sớm cho phép bạn tạo các giao diện người dùng tương tự cho PWA của mình.

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. Chuẩn hoá thuộc tính CSS đã từng thuộc quyền sở hữu riêng -webkit-app-region làm 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 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 đề là không gian ở bên trái hoặc bên phải các tuỳ chọn đ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ổ giúp các ứng dụng web tiến bộ (PWA) mang đến trải nghiệm 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à vùng 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 thông báo 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à cải tiến thiết kế Đang tiến hành
4. Bản dùng thử theo nguyên gốc Hoàn chỉnh
5. 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

Một ứ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ủ chế độ điều khiển cửa sổ sẽ chỉ hiển thị khi tất cả các điều kiện sau đây được đáp ứng:

  1. Ứng dụng không được mở trong trình duyệt, mà chỉ mở trong một cửa sổ PWA riêng biệt.
  2. Tệp kê khai bao gồm "display_override": ["window-controls-overlay"]. (Các giá trị khác sẽ được cho phép sau đó.)
  3. PWA đang chạy trên hệ điều hành máy tính để bàn.
  4. Nguồn hiện tại khớp với nguồn gốc của nơi 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ó thanh tiêu đề trống với các tuỳ chọn đ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, khi đã có khoảng trống trong thanh tiêu đề, bạn có thể di chuyển nội dung đến đó. Trong bài viết này, tôi đã xây dựng một ứng dụng web tiến bộ (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 trô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 tác dụng của mã này trong ảnh chụp màn hình bên dưới. Thanh tiêu đề hoàn toàn phản hồi. Khi bạn đổi kích thước cửa sổ PWA, thanh tiêu đề sẽ phản ứng như thể nó bao gồm 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 đề.
Thanh tiêu đề mới đang hoạt động và có tính thích ứng.

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

Mặc dù ảnh chụp màn hình ở trên cho thấy rằng 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 có thể kéo được (ngoài 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à 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. Hãy khắc phục vấn đề này bằng cách sử dụng thuộc tính CSS app-region có 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ỉ có phần tử input là có tính tương tác nên người dùng có thể 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 chế độ hỗ trợ Lớp phủ chế độ điều khiển cửa sổ bằng cách kiểm thử 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

Cho đến thời điểm này, mã 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 các nền tảng khác thì chúng nằm ở bên trái. Một vấn đề tệ hơn là trình đơn Chrome "ba dấu chấm" cũng sẽ thay đổi vị trí, dựa trên nền tảng. Tức là hình nền chuyển màu tuyến tính cần được điều chỉnh một cách linh động để chạy từ #131313maroon hoặc maroon#131313maroon, để 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 2 lớp mà mã ở trên sẽ đặ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ủ điều khiển cửa sổ có hiển thị hay không

Lớp phủ điều khiển cửa sổ sẽ không hiển thị trong khu vực thanh tiêu đề trong mọi trường hợp. Mặc dù đương nhiên sẽ không có dữ liệu này trên các trình duyệt không hỗ trợ tính năng Lớp phủ chế độ điều khiển cửa sổ, nhưng cũng sẽ không có khi PWA 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 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ề những thay đổi về hình học

Việc truy vấn vùng phủ điều khiển cửa sổ bằng getTitlebarAreaRect() có thể là đủ cho những việ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 kiểm soát chi tiết hơn. Ví dụ: một trường hợp sử dụng khả thi 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.

Vùng phủ của chế độ điều khiển cửa sổ nằm trên một cửa sổ hẹp có văn bản được rút ngắ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 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 2 công cụ 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 một thẻ và trên các trình duyệt không hỗ trợ

Có hai trường hợp có thể bạn cần xem xét:

  • Trường hợp một ứng dụng đang chạy trong trình duyệt hỗ trợ Lớp phủ chế độ điều khiển cửa sổ, nhưng là nơi ứng dụng được dùng trong một thẻ trình duyệt.
  • Trường hợp một ứng dụng đang chạy trong 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ị cùng dòng như nội dung HTML thông thường và giá trị dự phòng của biến env() sẽ bắt đầu xác định 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ủ. Nếu lớp phủ báo cáo false, hãy ẩn nội dung HTML đó.

Một ứng dụng web tiến bộ (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 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 ra "window-controls-overlay" và do đó sẽ sử dụng giá trị có thể có tiếp theo theo chuỗi dự phòng, chẳng hạn như "standalone".

Một ứng dụng web tiến bộ (PWA) đang 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ểm cần lưu ý về giao diện người dùng

Mặc dù việc này có thể hấp dẫn, nhưng bạn không nên tạo trình đơn thả xuống cổ điển trong vùng Lớp phủ chế độ đ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 muốn thấy các thanh trình đơn (cả trình đơn do hệ thống cung cấp và trình đơn 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 cẩn thận xem việc Lớp phủ chế độ điều khiển cửa sổ trở thành một phần của chế độ xem toàn màn hình có hợp lý hay không. Có thể bạn muốn sắp xếp lại bố cục của mình 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ạy trong các 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. Bạn cần cài đặt ứng dụng Lớp phủ chế độ điều khiển cửa sổ thực tế. Bạn có thể xem 2 ảnh chụp màn hình cho thấy những thông tin ở bên dưới. Mã nguồn cho ứng dụng có trên Glitch.

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

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

Ứng dụng minh hoạ Nội dung nổi bật của Wikimedia với Lớp phủ Điều khiển Cửa sổ và tìm kiếm đang hoạt động cho cụm từ &#39;cleopa...&#39; được đánh dấu nổi bật trong số các bài viết có cụm từ phù hợp &#39;Cleopatra&#39;.
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ổ theo các nguyên tắc cốt lõi được nêu trong bài viết Kiểm soát quyền truy cập vào các tính năng nền tảng web mạnh mẽ, bao gồm cả quyền kiểm soát của người dùng, độ trong suốt và tính hiệu quả.

Giả mạo

Việc cho phép các trang web kiểm soát một phần thanh tiêu đề sẽ giúp các nhà phát triển có thể giả mạo nội dung trong khu vực trước đây là một khu vực đáng tin cậy và 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 thanh tiêu đề. Khi khởi chạy lần đầu, thanh tiêu đề 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à tính năng khác" và các tuỳ chọn đ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), thì 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 điểm gốc và cạnh bên phải của lớp phủ. Ví dụ: nguồn gốc "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 là đáng tin cậy. Mục đích của kế hoạch là giữ lại văn bản gốc này để người dùng biết nguồn gốc của ứng dụng và đảm bảo rằng văn bản đó phù hợp với kỳ vọng của họ. Đối với các 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 gốc để ngăn trang web độc hại thêm nguồn gốc không an toàn với 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 vấn đề đáng kể nào về quyền riêng tư ngoài việc phát hiện tính năng. Tuy nhiên, do sự khác nhau về kích thước và vị trí 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, các 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. Tuy nhiên, do lo ngại về việc tạo vân tay số, nên có nhiều nội dung thảo luận về việc cố định 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 vẫn đang nỗ lực để 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, do giả định hiện tại cho rằng 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 cho việc quan sát các phiên bản hệ điều hành nhỏ. Mặc dù đây có thể là một vấn đề liên quan đến vân tay số, nhưng nó chỉ áp dụng cho những 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 được cung cấp cho các iframe được nhúng bên trong PWA.

Việc chuyển sang 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 đáp ứng các tiêu chí 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à để điều chỉnh thanh màu đen xuất hiện trong quá trình điều hướng đến một điểm gốc khác. Sau khi di chuyển về 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 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 gốc khác.

Ý kiến phản hồi

Nhóm Chromium muốn biết về trải nghiệm của bạn với API Lớp phủ chế độ kiểm soát cửa sổ.

Cho chúng tôi biết về thiết kế của API

Có điều gì về API không hoạt động như bạn mong đợi không? Hay có thiếu phương thức hoặc thuộc tính nào mà bạn cần triển khai không? Bạn có thắc mắc hoặc nhận xét về mô hình bảo mật? 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 vấn đề hiện có.

Báo cáo sự cố với quá trình triển khai

Bạn có phát hiện thấy lỗi khi triển khai Chromium không? Hay cách triển khai có khác với quy cách không? Gửi lỗi tại new.crbug.com. Hãy nhớ cung cấp nhiều thông tin chi tiết nhất có thể, hướng dẫn đơn giản để tái tạo và nhập UI>Browser>WebAppInstalls vào hộp Thành phần. Sự cố rất hữu ích trong việc chia sẻ các bản sao nhanh và dễ dàng.

Hỗ trợ API

Bạn có định sử dụng API Lớp phủ chế độ kiểm soát cửa sổ không? Sự hỗ trợ 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 biết tầm quan trọng của việc hỗ trợ các tính năng này.

Hãy gửi Tweet đến @ChromiumDev kèm theo hashtag #WindowControlsOverlay và cho chúng tôi biết vị trí cũng như cách bạn đang sử dụng hashtag này.

Các đường liên kết hữu ích

Xác nhận

Lớp phủ chế độ điều khiển cửa sổ được 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 đánh giá. Hình ảnh chính của Sigmund trên Unsplash.