Intersection Observer API là một API JavaScript cho phép chúng ta quan sát một phần tử và phát hiện khi phần tử đó đi qua một điểm đã chỉ định trong một vùng chứa cuộn — thường (nhưng không phải luôn luôn) là khung nhìn — kích hoạt hàm gọi lại.
Intersection Observer có thể được coi là có hiệu suất cao hơn so với việc lắng nghe các sự kiện cuộn trên luồng chính vì nó không đồng bộ và hàm gọi lại sẽ chỉ kích hoạt khi phần tử mà chúng ta đang quan sát đạt đến ngưỡng đã chỉ định, thay vì mỗi khi vị trí cuộn được cập nhật. Trong bài viết này, chúng ta sẽ xem xét một ví dụ về cách chúng ta có thể sử dụng Intersection Observer để xây dựng một thành phần tiêu đề cố định thay đổi khi giao với các phần khác nhau của trang web.
Khi chúng ta đã tạo observer, sau đó chúng ta cần hướng dẫn nó theo dõi một phần tử mục tiêu:
Bất kỳ giá trị tùy chọn nào cũng có thể bị bỏ qua, vì chúng sẽ trở về giá trị mặc định của chúng:
Nếu không chỉ định root, thì nó sẽ được phân loại là chế độ xem của trình duyệt. Ví dụ mã ở trên hiển thị các giá trị mặc định cho cả
Giá trị

Điều đó có nghĩa là về mặt kỹ thuật, một phần tử có thể được phân loại là "giao nhau" ngay cả khi nó nằm ngoài tầm nhìn (nếu gốc cuộn của chúng ta là khung nhìn).


Không phải lúc nào cũng dễ hình dung khi nào một phần tử sẽ được phân loại là có thể nhìn thấy bằng các tùy chọn này. Tôi đã xây dựng một công cụ nhỏ để giúp làm quen với Intersection Observer.

