Tạo thẻ di chuột tương tác

Thẻ di chuột cung cấp bản xem trước tương tác của nội dung khi người dùng thể hiện sự quan tâm đến một đường liên kết (thông qua thao tác di chuột hoặc lấy tiêu điểm). Thông tin này được hiển thị cho người dùng trước khi họ chuyển đến trang đích, đồng thời cung cấp cho người dùng nội dung họ cần hoặc giúp xác minh rằng đường liên kết sẽ chứa nội dung họ đang tìm kiếm.

Nếu bạn cần thành phần chú thích dạng văn bản thuần túy, không tương tác thay vì bản xem trước đường liên kết có cấu trúc, hãy xem phần Hiện chú thích khi di chuột.

1. Kích hoạt thẻ di chuột

Sử dụng API Interest Invokers để kích hoạt thẻ di chuột. Điều này đảm bảo thẻ xuất hiện khi di chuột và lấy tiêu điểm, đồng thời tự động xử lý việc kết nối hỗ trợ tiếp cận.

  • Thêm thuộc tính popover="hint" vào vùng chứa thẻ. Sử dụng hint để tránh đóng các trình đơn bật lên không liên quan có thể đang mở.
  • Thêm thuộc tính interestfor vào đường liên kết kích hoạt, trỏ đến id của thẻ.
<a href="/article" interestfor="article-preview">Article title</a>

<!-- The `id` must match the `interestfor` value on the trigger. -->
<div id="article-preview" popover="hint" class="hovercard">
  <h2>The buzz on hummingbirds</h2>
  <p class="byline">By Delphi Aguilar</p>
  <div>
    <img src="bird.jpg" alt="A vibrant hummingbird">
    <p>Brief summary of the article...</p>
  </div>
</div>

Bạn không cần thêm một đường liên kết khác đến nội dung bên trong thẻ di chuột, vì đường liên kết đó sẽ thừa so với đường liên kết kích hoạt.

Tính năng hỗ trợ tiếp cận được tích hợp vào interestfor

Khi bạn sử dụng interestfor, trình duyệt sẽ xử lý việc kết nối công nghệ hỗ trợ cho bạn:

  • Ngữ nghĩa ngầm ẩn: Mục tiêu có popover="hint" sẽ có vai trò tối thiểu ngầm ẩn là tooltip. KHÔNG tự đặt role="tooltip".
  • Liên kết ngầm: Trình duyệt liên kết ngầm phần tử nguồn với mục tiêu thông qua aria-describedby khi mục tiêu là văn bản thuần tuý hoặc thông qua aria-details khi mục tiêu chứa nội dung tương tác. KHÔNG thêm aria-describedby hoặc aria-details vào điều kiện kích hoạt.
  • Nội dung tương tác: Vì mối liên kết chuyển sang aria-details khi cần, nên mục tiêu ĐƯỢC PHÉP chứa nội dung tương tác (ví dụ: đường liên kết bên trong "thẻ mối quan tâm").

2. Định vị bằng tính năng Định vị neo

Đặt vị trí thẻ di chuột tương ứng với trình kích hoạt bằng CSS Anchor Positioning (Định vị neo CSS). Khi sử dụng interestfor, trình kích hoạt sẽ trở thành một neo ngầm ẩn cho cửa sổ bật lên.

[popover].hovercard {
  /* Position the card below the trigger, aligned with its inline-start edge
     and spanning toward the inline-end, flipping if it overflows the viewport. */
  position-area: block-end span-inline-end;
  position-try-fallbacks: flip-block, flip-inline;
}

3. Tạo ảnh động cho hoạt động vào và thoát

Theo mặc định, tiêu điểm sẽ xuất hiện và biến mất đối với người dùng bàn phím và chuột sau nửa giây. Điều này giúp ngăn cửa sổ bật lên được nhắm mục tiêu mở ra khi bạn di chuyển qua một trang hoặc di chuyển chuột xung quanh, đồng thời ngăn cửa sổ bật lên đóng lại khi bạn di chuyển chuột từ trình kích hoạt đến cửa sổ bật lên. Để điều chỉnh thời gian, hãy sử dụng thuộc tính CSS interest-delay.

