Лучшие практики оптимизации показа видео на сайте для максимально быстрой загрузки

Видео — один из самых тяжёлых ресурсов на веб-страницах, и их оптимизация критически важна для скорости загрузки.

Проблема

Видео файлы занимают много места, что приводит к долгой начальной загрузке страницы, особенно при нескольких видео или на мобильных устройствах с медленным интернетом. Это ухудшает метрики Core Web Vitals: Largest Contentful Paint (LCP) растёт, Cumulative Layout Shift (CLS) возникает из-за смены layout без указанных размеров, а Time to Interactive (TTI) замедляется из-за фоновой загрузки. Браузеры по умолчанию загружают видео сразу (preload="auto"), тратя трафик впустую, если пользователь не досмотрит.

Общие подходы решения

Оптимизация файлов: Используйте современные кодеки (AV1/VP9/H.265 вместо H.264), сжимайте видео (Handbrake/FFmpeg), предоставляйте несколько форматов через <source> (MP4 + WebM), adaptive bitrate streaming (HLS/DASH) для подстройки под скорость соединения.

Content Delivery Network (CDN): Разместите видео на CDN (Cloudflare, AWS CloudFront, BunnyCDN, Akamai), чтобы доставлять контент с ближайшего edge-сервера, снижая latency на 50-80% и справляясь с пиковыми нагрузками без перегрузки origin-сервера. Настройте долгосрочный кэш для сегментов (TTL 1 год для .ts/.mp4), короткий для манифестов (.m3u8/.mpd — 5 сек), origin shield для защиты источника и поддержку range-запросов для seeking. Замените src на CDN-URL: <source src="https://cdn.example.com/video.mp4"> — это ускорит загрузку глобально.

Отложенная загрузка:

  • Атрибут loading="lazy" (нативно в Chrome/Edge/Firefox для <video>).
  • preload="none" или "metadata" — не загружать видео до взаимодействия.
  • Poster-изображение вместо видео до клика.
  • Lazy loading с Intersection Observer API: загружать src только при входе в viewport.

Дополнительно: Укажите width/height для предотвращения CLS, используйте CDN, избегайте autoplay/muted, паузируйте видео вне viewport. Хостинг на YouTube/Vimeo с их embed-кодами для оптимизации.

Варианты решения

Vanilla JS

Используйте нативный loading="lazy":

<video
	loading="lazy"
	width="640"
	height="360"
	poster="poster.jpg"
	preload="none"
	controls
>
	<source src="video.mp4" type="video/mp4" />
</video>

Для старых браузеров — Intersection Observer:

const videos = document.querySelectorAll('video[data-src]');
const observer = new IntersectionObserver((entries) => {
	entries.forEach((entry) => {
		if (entry.isIntersecting) {
			const video = entry.target;
			video.src = video.dataset.src;
			video.load();
			observer.unobserve(video);
		}
	});
});
videos.forEach((video) => observer.observe(video));

HTML: <video data-src="video.mp4" ...>

jQuery

Аналогично Vanilla, но с jQuery-селекторами (Observer — нативный):

$('video[data-src]').each(function () {
	const observer = new IntersectionObserver((entries) => {
		entries.forEach((entry) => {
			if (entry.isIntersecting) {
				$(entry.target).attr('src', $(entry.target).data('src')).get(0).load();
				observer.unobserve(entry.target);
			}
		});
	});
	observer.observe(this);
});

Или плагин jquery.lazyload (но предпочтите нативный).

React

Создайте хук useIntersectionObserver:

import { useEffect, useRef, useState } from 'react';

const useIsVisible = (ref) => {
	const [isVisible, setIsVisible] = useState(false);
	useEffect(() => {
		const observer = new IntersectionObserver(([entry]) =>
			setIsVisible(entry.isIntersecting),
		);
		if (ref.current) observer.observe(ref.current);
		return () => observer.disconnect();
	}, []);
	return isVisible;
};

const LazyVideo = ({ src, ...props }) => {
	const videoRef = useRef();
	const isVisible = useIsVisible(videoRef);
	return (
		<video
			ref={videoRef}
			src={isVisible ? src : ''}
			{...props}
			preload='none'
		/>
	);
};

Использование: <LazyVideo src="video.mp4" />

Vue

Используйте composable или директиву (vue-intersection-observer). Пример с Composition API:

<template>
	<video ref="videoRef" :src="videoSrc" preload="none" controls />
</template>

<script setup>
import { ref, onMounted } from 'vue';

const videoRef = ref(null);
const videoSrc = ref('');

onMounted(() => {
	const observer = new IntersectionObserver(([entry]) => {
		if (entry.isIntersecting) {
			videoSrc.value = 'video.mp4'; // или props.src
			observer.disconnect();
		}
	});
	if (videoRef.value) observer.observe(videoRef.value);
});
</script>

Для YouTube/Vimeo — vue-lazytube.

Ссылки на репозитории и ресурсы

Эти практики ускоряют загрузку на 50-80% для страниц с видео.