Tippy.js предоставляет гибкий механизм для создания всплывающих
подсказок (tooltip), поддерживая различные события триггера, такие как
mouseenter, click, focus. Однако
иногда стандартных событий недостаточно, и требуется кастомный триггер,
основанный на наблюдении за элементом в видимой области окна браузера.
Для таких случаев идеально подходит
IntersectionObserver.
IntersectionObserver позволяет отслеживать момент
появления элемента на экране и автоматически инициировать открытие или
закрытие тултипа. Это особенно полезно для ленивой загрузки подсказок,
подсказок в каруселях, списках или при работе с бесконечной
прокруткой.
Пример простого тултипа с использованием IntersectionObserver:
import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
const target = document.querySelector('#my-element');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
tooltip.show();
} else {
tooltip.hide();
}
});
}, {
threshold: 0.5 // Элемент считается видимым, если 50% площади видны
});
const tooltip = tippy(target, {
content: 'Подсказка при появлении на экране',
trigger: 'manual', // Обязательный параметр для кастомного управления
});
observer.observe(target);
Ключевые моменты:
trigger: 'manual' отключает стандартные события и
позволяет управлять тултипом программно через методы
.show() и .hide().threshold определяет, насколько элемент должен
быть видим, чтобы сработал триггер. Можно задать массив, например
[0, 0.25, 0.5, 1], чтобы отслеживать различные состояния
видимости.observer.observe(target) инициирует
слежение за элементом.Для коллекций элементов, например, списков продуктов или карточек, удобно итерировать элементы и создавать отдельные тултипы для каждого:
const items = document.querySelectorAll('.product-card');
items.forEach(item => {
const tooltip = tippy(item, {
content: 'Информация о продукте',
trigger: 'manual',
});
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
entry.isIntersecting ? tooltip.show() : tooltip.hide();
});
}, { threshold: 0.5 });
observer.observe(item);
});
Важные детали:
delay в Tippy.js, чтобы добавлять
плавное появление при скролле.Tippy.js позволяет полностью интегрировать любые кастомные события с
помощью tippy.setProps() и tippy.state.
Пример:
const tooltip = tippy(target, {
content: 'Кастомный триггер через IntersectionObserver',
trigger: 'manual',
onShow(instance) {
console.log('Тултип показан', instance);
},
onHide(instance) {
console.log('Тултип скрыт', instance);
}
});
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
entry.isIntersecting
? tooltip.setProps({ content: 'Элемент на экране' }) && tooltip.show()
: tooltip.hide();
});
}, { threshold: 0.5 });
observer.observe(target);
Особенности:
onShow и onHide позволяют подключать
анимации, логику аналитики или динамическую подгрузку контента.setProps дает возможность менять контент или другие
параметры тултипа динамически при пересечении с viewport.При использовании IntersectionObserver с большим количеством элементов важно учитывать:
IntersectionObserver и передавать в
него множество элементов через observer.observe(element),
вместо создания отдельного observer на каждый элемент. Это снижает
нагрузку на браузер.const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const tooltip = entry.target._tippy;
entry.isIntersecting ? tooltip.show() : tooltip.hide();
});
}, { threshold: 0.5 });
document.querySelectorAll('.item').forEach(item => {
const tooltip = tippy(item, { content: 'Информация', trigger: 'manual' });
item._tippy = tooltip;
observer.observe(item);
});
Debounce для скролла: IntersectionObserver сам оптимизирован для работы со скроллом, но при сложных анимациях можно использовать throttle или debounce на callback функции.
Управление удалением элементов: При удалении
элементов из DOM важно вызывать observer.unobserve(element)
для предотвращения утечек памяти.
Для страниц с подгрузкой контента по скроллу (infinite scroll) можно автоматически привязывать тултипы к новым элементам:
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!entry.target._tippy) {
const tooltip = tippy(entry.target, { content: 'Новая карточка', trigger: 'manual' });
entry.target._tippy = tooltip;
}
entry.isIntersecting ? entry.target._tippy.show() : entry.target._tippy.hide();
});
}, { threshold: 0.5 });
const observeNewItems = (nodes) => {
nodes.forEach(node => observer.observe(node));
};
// Пример динамического добавления
const container = document.querySelector('#list');
const newNode = document.createElement('div');
newNode.classList.add('item');
container.appendChild(newNode);
observeNewItems([newNode]);
Преимущества:
trigger: 'manual' для полного
контроля.setProps и события
onShow/onHide для динамических изменений
контента.Такой подход делает Tippy.js максимально гибким для сложных
интерфейсов, где стандартные триггеры hover или
click не подходят, а управление через viewport является
ключевым.