154 lines
5.6 KiB
TypeScript
154 lines
5.6 KiB
TypeScript
interface DynamicAdaptObject {
|
||
element: HTMLElement
|
||
parent: HTMLElement
|
||
destination: HTMLElement
|
||
breakpoint: string
|
||
place: string
|
||
index: number
|
||
}
|
||
|
||
type AdaptType = 'min' | 'max'
|
||
|
||
class DynamicAdapt {
|
||
private readonly type: AdaptType
|
||
private objects: DynamicAdaptObject[]
|
||
private daClassname: string
|
||
private nodes: HTMLElement[]
|
||
private mediaQueries: string[]
|
||
|
||
constructor(type: AdaptType) {
|
||
this.type = type
|
||
}
|
||
|
||
init(): void {
|
||
// массив объектов
|
||
this.objects = []
|
||
this.daClassname = '_dynamic_adapt_'
|
||
// массив DOM-элементов
|
||
this.nodes = Array.from(document.querySelectorAll('[data-da]')) as HTMLElement[]
|
||
|
||
// наполнение objects объeктами
|
||
this.nodes.forEach((node) => {
|
||
const data = node.dataset.da?.trim() || ''
|
||
const dataArray = data.split(',')
|
||
const object: DynamicAdaptObject = {
|
||
element: node,
|
||
parent: node.parentNode as HTMLElement,
|
||
destination: document.querySelector(dataArray[0].trim()) as HTMLElement,
|
||
breakpoint: dataArray[1] ? dataArray[1].trim() : '767',
|
||
place: dataArray[2] ? dataArray[2].trim() : 'last',
|
||
index: this.indexInParent(node.parentNode as HTMLElement, node),
|
||
}
|
||
this.objects.push(object)
|
||
})
|
||
|
||
this.arraySort(this.objects)
|
||
|
||
// массив уникальных медиа-запросов
|
||
this.mediaQueries = this.objects
|
||
.map(({ breakpoint }) => `(${this.type}-width: ${breakpoint}px),${breakpoint}`)
|
||
.filter((item, index, self) => self.indexOf(item) === index)
|
||
|
||
// навешивание слушателя на медиа-запрос
|
||
// и вызов обработчика при первом запуске
|
||
this.mediaQueries.forEach((media) => {
|
||
const mediaSplit = media.split(',')
|
||
const matchMedia = window.matchMedia(mediaSplit[0])
|
||
const mediaBreakpoint = mediaSplit[1]
|
||
|
||
// массив объектов с подходящим брейкпоинтом
|
||
const objectsFilter = this.objects.filter(({ breakpoint }) => breakpoint === mediaBreakpoint)
|
||
matchMedia.addEventListener('change', () => {
|
||
this.mediaHandler(matchMedia, objectsFilter)
|
||
})
|
||
this.mediaHandler(matchMedia, objectsFilter)
|
||
})
|
||
}
|
||
|
||
// Основна функция
|
||
private mediaHandler(matchMedia: MediaQueryList, objects: DynamicAdaptObject[]): void {
|
||
if (matchMedia.matches) {
|
||
objects.forEach((object) => {
|
||
this.moveTo(object.place, object.element, object.destination)
|
||
})
|
||
} else {
|
||
objects.forEach(({ parent, element, index }) => {
|
||
if (element.classList.contains(this.daClassname)) {
|
||
this.moveBack(parent, element, index)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// Функция перемещения
|
||
private moveTo(place: string, element: HTMLElement, destination: HTMLElement): void {
|
||
element.classList.add(this.daClassname)
|
||
if (place === 'last' || place >= destination.children.length.toString()) {
|
||
destination.append(element)
|
||
return
|
||
}
|
||
if (place === 'first') {
|
||
destination.prepend(element)
|
||
return
|
||
}
|
||
destination.children[Number.parseInt(place)].before(element)
|
||
}
|
||
|
||
// Функция возврата
|
||
private moveBack(parent: HTMLElement, element: HTMLElement, index: number): void {
|
||
element.classList.remove(this.daClassname)
|
||
if (parent.children[index] !== undefined) {
|
||
parent.children[index].before(element)
|
||
} else {
|
||
parent.append(element)
|
||
}
|
||
}
|
||
|
||
// Функция получения индекса внутри родителя
|
||
private indexInParent(parent: HTMLElement, element: HTMLElement): number {
|
||
return Array.from(parent.children).indexOf(element)
|
||
}
|
||
|
||
// Функция сортировки массива по breakpoint и place
|
||
// по возрастанию для this.type = min
|
||
// по убыванию для this.type = max
|
||
private arraySort(arr: DynamicAdaptObject[]): void {
|
||
if (this.type === 'min') {
|
||
arr.sort((a, b) => {
|
||
if (a.breakpoint === b.breakpoint) {
|
||
if (a.place === b.place) {
|
||
return 0
|
||
}
|
||
if (a.place === 'first' || b.place === 'last') {
|
||
return -1
|
||
}
|
||
if (a.place === 'last' || b.place === 'first') {
|
||
return 1
|
||
}
|
||
return 0
|
||
}
|
||
return Number.parseInt(a.breakpoint) - Number.parseInt(b.breakpoint)
|
||
})
|
||
} else {
|
||
arr.sort((a, b) => {
|
||
if (a.breakpoint === b.breakpoint) {
|
||
if (a.place === b.place) {
|
||
return 0
|
||
}
|
||
if (a.place === 'first' || b.place === 'last') {
|
||
return 1
|
||
}
|
||
if (a.place === 'last' || b.place === 'first') {
|
||
return -1
|
||
}
|
||
return 0
|
||
}
|
||
return Number.parseInt(b.breakpoint) - Number.parseInt(a.breakpoint)
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
const da = new DynamicAdapt('max')
|
||
da.init()
|