185 lines
8.9 KiB
TypeScript
185 lines
8.9 KiB
TypeScript
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({})
|