Tôi đã đưa bản demo vào cuối bài viết này, vì vậy bạn có thể thoải mái chuyển thẳng đến đó nếu bạn muốn giải mã. (Ngoài ra còn có kho lưu trữ Github.)
Mỗi phần có chiều cao tối thiểu là
Tiếp theo là HTML cho phần còn lại của trang, được chia thành các phần. Để ngắn gọn, tôi chỉ đưa vào các phần có liên quan đến bài viết, nhưng đánh dấu đầy đủ được đưa vào bản demo. Mỗi phần bao gồm một thuộc tính dữ liệu chỉ định tên của màu nền và một
Chúng ta sẽ định vị tiêu đề bằng CSS sao cho tiêu đề cố định ở đầu trang khi người dùng cuộn:
Chúng ta cũng sẽ cung cấp cho các phần chiều cao tối thiểu và căn giữa nội dung. (Mã này không cần thiết để Intersection Observer hoạt động, nó chỉ dành cho thiết kế.)
Để giải quyết vấn đề này, trong bản demo, chúng ta có thể gói mã đánh dấu của mình trong một phần tử khác, phần tử này sẽ đóng vai trò là vùng chứa cuộn — gốc trong các tùy chọn IO của chúng ta — thay vì khung nhìn của trình duyệt, như chúng ta có thể mong đợi:
Nếu bạn muốn xem cách sử dụng viewport làm gốc cho cùng một bản demo, thì điều này được bao gồm trong kho lưu trữ Github.
Chúng tôi sẽ sử dụng các thuộc tính tùy chỉnh này trong tiêu đề của mình:
Chúng tôi cũng sẽ thiết lập màu sắc cho các phần khác nhau của mình. Tôi đang sử dụng các thuộc tính dữ liệu làm bộ chọn, nhưng bạn cũng có thể dễ dàng sử dụng một lớp nếu bạn thích.
Chúng ta cũng có thể thiết lập một số kiểu cho tiêu đề khi từng phần được xem:
Có một trường hợp mạnh hơn để sử dụng các thuộc tính dữ liệu ở đây vì chúng ta sẽ chuyển đổi thuộc tính
Chúng ta đang đặt ngưỡng là 0, vì chúng ta muốn nó kích hoạt nếu bất kỳ phần nào của phần này giao với lề gốc.
Trước hết, chúng ta sẽ tạo một lệnh gọi lại để thay đổi giá trị
Sau đó, chúng ta sẽ tạo trình quan sát để theo dõi các phần giao nhau:
Bây giờ, chúng ta sẽ thấy màu tiêu đề được cập nhật khi từng phần giao với tiêu đề.
Xem Bút [Happy Face Ice Cream Parlour – Bước 2](https://codepen.io/smashingmag/pen/poPgpjZ) của Michelle Barker.
Xem Bút Happy Face Ice Cream Parlour – Bước 2 của Michelle Barker.
Tuy nhiên, bạn có thể nhận thấy rằng màu sắc không cập nhật chính xác khi chúng ta cuộn xuống. Trên thực tế, tiêu đề đang cập nhật với màu sắc của phần trước mỗi lần! Ngược lại, khi cuộn lên trên, nó hoạt động hoàn hảo. Chúng ta cần xác định hướng cuộn và thay đổi hành vi cho phù hợp.
Chúng ta cũng sẽ tạo một hàm mới để cập nhật màu tiêu đề, truyền vào phần mục tiêu dưới dạng đối số:
Cho đến nay, chúng ta không thấy có thay đổi nào đối với hành vi của tiêu đề. Nhưng bây giờ chúng ta đã biết hướng cuộn, chúng ta có thể truyền vào một mục tiêu khác cho hàm
Tuy nhiên, vẫn còn một vấn đề nữa: tiêu đề sẽ cập nhật không chỉ khi phần chạm vào tiêu đề mà còn khi phần tử tiếp theo xuất hiện ở cuối khung nhìn. Điều này là do người quan sát của chúng ta kích hoạt lệnh gọi lại hai lần: một lần khi phần tử đang vào và một lần nữa khi phần tử đang rời khỏi.
Để xác định xem tiêu đề có nên cập nhật hay không, chúng ta có thể sử dụng khóa
Chúng ta sẽ cập nhật hàm
Bây giờ màu sắc của chúng ta sẽ cập nhật chính xác. Chúng ta có thể thiết lập một chuyển đổi CSS để hiệu ứng đẹp hơn một chút:
Xem Bút [Tiệm Kem Happy Face – Bước 3](https://codepen.io/smashingmag/pen/bGWEaEa) của Michelle Barker.
Xem Bút Tiệm Kem Happy Face – Bước 3 của Michelle Barker.
Chúng ta có thể sử dụng một thuộc tính tùy chỉnh cho chiều rộng, với giá trị mặc định là 0. Chúng ta cũng sẽ sử dụng một thuộc tính tùy chỉnh cho giá trị translate x. Chúng ta sẽ thiết lập các giá trị cho những giá trị này trong hàm gọi lại khi người dùng cuộn.
Bây giờ chúng ta có thể viết một hàm sẽ cập nhật chiều rộng và vị trí của điểm đánh dấu tại điểm giao nhau:
Chúng ta có thể gọi hàm này cùng lúc cập nhật màu sắc:
Chúng ta cũng cần đặt vị trí ban đầu cho điểm đánh dấu, để nó không xuất hiện đột ngột. Khi tài liệu được tải, chúng ta sẽ gọi hàm
Cuối cùng, hãy thêm một chuyển tiếp CSS để điểm đánh dấu trượt qua tiêu đề từ liên kết này sang liên kết tiếp theo. Khi chúng ta đang chuyển đổi thuộc tính
Xem Bút [Ví dụ về Quán kem Happy Face – Intersection Observer](https://codepen.io/smashingmag/pen/XWRXVXQ) của Michelle Barker.
Xem Bút Ví dụ về Quán kem Happy Face – Intersection Observer của Michelle Barker.
Để phát hiện Intersection Observer có được hỗ trợ hay không, chúng tôi có thể sử dụng lệnh sau:
Intersection Observer có thể được coi là có hiệu suất cao hơn so với việc lắng nghe các sự kiện cuộn trên luồng chính vì nó không đồng bộ và hàm gọi lại sẽ chỉ kích hoạt khi phần tử mà chúng ta đang quan sát đạt đến ngưỡng đã chỉ định, thay vì mỗi khi vị trí cuộn được cập nhật. Trong bài viết này, chúng ta sẽ xem xét một ví dụ về cách chúng ta có thể sử dụng Intersection Observer để xây dựng một thành phần tiêu đề cố định thay đổi khi giao với các phần khác nhau của trang web.
Cách sử dụng cơ bản
Để sử dụng Intersection Observer, trước tiên chúng ta cần tạo một observer mới, lấy hai tham số: Một đối tượng có các tùy chọn của observer và hàm gọi lại mà chúng ta muốn thực thi bất cứ khi nào phần tử chúng ta đang quan sát (được gọi là mục tiêu observer) giao với gốc (bộ chứa cuộn, phải là tổ tiên của phần tử mục tiêu).
Mã:
const options = { root: document.querySelector('[data-scroll-root]'), rootMargin: '0px', threshold: 1.0}const callback = (entries, observer) => { entries.forEach((entry) => console.log(entry))}const observer = new IntersectionObserver(callback, options)
Mã:
const targetEl = document.querySelector('[data-target]')observer.observe(targetEl)
Mã:
const options = { rootMargin: '0px', threshold: 1.0}
rootMargin
và threshold
. Những điều này có thể khó hình dung, vì vậy cần giải thích:rootMargin
Giá trị rootMargin
hơi giống với việc thêm lề CSS vào phần tử gốc — và, giống như lề, có thể nhận nhiều giá trị, bao gồm cả giá trị âm. Phần tử mục tiêu sẽ được coi là giao nhau so với lề.
Điều đó có nghĩa là về mặt kỹ thuật, một phần tử có thể được phân loại là "giao nhau" ngay cả khi nó nằm ngoài tầm nhìn (nếu gốc cuộn của chúng ta là khung nhìn).

rootMargin
mặc định là 0px
, nhưng có thể lấy một chuỗi bao gồm nhiều giá trị, giống như sử dụng thuộc tính margin
trong CSS.threshold
threshold
có thể bao gồm một giá trị đơn lẻ hoặc một mảng các giá trị từ 0 đến 1. Nó biểu thị tỷ lệ phần tử phải nằm trong giới hạn gốc để được coi là giao nhau. Sử dụng giá trị mặc định là 1, lệnh gọi lại sẽ kích hoạt khi 100% phần tử mục tiêu hiển thị trong gốc.
Không phải lúc nào cũng dễ hình dung khi nào một phần tử sẽ được phân loại là có thể nhìn thấy bằng các tùy chọn này. Tôi đã xây dựng một công cụ nhỏ để giúp làm quen với Intersection Observer.
Tạo Header
Bây giờ chúng ta đã nắm được các nguyên tắc cơ bản, hãy bắt đầu xây dựng tiêu đề động của mình. Chúng ta sẽ bắt đầu với một trang web được chia thành các phần. Hình ảnh này hiển thị bố cục hoàn chỉnh của trang mà chúng ta sẽ xây dựng:
Tôi đã đưa bản demo vào cuối bài viết này, vì vậy bạn có thể thoải mái chuyển thẳng đến đó nếu bạn muốn giải mã. (Ngoài ra còn có kho lưu trữ Github.)
Mỗi phần có chiều cao tối thiểu là
100vh
(mặc dù chúng có thể dài hơn, tùy thuộc vào nội dung). Tiêu đề của chúng tôi được cố định ở đầu trang và giữ nguyên vị trí khi người dùng cuộn (sử dụng position: fixed
). Các phần có nền màu khác nhau và khi chúng gặp tiêu đề, màu của tiêu đề sẽ thay đổi để bổ sung cho màu của phần đó. Ngoài ra còn có một điểm đánh dấu để hiển thị phần hiện tại mà người dùng đang ở, điểm này sẽ trượt dọc khi phần tiếp theo xuất hiện.Để giúp chúng ta dễ dàng đi thẳng đến mã có liên quan, tôi đã thiết lập một bản demo tối giản với điểm bắt đầu của chúng ta (trước khi chúng ta bắt đầu sử dụng API Intersection Observer), trong trường hợp bạn muốn theo dõi.Đánh dấu
Chúng ta sẽ bắt đầu với HTML cho tiêu đề của mình. Đây sẽ là một tiêu đề khá đơn giản với liên kết trang chủ và điều hướng, không có gì đặc biệt cầu kỳ, nhưng chúng ta sẽ sử dụng một vài thuộc tính dữ liệu:data-header
cho chính tiêu đề (để chúng ta có thể nhắm mục tiêu vào phần tử bằng JS) và ba liên kết neo với thuộc tính data-link
, sẽ cuộn người dùng đến phần có liên quan khi nhấp vào:
Mã:
[URL=#0]Trang chủ[/URL] [LIST]
[*] [URL=#about-us]Giới thiệu về chúng tôi[/URL]
[*] [URL=#flavours]Hương vị[/URL]
[*] [URL=#get-in-touch]Liên hệ[/URL] [/LIST]
id
tương ứng với một trong các liên kết neo trong tiêu đề:
Mã:
Mã:
header { position: fixed; width: 100%;}
Mã:
section { padding: 5rem 0; min-height: 100vh; display: flex; justify-content: center; align-items: center;}
Cảnh báo iframe
Trong khi xây dựng bản demo Codepen này, tôi đã gặp phải một vấn đề khó hiểu khi mã Intersection Observer của tôi nên hoạt động hoàn hảo nhưng lại không kích hoạt lệnh gọi lại tại đúng điểm giao nhau mà thay vào đó lại kích hoạt khi phần tử mục tiêu giao nhau với cạnh khung nhìn. Sau một hồi suy nghĩ, tôi nhận ra rằng điều này là do trong Codepen, nội dung được tải trong một khung nhìn, được xử lý khác. (Xem phần tài liệu MDN về Cắt và hình chữ nhật giao nhau để biết đầy đủ chi tiết.)Để giải quyết vấn đề này, trong bản demo, chúng ta có thể gói mã đánh dấu của mình trong một phần tử khác, phần tử này sẽ đóng vai trò là vùng chứa cuộn — gốc trong các tùy chọn IO của chúng ta — thay vì khung nhìn của trình duyệt, như chúng ta có thể mong đợi:
Mã:
CSS
Trong CSS, chúng ta sẽ định nghĩa một số thuộc tính tùy chỉnh cho các màu chúng ta đang sử dụng. Chúng ta cũng sẽ định nghĩa hai thuộc tính tùy chỉnh bổ sung cho văn bản tiêu đề và màu nền, và đặt một số giá trị ban đầu. (Chúng tôi sẽ cập nhật hai thuộc tính tùy chỉnh này cho các phần khác nhau sau.)
Mã:
:root { --mint: #5ae8d5; --chocolate: #573e31; --raspberry: #f2308e; --vanilla: #faf2c8; --headerText: var(--vanilla); --headerBg: var(--raspberry);}
Mã:
header { background-color: var(--headerBg); color: var(--headerText);}
Mã:
[data-section="raspberry"] { background-color: var(--raspberry); color: var(--vanilla);}[data-section="mint"] { background-color: var(--mint); color: var(--chocolate);}[data-section="vanilla"] { background-color: var(--vanilla); color: var(--chocolate);}[data-section="chocolate"] { background-color: var(--chocolate); color: var(--vanilla);}
Mã:
/* Tiêu đề */[data-theme="raspberry"] { --headerText: var(--raspberry); --headerBg: var(--vanilla);}[data-theme="mint"] { --headerText: var(--mint); --headerBg: var(--chocolate);}[data-theme="chocolate"] { --headerText: var(--chocolate); --headerBg: var(--vanilla);}
data-theme
của tiêu đề tại mỗi giao điểm.Tạo Observer
Bây giờ chúng ta đã thiết lập HTML và CSS cơ bản cho trang của mình, chúng ta có thể tạo một observer để theo dõi từng phần của mình xuất hiện. Chúng ta muốn kích hoạt lệnh gọi lại bất cứ khi nào một phần tiếp xúc với phần dưới cùng của tiêu đề khi chúng ta cuộn xuống trang. Điều này có nghĩa là chúng ta cần đặt một lề gốc âm tương ứng với chiều cao của tiêu đề.
Mã:
const header = document.querySelector('[data-header]')const sections = [...document.querySelectorAll('[data-section]')]const scrollRoot = document.querySelector('[data-scroller]')const options = { root: scrollRoot, rootMargin: `${header.offsetHeight * -1}px`, threshold: 0}
Trước hết, chúng ta sẽ tạo một lệnh gọi lại để thay đổi giá trị
data-theme
của tiêu đề. (Điều này đơn giản hơn việc thêm và xóa các lớp, đặc biệt là khi phần tử tiêu đề của chúng ta có thể đã áp dụng các lớp khác.)
Mã:
/* Hàm gọi lại sẽ kích hoạt khi giao nhau */const onIntersect = (entries) => { entries.forEach((entry) => { const theme = entry.target.dataset.section header.setAttribute('data-theme', theme) })}
Mã:
/* Tạo trình quan sát */const observer = new IntersectionObserver(onIntersect, options)/* Đặt trình quan sát của chúng ta để quan sát từng phần */sections.forEach((section) => { observer.observe(section)})
Xem Bút [Happy Face Ice Cream Parlour – Bước 2](https://codepen.io/smashingmag/pen/poPgpjZ) của Michelle Barker.
Xem Bút Happy Face Ice Cream Parlour – Bước 2 của Michelle Barker.
Tuy nhiên, bạn có thể nhận thấy rằng màu sắc không cập nhật chính xác khi chúng ta cuộn xuống. Trên thực tế, tiêu đề đang cập nhật với màu sắc của phần trước mỗi lần! Ngược lại, khi cuộn lên trên, nó hoạt động hoàn hảo. Chúng ta cần xác định hướng cuộn và thay đổi hành vi cho phù hợp.
Tìm hướng cuộn
Chúng ta sẽ đặt một biến trong JS cho hướng cuộn, với giá trị ban đầu là'up'
và một biến khác cho vị trí cuộn đã biết cuối cùng (prevYPosition
). Sau đó, trong lệnh gọi lại, nếu vị trí cuộn lớn hơn giá trị trước đó, chúng ta có thể đặt giá trị direction
là 'down'
hoặc 'up'
nếu ngược lại.
Mã:
let direction = 'up'let prevYPosition = 0const setScrollDirection = () => { if (scrollRoot.scrollTop = > prevYPosition) { direction = 'down' } else { direction = 'up' } prevYPosition = scrollRoot.scrollTop}const onIntersect = (entries, observer) => { entries.forEach((entry) => { setScrollDirection() /* ... */ })}
Mã:
const updateColors = (target) => { const theme = target.dataset.section header.setAttribute('data-theme', theme)}const onIntersect = (entries) => { entries.forEach((entry) => { setScrollDirection() updateColors(entry.target) })}
updateColors()
của mình. Nếu hướng cuộn là hướng lên, chúng ta sẽ sử dụng mục tiêu mục nhập. Nếu nó ở dưới, chúng ta sẽ sử dụng phần tiếp theo (nếu có).
Mã:
const getTargetSection = (target) => { if (direction === 'up') return target if (target.nextElementSibling) { return target.nextElementSibling } else { return target }}const onIntersect = (entries) => { entries.forEach((entry) => { setScrollDirection() const target = getTargetSection(entry.target) updateColors(target) })}
Để xác định xem tiêu đề có nên cập nhật hay không, chúng ta có thể sử dụng khóa
isIntersecting
từ đối tượng entry
. Hãy tạo một hàm khác để trả về giá trị boolean cho biết màu tiêu đề có nên cập nhật hay không:
Mã:
const shouldUpdate = (entry) => { if (direction === 'down' && !entry.isIntersecting) { return true } if (direction === 'up' && entry.isIntersecting) { return true } return false}
onIntersect()
của mình cho phù hợp:
Mã:
const onIntersect = (entries) => { entries.forEach((entry) => { setScrollDirection() /* Không làm gì nếu không cần cập nhật */ if (!shouldUpdate(entry)) return const target = getTargetSection(entry.target) updateColors(target) })}
Mã:
header { transition: background-color 200ms, color 200ms;}
Xem Bút Tiệm Kem Happy Face – Bước 3 của Michelle Barker.
Thêm Hiệu ứng Động Đánh dấu
Tiếp theo, chúng ta sẽ thêm một đánh dấu vào tiêu đề, đánh dấu này sẽ cập nhật vị trí của nó khi chúng ta cuộn đến các phần khác nhau. Chúng ta có thể sử dụng một phần tử giả cho việc này, vì vậy chúng ta không cần thêm bất cứ thứ gì vào HTML của mình. Chúng ta sẽ cung cấp cho nó một số kiểu CSS đơn giản để định vị nó ở góc trên bên trái của tiêu đề và cung cấp cho nó một màu nền. Chúng ta đang sử dụngcurrentColor
cho việc này, vì nó sẽ lấy giá trị của màu văn bản tiêu đề:
Mã:
header::after { content: ''; position: absolute; top: 0; left: 0; height: 0.4rem; background-color: currentColor;}
Mã:
header::after { content: ''; position: absolute; top: 0; left: 0; height: 0.4rem; width: var(--markerWidth, 0); background-color: currentColor; transform: translate3d(var(--markerLeft, 0), 0, 0);}
Mã:
const updateMarker = (target) => { const id = target.id /* Không làm gì nếu không có ID mục tiêu */ if (!id) return /* Tìm liên kết điều hướng tương ứng hoặc sử dụng liên kết đầu tiên */ let link = headerLinks.find((el) => { return el.getAttribute('href') === `#${id}` }) link = link || headerLinks[0] /* Lấy các giá trị và thiết lập các thuộc tính tùy chỉnh */ const distanceFromLeft = link.getBoundingClientRect().left header.style.setProperty('--markerWidth', `${link.clientWidth}px`) header.style.setProperty('--markerLeft', `${distanceFromLeft}px`)}
Mã:
const onIntersect = (entries) => { entries.forEach((entry) => { setScrollDirection() if (!shouldUpdate(entry)) return const target = getTargetSection(entry.target) updateColors(target) updateMarker(target) })}
updateMarker()
, sử dụng phần đầu tiên làm mục tiêu:
Mã:
document.addEventListener('readystatechange', e => { if (e.target.readyState === 'complete') { updateMarker(sections[0]) }})
width
, chúng ta có thể sử dụng will-change
để cho phép trình duyệt thực hiện tối ưu hóa.
Mã:
header::after { transition: transform 250ms, width 200ms, background-color 200ms; will-change: width;}
Cuộn mượt
Để hoàn thiện, thật tuyệt nếu khi người dùng nhấp vào liên kết, họ sẽ được cuộn mượt mà xuống trang, thay vì nhảy đến phần đó. Ngày nay, chúng ta có thể thực hiện ngay trong CSS của mình, không cần JS! Để có trải nghiệm dễ tiếp cận hơn, bạn nên tôn trọng sở thích chuyển động của người dùng bằng cách chỉ triển khai cuộn mượt mà nếu họ chưa chỉ định sở thích giảm chuyển động trong cài đặt hệ thống của mình:
Mã:
@media (prefers-reduced-motion: no-preference) { .scroller { scroll-behavior: smooth; }}
Bản demo cuối cùng
Kết hợp tất cả các bước trên lại với nhau sẽ cho ra bản demo hoàn chỉnh.Xem Bút [Ví dụ về Quán kem Happy Face – Intersection Observer](https://codepen.io/smashingmag/pen/XWRXVXQ) của Michelle Barker.
Xem Bút Ví dụ về Quán kem Happy Face – Intersection Observer của Michelle Barker.
Hỗ trợ trình duyệt
Intersection Observer được hỗ trợ rộng rãi trong các trình duyệt hiện đại. Khi cần thiết, nó có thể được polyfilled cho các trình duyệt cũ hơn — nhưng tôi thích áp dụng phương pháp cải tiến dần dần khi có thể. Trong trường hợp tiêu đề của chúng tôi, việc cung cấp một phiên bản đơn giản, không thay đổi cho các trình duyệt không hỗ trợ sẽ không gây ảnh hưởng nhiều đến trải nghiệm của người dùng.Để phát hiện Intersection Observer có được hỗ trợ hay không, chúng tôi có thể sử dụng lệnh sau:
Mã:
if ('IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype) { /* Mã để thực thi nếu IO được hỗ trợ */} else { /* Mã để thực thi nếu không được hỗ trợ */}
Tài nguyên
Đọc thêm về Intersection Observer:- Tài liệu mở rộng, với một số ví dụ thực tế từ MDN
- Intersection Observer công cụ trực quan hóa
- Khả năng hiển thị phần tử thời gian với Intersection Observer API – một hướng dẫn khác từ MDN, hướng dẫn cách sử dụng IO để theo dõi khả năng hiển thị quảng cáo
- Bài viết này của Denys Mishunov đề cập đến một số cách sử dụng khác cho IO, bao gồm cả tài sản tải chậm. Mặc dù hiện tại ít cần thiết hơn (nhờ thuộc tính
loading
), nhưng vẫn còn nhiều điều cần tìm hiểu ở đây.
Đọc thêm
- Tạo hoạt ảnh UI có thể truy cập
- Kiểu điều khiển biểu mẫu nâng cao với Selectmenu và API neo
- Tạo biểu mẫu nhiều bước hiệu quả để có trải nghiệm người dùng tốt hơn
- Cuộc chiến giành luồng chính