Нативный loading="lazy" действительно слабоват — браузер всё равно создаёт iframe-контексты заранее. Вот проверенные решения:
1.
Facade Pattern — главный приём
Вместо iframe сразу показываем лёгкую «заглушку» с превью. Iframe создаётся только по клику:
class VideoFacade extends HTMLElement {
connectedCallback() {
const videoId = this.dataset.videoId;
const title = this.dataset.title || 'Видео';
this.innerHTML = `
<div class="video-facade" role="button" aria-label="Воспроизвести ${title}">
<img src="/thumbs/${videoId}.jpg"
alt="${title}"
loading="lazy"
decoding="async">
<svg class="play-btn" viewBox="0 0 68 48">
<path d="M66.5 7.7c-.8-2.9-2.5-5.4-5.4-6.2C55.8.1 34 0 34 0S12.2.1 6.9 1.5c-2.9.8-4.6 3.3-5.4 6.2C.1 13 0 24 0 24s.1 11 1.5 16.3c.8 2.9 2.5 5.4 5.4 6.2C12.2 47.9 34 48 34 48s21.8-.1 27.1-1.5c2.9-.8 4.6-3.3 5.4-6.2C67.9 35 68 24 68 24s-.1-11-1.5-16.3z" fill="#212121" fill-opacity=".8"/>
<path d="M45 24L27 14v20" fill="#fff"/>
</svg>
</div>
`;
this.addEventListener('click', () => this.activate(), { once: true });
}
activate() {
const iframe = document.createElement('iframe');
iframe.src = `https://player.example.com/ifr/${this.dataset.videoId}?autoplay=1&muted=1`;
iframe.allow = 'autoplay; fullscreen';
iframe.allowFullscreen = true;
this.replaceChildren(iframe);
}
}
customElements.define('video-facade', VideoFacade);
<!-- Было: тяжёлый iframe -->
<!-- Стало: ~2KB вместо ~500KB на видео -->
<video-facade
data-video-id="4854766"
data-title="Название ролика">
</video-facade>
2.
Intersection Observer + виртуализация превью
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const facade = entry.target;
const img = facade.querySelector('img[data-src]');
if (img) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
observer.unobserve(facade);
}
});
}, {
rootMargin: '200px 0px', // предзагрузка за 200px до viewport
threshold: 0
});
document.querySelectorAll('video-facade').forEach(el => observer.observe(el));
3. CSS content-visibility — разгрузка рендеринга
.video-grid {
/* Браузер пропускает layout/paint для невидимых элементов */
content-visibility: auto;
contain-intrinsic-size: 0 300px; /* примерная высота блока */
}
.video-facade {
contain: layout style paint;
aspect-ratio: 16/9;
}
4. Готовые решения
lite-youtube-embed, 1.3KB,
Web Component для YouTube
lite-vimeo-embed, 1.2KB, То же для
Vimeo
lazysizes + unveilhooks,3KB,
Универсальный lazy-load