רכיבי מדיה (<video> ו-<audio>) והמשאבים שמשויכים אליהם (כמו תמונות <video poster> ומטא-נתונים שנטענו מראש) צורכים רוחב פס משמעותי של הרשת והאחסון במהלך טעינת הדף הראשונית. גם כשמגדירים את preload="metadata" או את preload="none" ב-<video>, הדפדפן מאחזר באופן מיידי את תמונת ה-poster שלו, ו-preload="metadata" עדיין יוזם בקשות טווח לכותרות מדיה בכל נגן בדף.
הוספת loading="lazy" לרכיבי <video> ו-<audio> מחוץ למסך מורה לדפדפן לדחות את כל הבקשות לאחזור מהרשת של מדיה – כולל הורדות של תמונות poster, רמזים של preload והפעלה של autoplay – עד שהרכיב מגיע למרחק שחושב על ידי הדפדפן מאזור התצוגה החזותי.
איך מטמיעים
- הוספת
loading="lazy"למדיה שאינה מופיעה במסך: צריך להחיל אתloading="lazy"על רכיבי<video>ו-<audio>שממוקמים בחלק הנגלל. השמיטו את המאפייןloadingבמדיה בחלק העליון וקבוע של הדף, כדי שברירת המחדל תהיהeager. - הגדירו ערכים מפורשים של
widthו-heightברכיבי<video>: תמיד צריך לציין מאפייניwidthו-heightשל מספרים שלמים (בפיקסלים של CSS) ב-<video>. סרטונים שלא נטענו מוגדרים כברירת מחדל למידות300×150. בלי הגדרת גודל מפורשת, יכול להיות שסרטון בטעינה מדורגת לא יחצה את אזור התצוגה הגלוי ולעולם לא ייטען, או שהוא יגרום לשינויי פריסה משבשים (CLS) כשהוא ייטען. - לכלול את המאפיין
controlsברכיבי<audio>: תמיד צריך לכלול אתcontrolsב-<audio loading="lazy">. מכיוון שלרכיבי<audio>אין תיבה חזותית פנימית בלי אמצעי בקרה של הדפדפן, רכיב<audio loading="lazy">בליcontrolsלא יכול להצטלב באופן גלוי עם אזור התצוגה, ולכן הוא אף פעם לא ייטען או יופעל באופן אוטומטי. - מאפייני הקואורדינטות
preloadו-poster:- ב-
<video loading="lazy">, גם אחזור התמונה שלposterוגם ההתנהגות שלpreload(none,metadataאוauto) נדחים עד שהסרטון מתקרב לאזור התצוגה. - צריך לשלב את
loading="lazy"עםpreload="none"כשהמשתמש צריך ללחוץ באופן מפורש על הפעלה אחרי גלילה למדיה, או עםpreload="metadata"כשנתוני משך הזמן והפריים הראשוני צריכים להתמלא באופן אוטומטי ברגע שהנגן נכנס לאזור התצוגה.
- ב-
- טיפול בטעינה מדורגת של
autoplayבאופן בטוח: כשמשלבים אתloading="lazy"עםautoplayב-<video>מושתק (או<audio controls>בהצטרפות), ההפעלה וההורדה ממתינות עד שהרכיב מתקרב לאזור התצוגה. תמיד צריך לכבד את@media (prefers-reduced-motion: reduce)לפני הפעלה אוטומטית של מדיה עם תנועה.
קוד לדוגמה
<!-- 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);
});
שיטות מומלצות
- חובה: תמיד צריך להגדיר מאפיינים מפורשים של
widthו-heightברכיבי<video>. לסרטונים שלא נטענו יש גודל פנימי של300×150, והם לא ייטענו אף פעם אם הם לא מצטלבים עם חלק גלוי בדף. - חובה: תמיד צריך לכלול את המאפיין
controlsברכיבי<audio loading="lazy">. רכיבי<audio>בטעינה מדורגת (lazy loading) בליcontrolsלא מוצגים באופן גלוי, ולעולם לא ייטענו או יופעלו באופן אוטומטי. - אל תפעילו את
loading="lazy"על רכיבים בחלק העליון של הדף או על רכיבי LCP (המהירות שבה נטען רכיב התוכן הכי גדול)<video>, כי זה מעכב את השליפה של נתוני התמונות והסרטונים שלposter. - מומלץ להשמיט את המאפיין
loadingכשרוצים להשתמש בטעינה מוקדמת כברירת מחדל, במקום לכתוב במפורשloading="eager". - אל תסתמכו על האירוע
windowloadכדי לזהות מתי רכיבי<video>או<audio>בטעינה מדורגת נטענו. האירועloadמופעל רק על סמך מדיה שנטענת באופן מיידי, גם אם רכיב מדיה מסוגloading="lazy"נמצא בהתחלה בתוך אזור התצוגה החזותי. - חשוב לשים לב שדפדפנים דוחים מדיה מסוג
loading="lazy"רק כש-JavaScript מופעל (אמצעי למניעת מעקב שמונע משרתים להסיק את מיקום הגלילה באמצעות בקשות מדיה כש-scripting מושבת).
אסטרטגיית גיבוי
אם יעד ה-Baseline לא תומך ב-loading="lazy" ב-<video> וב-<audio>, צריך לבחור בין שיפור הדרגתי (progressive enhancement) לבין חלופה של IntersectionObserver, בהתאם למידת החשיבות של חיסכון ברוחב הפס:
שיפור הדרגתי (ברירת המחדל המומלצת)
דפדפנים שלא מזהים את loading="lazy" ב-<video> וב-<audio> מתעלמים מהמאפיין וחוזרים לטעינה רגילה שנקבעת על ידי המאפיין preload. התאמה של loading="lazy" עם preload="none" או preload="metadata" מבטיחה שדפדפנים לא נתמכים עדיין יימנעו מהורדה של קובץ המדיה המלא מראש, למרות שתמונות <video poster> וכותרות preload="metadata" יאוחזרו באופן מיידי.
קוד חלופי בהתאמה אישית (IntersectionObserver)
אם יעד ה-Baseline דורש דחייה קפדנית של הורדות <video poster>, בקשות preload="metadata" או הפעלה שמופעלת על ידי אזור התצוגה בדפדפנים ללא תמיכה מובנית, צריך לזהות את התכונה Object.hasOwn(HTMLMediaElement.prototype, 'loading') ואז לחזור ל-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);
}
}