v147 · HTML
Compatibility Lab
Detects whether loading="lazy" works on <video> and <audio> elements via DOM attribute reflection. Runs a live probe injecting a video element with loading="lazy" and checking whether the attribute is reflected. Provides the IntersectionObserver fallback pattern for browsers without native media lazy loading.
API probes
Live attribute reflection test
video.loading — attribute reflection check
Click "Run probe" to test…
invalid value contract
The media loading property is an enumerated attribute. Unknown values are preserved in markup but the IDL property falls back to eager, matching the feature conformance suite.
Click "Try invalid values" to verify the fallback.
Native vs JS fallback comparison
Native CHROME 147+
Set the attribute and done:
Browser chooses threshold automatically based on connection speed and scroll velocity. Zero JavaScript. Works with prerender. Respects
<video loading="lazy" src="…">Browser chooses threshold automatically based on connection speed and scroll velocity. Zero JavaScript. Works with prerender. Respects
prefers-reduced-data.
JS Fallback ALL BROWSERS
Use
Observe → set src on entry.
Works everywhere but requires JavaScript, a custom threshold, and careful observer cleanup.
IntersectionObserver:<video data-src="…">Observe → set src on entry.
Works everywhere but requires JavaScript, a custom threshold, and careful observer cleanup.
Detecting which approach to use in this browser…
Fallback pattern
/* Detect native lazy loading on media elements */
const VIDEO_LAZY = (() => {
const v = document.createElement('video');
return 'loading' in v;
})();
const AUDIO_LAZY = (() => {
const a = document.createElement('audio');
return 'loading' in a;
})();
/* Apply lazy loading — native or IntersectionObserver */
function lazyLoad(el) {
if (VIDEO_LAZY && (el.tagName === 'VIDEO' || el.tagName === 'AUDIO')) {
el.loading = 'lazy';
if (el.dataset.src) {
el.src = el.dataset.src;
delete el.dataset.src;
}
return;
}
// Fallback: IntersectionObserver
el.src = '';
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const target = entry.target;
target.src = target.dataset.src;
observer.unobserve(target);
}
});
}, { rootMargin: '200px' }); // 200px buffer like Chrome's native threshold
observer.observe(el);
}
/* Usage */
document.querySelectorAll('video[data-src], audio[data-src]').forEach(lazyLoad);
references
implementation reference
Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