[interestfor]{
  /* Shorthand for interest-delay-start and interest-delay-end */
  interest-delay: 0.2s 0.1s;
}

Nếu người dùng có khả năng di chuyển qua nhiều trình kích hoạt liên tiếp nhanh chóng, hãy đặt độ trễ ban đầu thành 0s nếu cửa sổ bật lên do người dùng quan tâm đã mở.

:root:has(:interest-source) [interestfor] {
  interest-delay-start: 0s;
}

Sử dụng @starting-style để xác định ảnh động vào cảnh và transition-behavior: allow-discrete (cùng với display và overlay) để tạo ảnh động thoát cảnh khỏi lớp trên cùng, kiểm soát các hiệu ứng chuyển đổi phía sau @media (prefers-reduced-motion: no-preference).

[popover].hovercard {
  opacity: 0;
  transform-origin: top;
}

[popover].hovercard:popover-open {
  opacity: 1;
  transform: translateY(0) scale(1);
}

@starting-style {
  [popover].hovercard:popover-open {
    opacity: 0;
    transform: translateY(-10px) scale(0.95);
  }
}

/* MANDATORY: Respect user preference for reduced motion by selectively applying transitions. */
@media (prefers-reduced-motion: no-preference) {
  [popover].hovercard {
    transition:
      opacity,
      transform,
      display,
      overlay;
    transition-duration: 0.4s;
    transition-behavior: allow-discrete;
  }
}

4. KHÔNG BẮT BUỘC: Chuyển đổi liền mạch sang trang tiếp theo

Bạn có thể tuỳ ý sử dụng Hiệu ứng chuyển đổi khung hiển thị giữa các tài liệu để tạo ảnh động "biến đổi" liền mạch giữa thẻ di chuột và trang đích. Xem phần Hiệu ứng chuyển đổi nhất quán giữa các tài liệu để biết hướng dẫn triển khai.

Bằng cách gán các giá trị view-transition-name khớp với các phần tử trong thẻ di chuột và các phần tử tương ứng trên trang tiếp theo, trình duyệt sẽ tạo ảnh động cho các phần tử đó trong suốt quá trình điều hướng. Bạn có thể đặt tên cho nhiều phần tử (ví dụ: tiêu đề, hình ảnh, dòng ghi tên tác giả) để tạo hiệu ứng biến đổi phức tạp, nhiều phần tử. Chỉ định view-transition-name cho các phần tử trên cửa sổ bật lên đang mở bằng :popover-open để tránh tạo view-transition-name trùng lặp hoặc yêu cầu view-transition-name dành riêng cho đường liên kết.

/* Opt-in to cross-document transitions on both pages */
@view-transition {
  navigation: auto;
}

/* Don't show transitions when loading this page */
::view-transition-group(*) {
  animation-duration: 0s;
}

/* Assign names ONLY when the popover is open to ensure only the correct elements are selected. */
[popover]:popover-open {
  view-transition-name: --hovercard;
  view-transition-class: morph;

  h2 {
    view-transition-name: --title;
    view-transition-class: morph;
  }

  img {
    view-transition-name: --image;
    view-transition-class: morph;
  }
}

BẮT BUỘC: Để đảm bảo quá trình chuyển đổi ổn định và không bị nhấp nháy, trang đích phải sử dụng blocking="render" và <link rel="expect"> để chờ nội dung quan trọng được phân tích cú pháp trước khi kết xuất khung hình đầu tiên.

<!-- On the destination page: -->
<head>
  <!-- Block rendering until the hero image and title are parsed,
       ensuring the view transition snapshot is taken at the right time. -->
  <link rel="expect" href="#main-content" blocking="render">
</head>
<body>
  <main id="main-content">
    <h1>The buzz on hummingbirds</h1>
    <img src="bird.jpg">
  </main>
</body>
/* On the destination page: */
main {
  view-transition-name: --hovercard;
  view-transition-class: morph;
}
h1 {
  view-transition-name: --title;
  view-transition-class: morph;
}
img {
  view-transition-name: --image;
  view-transition-class: morph;
}

/* By default no transition */
::view-transition-group(*) {
  animation-duration: 0s;
}
/* Short transition for morphing elements */
::view-transition-group(.morph) {
  animation-duration: 0.4s;
}
/* Disable transition if no `old` view */
::view-transition-new(.morph):only-child {
  animation-duration: 0s;
}

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

