Intersection Observer — это современный API браузера для отслеживания пересечения DOM-элементов с областью видимости или другим родительским элементом. В Atomico он интегрируется естественно через реактивные хуки и предоставляет удобные средства для управления состоянием видимости компонентов.
Для использования Intersection Observer необходимо создать
наблюдатель и связать его с целевым элементом. В Atomico это делается
через хук useEffect или через реактивные свойства:
import { c, useRef, useEffect } from "atomico";
function VisibilityComponent() {
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
console.log("Элемент виден");
} else {
console.log("Элемент скрыт");
}
},
{
root: null, // отслеживание относительно viewport
threshold: 0.5 // срабатывание, когда 50% элемента видны
}
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return <div ref={ref} style={{ height: "200px", background: "#ccc" }}>Наблюдаемый элемент</div>;
}
export const visibilityComponent = c(VisibilityComponent);
Ключевые моменты:
root — элемент, относительно которого отслеживается
видимость. null соответствует viewport.threshold — процент видимой области элемента, при
котором срабатывает callback. Может быть числом или массивом.observer.disconnect() — обязательно для очистки
ресурсов.Atomico поддерживает реактивные переменные через
useState и useEffect. Можно хранить состояние
видимости компонента в реактивной переменной:
import { c, useState, useRef, useEffect } from "atomico";
function ReactiveVisibility() {
const [visible, setVisible] = useState(false);
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setVisible(entry.isIntersecting);
}, { threshold: 0.1 });
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return (
<div ref={ref} style={{ height: "150px", background: visible ? "green" : "red" }}>
{visible ? "Виден" : "Скрыт"}
</div>
);
}
export const reactiveVisibility = c(ReactiveVisibility);
Преимущество такой интеграции — автоматический ререндер компонента
при изменении состояния visible.
Для наблюдения сразу нескольких элементов удобно использовать массив рефов или делегировать регистрацию через querySelectorAll:
import { c, useEffect } from "atomico";
function MultiObserver() {
useEffect(() => {
const elements = document.querySelectorAll(".observe");
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.background = "blue";
} else {
entry.target.style.background = "gray";
}
});
},
{ threshold: 0.2 }
);
elements.forEach(el => observer.observe(el));
return () => observer.disconnect();
}, []);
return (
<>
<div className="observe" style={{ height: "100px", margin: "10px" }}>Элемент 1</div>
<div className="observe" style={{ height: "100px", margin: "10px" }}>Элемент 2</div>
<div className="observe" style={{ height: "100px", margin: "10px" }}>Элемент 3</div>
</>
);
}
export const multiObserver = c(MultiObserver);
Особенности:
Intersection Observer имеет несколько ключевых опций:
null, используется viewport."0px 0px -50px 0px".[0, 0.25, 0.5, 1].Пример с rootMargin и массивом threshold:
const observer = new IntersectionObserver(([entry]) => {
console.log(entry.intersectionRatio);
}, {
root: document.querySelector(".scroll-container"),
rootMargin: "0px 0px -20px 0px",
threshold: [0, 0.25, 0.5, 0.75, 1]
});
Одна из популярных практик — ленивое подгружение изображений при появлении в viewport:
import { c, useRef, useEffect } from "atomico";
function LazyImage({ src, placeholder }) {
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && ref.current.dataset.src) {
ref.current.src = ref.current.dataset.src;
ref.current.removeAttribute("data-src");
observer.unobserve(ref.current);
}
}, { threshold: 0.1 });
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return <img ref={ref} data-src={src} src={placeholder} alt="Lazy loaded" />;
}
export const lazyImage = c(LazyImage);
Преимущества:
disconnect при удалении элементов
предотвращает утечки памяти.threshold и rootMargin позволяет
точнее контролировать срабатывание и минимизировать лишние
callback.При серверном рендеринге необходимо проверять доступность
window:
useEffect(() => {
if (typeof window === "undefined") return;
// Инициализация Intersection Observer
}, []);
Это предотвращает ошибки при рендеринге на сервере и делает компонент безопасным для универсальных приложений на Atomico.
useRef
и useEffect.Использование этого подхода позволяет создавать высокопроизводительные и отзывчивые интерфейсы без лишних обработчиков скролла и сложной логики расчёта позиции элементов.