templates/vitekit/__/vite-template-main/js/libs/watcher.ts
2026-04-12 21:03:18 +03:00

185 lines
8.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { FLS, uniqArray } from '../files/functions'
import { flsModules } from '../files/modules'
import { IWatcherConfig, IWatcherElement, IWatcherParams } from '../types'
// Наблюдатель объектов [всевидящее око]
// data-watch - можно писать значение для применения кастомного кода
// data-watch-root - родитель внутри которого наблюдать за объектом
// data-watch-margin - отступ
// data-watch-threshold - процент показа объекта для срабатывания
// data-watch-once - наблюдать только один раз
// _watcher-view - класс который добавляется при появлении объекта
class ScrollWatcher {
private config: IWatcherConfig
private observer: IntersectionObserver | null
constructor(props: Partial<IWatcherConfig>) {
const defaultConfig = {
logging: false,
}
this.config = Object.assign(defaultConfig, props)
this.observer = null
!document.documentElement.classList.contains('watcher') ? this.scrollWatcherRun() : null
}
// Обновляем конструктор
scrollWatcherUpdate(): void {
this.scrollWatcherRun()
}
// Запускаем конструктор
scrollWatcherRun(): void {
document.documentElement.classList.add('watcher')
this.scrollWatcherConstructor(document.querySelectorAll('[data-watch]') as NodeListOf<IWatcherElement>)
}
// Конструктор наблюдателей
scrollWatcherConstructor(items: NodeListOf<IWatcherElement>): void {
if (items.length) {
this.scrollWatcherLogging(`Проснулся, слежу за объектами (${items.length})...`)
// Уникализируем параметры
const uniqParams = uniqArray(
Array.from(items).map((item) => {
return `${
item.dataset.watchRoot ? item.dataset.watchRoot : null
}|${item.dataset.watchMargin ? item.dataset.watchMargin : '0px'}|${item.dataset.watchThreshold ? item.dataset.watchThreshold : 0}`
})
)
// Получаем группы объектов с одинаковыми параметрами,
// создаем настройки, инициализируем наблюдатель
uniqParams.forEach((uniqParam) => {
const uniqParamArray = uniqParam.split('|')
const paramsWatch: IWatcherParams = {
root: uniqParamArray[0],
margin: uniqParamArray[1],
threshold: uniqParamArray[2],
}
const groupItems = Array.from(items).filter((item) => {
const watchRoot = item.dataset.watchRoot ? item.dataset.watchRoot : null
const watchMargin = item.dataset.watchMargin ? item.dataset.watchMargin : '0px'
const watchThreshold = item.dataset.watchThreshold ? item.dataset.watchThreshold : '0'
if (
String(watchRoot) === paramsWatch.root &&
String(watchMargin) === paramsWatch.margin &&
String(watchThreshold) === paramsWatch.threshold
) {
return item
}
return false
})
const configWatcher = this.getScrollWatcherConfig(paramsWatch)
if (configWatcher) {
// Инициализация наблюдателя со своими настройками
this.scrollWatcherInit(groupItems, configWatcher)
}
})
} else {
this.scrollWatcherLogging('Сплю, нет объектов для слежения. ZzzZZzz')
}
}
// Функция создания настроек
getScrollWatcherConfig(paramsWatch: IWatcherParams): IntersectionObserverInit | null {
// Создаем настройки
const configWatcher: IntersectionObserverInit = {}
// Родитель, внутри которого ведется наблюдение
if (paramsWatch.root && document.querySelector(paramsWatch.root)) {
configWatcher.root = document.querySelector(paramsWatch.root)
} else if (paramsWatch.root !== 'null') {
this.scrollWatcherLogging(`Эмм... родительского объекта ${paramsWatch.root} нет на странице`)
return null
}
// Отступ срабатывания
configWatcher.rootMargin = paramsWatch.margin
if (!paramsWatch.margin.includes('px') && !paramsWatch.margin.includes('%')) {
this.scrollWatcherLogging(`Ой ой, настройку data-watch-margin нужно задавать в PX или %`)
return null
}
// Точки срабатывания
if (paramsWatch.threshold === 'prx') {
// Режим параллакса
const threshold: number[] = []
for (let i = 0; i <= 1.0; i += 0.005) {
threshold.push(i)
}
configWatcher.threshold = threshold
} else {
configWatcher.threshold = Array.isArray(paramsWatch.threshold)
? paramsWatch.threshold.map(Number)
: paramsWatch.threshold.split(',').map(Number)
}
return configWatcher
}
// Функция создания нового наблюдателя со своими настройками
scrollWatcherCreate(configWatcher: IntersectionObserverInit): void {
this.observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
this.scrollWatcherCallback(entry, observer)
})
}, configWatcher)
}
// Функция инициализации наблюдателя со своими настройками
scrollWatcherInit(items: IWatcherElement[], configWatcher: IntersectionObserverInit): void {
// Создание нового наблюдателя со своими настройками
this.scrollWatcherCreate(configWatcher)
// Передача наблюдателю элементов
if (this.observer) {
items.forEach((item) => {
this.observer?.observe(item)
})
}
}
// Функция обработки базовых действий точек срабатывания
scrollWatcherIntersecting(entry: IntersectionObserverEntry, targetElement: IWatcherElement): void {
if (entry.isIntersecting) {
// Видим объект
// добавляем класс
!targetElement.classList.contains('_watcher-view') ? targetElement.classList.add('_watcher-view') : null
this.scrollWatcherLogging(`Я вижу ${targetElement.classList}, добавил класс _watcher-view`)
} else {
// Не видим объект
// убираем класс
targetElement.classList.contains('_watcher-view') ? targetElement.classList.remove('_watcher-view') : null
this.scrollWatcherLogging(`Я не вижу ${targetElement.classList}, убрал класс _watcher-view`)
}
}
// Функция отключения слежения за объектом
scrollWatcherOff(targetElement: IWatcherElement, observer: IntersectionObserver): void {
observer.unobserve(targetElement)
}
// Функция вывода в консоль
scrollWatcherLogging(message: string): void {
this.config.logging ? FLS(`[Наблюдатель]: ${message}`) : null
}
// Функция обработки наблюдения
scrollWatcherCallback(entry: IntersectionObserverEntry, observer: IntersectionObserver): void {
const targetElement = entry.target as IWatcherElement
// Обработка базовых действий точек срабатывания
this.scrollWatcherIntersecting(entry, targetElement)
// Если есть атрибут data-watch-once убираем слежку
targetElement.hasAttribute('data-watch-once') && entry.isIntersecting
? this.scrollWatcherOff(targetElement, observer)
: null
// Создаем свое событие обратной связи
document.dispatchEvent(
new CustomEvent('watcherCallback', {
detail: {
entry,
},
})
)
}
}
// Запускаем и добавляем в объект модулей
flsModules.watcher = new ScrollWatcher({})