Vì điều kiện kích hoạt là một đường liên kết <a href="..."> chuẩn, nên các trình duyệt không hỗ trợ vẫn có thể điều hướng khi người dùng nhấp vào nếu bạn chọn coi thẻ di chuột là một cải tiến tăng dần. Khi cần hỗ trợ thẻ di chuột trên nhiều trình duyệt, hãy tải polyfill theo điều kiện nếu cần.

Phương án dự phòng và hỗ trợ trình duyệt cho Popover

Popover API chủ yếu là cải tiến tăng dần, nhưng các hành vi xác định của API này (quảng bá lớp trên cùng, đóng nhẹ và gọi popovertarget) không có hành vi tương đương chỉ dùng CSS. Các trình duyệt cũ cần có polyfill hoặc phương án dự phòng thủ công nếu bạn không muốn cung cấp polyfill.

Polyfill: Để hỗ trợ thuộc tính popover trong các trình duyệt cũ, hãy tải @oddbird/popover-polyfill theo điều kiện. BẮT BUỘC: Phát hiện tính năng bằng cách kiểm tra thuộc tính popover trên HTMLElement.prototype và chỉ tải polyfill khi thiếu tính năng hỗ trợ gốc — KHÔNG tải polyfill vô điều kiện.

Với trình đóng gói hoặc bản đồ nhập:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

Nếu không có trình đóng gói, hãy nhập từ CDN bên trong <script type="module">:

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

Lưu ý về kiểu: Polyfill không thể xác định lớp giả :popover-open thực, vì vậy, lớp này sẽ áp dụng lớp .\:popover-open. BẮT BUỘC: Kết hợp cả hai bằng :is() hoặc :where(), nếu không, các trình duyệt thiếu :popover-open sẽ loại bỏ toàn bộ quy tắc:

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

Ngoài ra, đối với phương án dự phòng cũ không có polyfill, hãy sử dụng position: fixed và tính toán toạ độ theo cách thủ công thông qua getBoundingClientRect() hoặc dựa vào vị trí mặc định bằng inset: auto nếu điều đó phù hợp với trường hợp sử dụng.

Hỗ trợ dự phòng và trình duyệt cho trình gọi Interest

Trình gọi quan tâm phải được polyfill có điều kiện bằng cách sử dụng gói polyfill interestfor từ NPM. Ưu tiên gói polyfill hơn là sử dụng CDN.

BẮT BUỘC: Phát hiện tính năng bằng cách kiểm tra thuộc tính interestForElement trên HTMLButtonElement.prototype và chỉ tải polyfill khi thiếu tính năng hỗ trợ gốc.

<script type="module">
  if (!HTMLButtonElement.prototype.hasOwnProperty("interestForElement")) {
    // CDN link only used for example, prefer bundling.
    await import("https://unpkg.com/interestfor@latest");
  }
</script>

Lưu ý về Polyfill CSS

Khi sử dụng polyfill, CSS API sẽ thay đổi một chút để bù đắp cho việc thiếu lớp giả và thuộc tính gốc:

  • Lớp giả: Polyfill không thể xác định các lớp giả :interest-source và :interest-target thực, vì vậy, polyfill sẽ áp dụng các lớp .interest-source và .interest-target. BẮT BUỘC: Kết hợp các trạng thái này với :is() để hỗ trợ cả trạng thái gốc và trạng thái polyfill.
/* Styles to apply when the effect is being previewed */
:is(:interest-source, .interest-source) { /* ... */ }
:is(:interest-target, .interest-target) { /* ... */ }
  • Thuộc tính CSS: Đối với interest-delay, interest-delay-start và interest-delay-end, polyfill yêu cầu sử dụng các biến CSS vì không thể đọc trực tiếp các thuộc tính tuỳ chỉnh trong tất cả trình duyệt.
/* Adjust the start and end delay for interest invokers */
[interestfor] {
  --interest-delay-start: 0.2s;
  interest-delay-start: var(--interest-delay-start);
  --interest-delay-end: 0.1s;
  interest-delay-end: var(--interest-delay-end);
}