Как отключить анимацию за пределами Viewport?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Отключение анимаций за пределами Viewport
Зачем это нужно
Анимации элементов, которые находятся вне видимой области (Viewport), напрасно потребляют ресурсы браузера и батареи устройства. Отключение таких анимаций улучшает производительность приложения, особенно на мобильных устройствах и при наличии множества анимируемых элементов.
Способ 1: Intersection Observer API
Самый современный и рекомендуемый подход — использование Intersection Observer API, который позволяет отслеживать видимость элемента в Viewport:
function observeElement(element) {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Элемент видим — запускаем анимацию
entry.target.classList.add("animate-in");
} else {
// Элемент невидим — останавливаем анимацию
entry.target.classList.remove("animate-in");
}
});
},
{ threshold: 0.1 }
);
observer.observe(element);
}
// Применяем ко всем элементам с классом .animated
document.querySelectorAll(".animated").forEach(observeElement);
Способ 2: CSS-решение с prefers-reduced-motion
Учитываем предпочтения пользователя по редукции движений:
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
if (prefersReducedMotion) {
document.body.classList.add("reduce-motion");
}
// В CSS:
// .animate-in { animation: slideIn 0.5s ease-out; }
// .reduce-motion .animate-in { animation: none; }
Способ 3: Комбинированный подход
Объединяем Intersection Observer с проверкой редукции движений:
class AnimationManager {
constructor() {
this.prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
}
observe(element) {
if (this.prefersReducedMotion) {
return; // Не наблюдаем, если движения отключены
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
entry.target.style.animationPlayState = entry.isIntersecting
? "running"
: "paused";
});
},
{ threshold: 0.1 }
);
observer.observe(element);
}
}
const manager = new AnimationManager();
document.querySelectorAll(".animated").forEach((el) =>
manager.observe(el)
);
Способ 4: React Hook для Intersection Observer
Для React приложений рекомендуется создать кастомный хук:
function useInView(ref, options = {}) {
const [isInView, setIsInView] = React.useState(false);
React.useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
setIsInView(entry.isIntersecting);
},
{ threshold: 0.1, ...options }
);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, [ref, options]);
return isInView;
}
// Использование:
function AnimatedComponent() {
const ref = React.useRef(null);
const isInView = useInView(ref);
return (
<div
ref={ref}
className={isInView ? "animate-in" : ""}
>
Анимированный контент
</div>
);
}
Лучшие практики
- Используйте Intersection Observer — это стандарт W3C, поддерживается всеми современными браузерами
- Учитывайте
prefers-reduced-motion— соблюдайте WCAG рекомендации для доступности - Установите правильный
threshold— определите, на сколько процентов элемент должен быть видимым перед запуском анимации - Отписывайтесь от Observer — вызывайте
disconnect()в cleanup функции для предотвращения утечек памяти - Тестируйте на мобильных устройствах — убедитесь, что оптимизация работает корректно
Совместимость
Intersection Observer API поддерживается всеми современными браузерами. Для старых браузеров (IE 11) можно использовать polyfill: intersection-observer npm пакет.