Các phần tử đa phương tiện (<video> và <audio>) và tài nguyên liên quan (chẳng hạn như hình ảnh <video poster> và siêu dữ liệu được tải trước) tiêu tốn đáng kể băng thông mạng và bộ nhớ trong quá trình tải trang ban đầu. Ngay cả khi preload="metadata" hoặc preload="none" được đặt trên <video>, trình duyệt vẫn sẽ tìm nạp ngay hình ảnh poster và preload="metadata" vẫn bắt đầu các yêu cầu về dải ô cho tiêu đề nội dung nghe nhìn trên mọi trình phát trên trang.
Việc thêm loading="lazy" vào các phần tử <video> và <audio> ngoài màn hình sẽ hướng dẫn trình duyệt trì hoãn tất cả yêu cầu mạng về nội dung nghe nhìn (bao gồm cả việc tải hình ảnh poster xuống, gợi ý preload và thực thi autoplay) cho đến khi phần tử đạt đến khoảng cách được trình duyệt tính toán từ khung nhìn trực quan.
Cách triển khai
- Thêm
loading="lazy"vào nội dung nghe nhìn ngoài màn hình: Áp dụngloading="lazy"cho các phần tử<video>và<audio>nằm dưới màn hình đầu tiên. Bỏ qua thuộc tínhloadingtrên nội dung nghe nhìn trong màn hình đầu tiên để thuộc tính này mặc định làeager. - Đặt
widthvàheightrõ ràng trên các phần tử<video>: Luôn chỉ định các thuộc tínhwidthvàheightlà số nguyên (tính bằng pixel CSS) trên<video>. Video chưa tải có kích thước mặc định là300×150; nếu không có kích thước rõ ràng, video tải từng phần có thể không giao nhau với khung nhìn hiển thị và không bao giờ tải, hoặc gây ra sự thay đổi bố cục gây gián đoạn (CLS) khi tải. - Thêm thuộc tính
controlsvào các phần tử<audio>: Luôn thêmcontrolsvào<audio loading="lazy">. Vì các phần tử<audio>không có hộp hình ảnh nội tại nếu không có các chế độ điều khiển của trình duyệt, nên phần tử<audio loading="lazy">không cócontrolskhông thể giao nhau một cách rõ ràng với khung nhìn và sẽ không bao giờ tải hoặc tự động phát. - Thuộc tính toạ độ
preloadvàposter:- Trên
<video loading="lazy">, cả hoạt động tìm nạp hình ảnhpostervà hành vipreload(none,metadatahoặcauto) đều bị trì hoãn cho đến khi video tiếp cận khung nhìn. - Ghép nối
loading="lazy"vớipreload="none"khi người dùng phải nhấn nút phát một cách rõ ràng sau khi di chuyển đến nội dung nghe nhìn, hoặcpreload="metadata"khi thời lượng và dữ liệu khung hình ban đầu sẽ tự động điền sau khi trình phát vào khung nhìn.
- Trên
- Xử lý
autoplaytải từng phần một cách an toàn: Khiloading="lazy"được kết hợp vớiautoplaytrên một<video>bị tắt tiếng (hoặc<audio controls>chọn sử dụng), quá trình phát và tải xuống sẽ chờ cho đến khi phần tử tiếp cận khung nhìn. Luôn tuân thủ@media (prefers-reduced-motion: reduce)trước khi tự động phát nội dung nghe nhìn chuyển động.
Mã mẫu
<!-- Above-the-fold hero video: DO NOT use loading="lazy".
Omit loading (defaults to "eager") so the poster and initial media load immediately. -->
<video
controls
width="1280"
height="720"
poster="/images/hero-poster.jpg"
preload="metadata"
>
<source src="/media/keynote.webm" type="video/webm">
<source src="/media/keynote.mp4" type="video/mp4">
<track src="/media/keynote-en.vtt" kind="captions" srclang="en" label="English">
</video>
<!-- Below-the-fold video player:
MANDATORY: Specify integer width and height attributes so the unloaded video
has a non-zero layout box to intersect the viewport and avoids layout shift.
Both the poster image and preload="metadata" are deferred until near the viewport. -->
<video
controls
loading="lazy"
width="800"
height="450"
poster="/images/tutorial-poster.jpg"
preload="metadata"
>
<source src="/media/tutorial.webm" type="video/webm">
<source src="/media/tutorial.mp4" type="video/mp4">
<track src="/media/tutorial-en.vtt" kind="captions" srclang="en" label="English">
</video>
<!-- Below-the-fold inline product loop:
With loading="lazy" and autoplay, downloading and playback wait until the
video approaches the viewport. Include muted and playsinline for autoplay. -->
<video
controls
loading="lazy"
autoplay
loop
muted
playsinline
width="640"
height="360"
poster="/images/feature-loop-poster.jpg"
>
<source src="/media/feature-loop.webm" type="video/webm">
<source src="/media/feature-loop.mp4" type="video/mp4">
<track src="/media/feature-loop-en.vtt" kind="captions" srclang="en" label="English">
</video>
<!-- Below-the-fold audio player:
MANDATORY: Include the controls attribute. Lazy-loaded <audio> without
controls is invisible, cannot intersect the viewport, and will never load. -->
<figure>
<figcaption id="episode-title">Episode 42: Browser Architecture Deep Dive</figcaption>
<audio
controls
loading="lazy"
preload="metadata"
src="/media/episode-42.mp3"
aria-labelledby="episode-title"
aria-details="episode-transcript"
></audio>
<details id="episode-transcript">
<summary>View transcript</summary>
<p>Transcript content for Episode 42...</p>
</details>
</figure>
// Pause autoplaying videos when the user prefers reduced motion
const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
function syncReducedMotion(prefersReducedMotion) {
for (const video of document.querySelectorAll('video[autoplay]')) {
if (prefersReducedMotion) {
video.removeAttribute('autoplay');
video.pause();
}
}
}
syncReducedMotion(reducedMotionQuery.matches);
reducedMotionQuery.addEventListener('change', (event) => {
syncReducedMotion(event.matches);
});
Các phương pháp hay nhất
- BẮT BUỘC: Luôn đặt rõ ràng các thuộc tính
widthvàheighttrên các phần tử<video>. Video chưa tải có kích thước nội tại là300×150và sẽ không bao giờ tải nếu không giao nhau với một phần hiển thị của trang. - BẮT BUỘC: Luôn thêm thuộc tính
controlsvào các phần tử<audio loading="lazy">. Các phần tử<audio>được tải từng phần mà không cócontrolssẽ không được hiển thị và sẽ không bao giờ tải hoặc tự động phát. - KHÔNG áp dụng
loading="lazy"cho các phần tử trong màn hình đầu tiên hoặc Nội dung lớn nhất hiển thị (LCP)<video>, vì việc này sẽ làm chậm quá trình tìm nạp cả dữ liệu hình ảnh và videoposter. - NÊN bỏ qua thuộc tính
loadingkhi muốn tải nhanh mặc định thay vì viết rõ ràngloading="eager". - KHÔNG dựa vào sự kiện
windowloadđể phát hiện thời điểm các phần tử<video>hoặc<audio>tải từng phần đã tải xong. Sự kiệnloadchỉ kích hoạt dựa trên nội dung nghe nhìn được tải sớm, ngay cả khi phần tử nghe nhìnloading="lazy"ban đầu nằm trong khung nhìn trực quan. - LƯU Ý rằng trình duyệt chỉ trì hoãn nội dung nghe nhìn
loading="lazy"khi JavaScript được bật (một biện pháp chống theo dõi để ngăn máy chủ suy luận vị trí cuộn thông qua các yêu cầu nội dung nghe nhìn khi tính năng viết tập lệnh bị tắt).
Chiến lược dự phòng
Nếu mục tiêu Baseline của bạn không hỗ trợ loading="lazy" trên <video> và <audio>, hãy chọn giữa cải tiến tăng dần và phương án dự phòng IntersectionObserver dựa trên mức độ nghiêm ngặt của việc bảo toàn băng thông:
Cải tiến tăng dần (mặc định được đề xuất)
Các trình duyệt không nhận dạng được loading="lazy" trên <video> và <audio> sẽ bỏ qua thuộc tính này và quay lại chế độ tải tiêu chuẩn chịu sự điều chỉnh của thuộc tính preload. Việc ghép nối loading="lazy" với preload="none" hoặc preload="metadata" đảm bảo các trình duyệt không được hỗ trợ vẫn tránh tải trước toàn bộ tệp nội dung nghe nhìn, mặc dù hình ảnh <video poster> và tiêu đề preload="metadata" sẽ được tìm nạp nhanh chóng.
Mã dự phòng tuỳ chỉnh (IntersectionObserver)
Khi mục tiêu Đường cơ sở yêu cầu trì hoãn nghiêm ngặt việc tải <video poster> xuống, yêu cầu preload="metadata" hoặc phát do khung nhìn kích hoạt trong các trình duyệt không có tính năng hỗ trợ gốc, hãy phát hiện tính năng Object.hasOwn(HTMLMediaElement.prototype, 'loading'), sau đó quay lại IntersectionObserver:
// Feature-detect native media lazy loading on HTMLMediaElement.prototype
const supportsLazyMedia = Object.hasOwn(HTMLMediaElement.prototype, 'loading');
if (!supportsLazyMedia) {
const lazyMediaElements = document.querySelectorAll(
'video[loading="lazy"], audio[loading="lazy"][controls]',
);
// Example-only rootMargin value; adjust distance threshold for your layout
const mediaObserver = new IntersectionObserver(
(entries, observer) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const media = entry.target;
// Restore deferred poster and preload attributes when entering the viewport
if (media.dataset.poster) {
media.poster = media.dataset.poster;
delete media.dataset.poster;
}
if (media.dataset.preload) {
media.preload = media.dataset.preload;
delete media.dataset.preload;
}
if (media.dataset.src) {
media.src = media.dataset.src;
delete media.dataset.src;
}
for (const source of media.querySelectorAll('source[data-src]')) {
source.src = source.dataset.src;
delete source.dataset.src;
}
media.load();
observer.unobserve(media);
}
},
{ rootMargin: '200px 0px' },
);
for (const media of lazyMediaElements) {
// Hold back network fetches in unsupported browsers until intersection
if (media.hasAttribute('poster')) {
media.dataset.poster = media.getAttribute('poster');
media.removeAttribute('poster');
}
if (media.hasAttribute('preload') && media.getAttribute('preload') !== 'none') {
media.dataset.preload = media.getAttribute('preload');
}
media.preload = 'none';
mediaObserver.observe(media);
}
}