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()