Wrapp-им набор DOM элементов с помощью JavaScript
Предположим у вас на сайте генерируется такая структура DOM:
Для того чтобы элементы заполняли все пространство в ширину идя один за другим мы обычно применяем CSS стили, например родителю задаем стиль display:flex; flex-wrap:wrap. Элементы выстраиваются в ряд как текст и переносятся на новую строку достигнув границы. Но сможете ли вы как-то стилизовать эти новые строки? без JS никак…
В этой статье я расскажу о том, как с помощью чистого JavaScript можно:
- Как отловить событие переноса строки
- Как обернуть новые строки в отдельные блоки
- Как автоматически переделывать блоки при изменении размеров экрана
Для начала посмотрите на результат который у меня получился, попробуйте изменять размеры экрана чтобы увидеть как изменяется количество блоков и элементов в них. Черным цветом выделены сами элементы, а красным и серым – подставляемая обертка div:
Как отловить событие переноса строки
Откровенно говоря такого события к сожалению нету в чистом виде. Чтобы понять где наступил перенос я буд сравнивать каждый элемент с предыдущим, а точнее его координату по высоте и в этом мне поможет метод getBoundingClientRect()
Итак, нам понадобятся переменные-помощники:
const parent = document.querySelector(className);
// получаем родителя
let items = [...parent.children];
// массив всех элементов у родителя
let wrappedItems = [];
// для координаты последнего перенесенного элемента
let prevItem = [];
// для координаты предыдущего элемента
let currItem = [];
// сюда кладем координату активного элемента
let obj = []; // это будет главный массив который передаем дальше
Code language: JavaScript (javascript)
С помощью метода fore of перебираем наш массив items
for(const item of items) {
currItem = item.getBoundingClientRect().top;
if (prevItem.length == 0) {
obj.push({'a': [item]})
} else {
if (prevItem < currItem) {
wrappedItems = currItem;
obj.push({'a': [item]});
} else if (wrappedItems && wrappedItems === currItem) {
obj[obj.length-1]['a'].push(item);
} else {
obj[obj.length-1]['a'].push(item);
}
}
prevItem = currItem;
};
Code language: JavaScript (javascript)
Логика такая:
- Если предыдущего элемента нету то создаем новый объект внутри obj и помещаем в него активный элемент.
- Если предыдущий элемент есть и он на одинаковой высоте с активным то находим в массиве obj последний объект и помещаем активный элемент в него.
- Если предыдущий элемент находится выше активного то создаем новый объект внутри массива obj и помещаем в него активный элемент.
Итак, что мы получили? примерно такой массив:
obj.push( {'a': [item]} ); // пушим новый объект и первый элемент в него
obj[obj.length-1]['a'].push(item) // пушим элемент в самый последний объект
// в результате у нас появился такой массив:
obj = [
{'a': [ item, item, item ] },
{'a': [ item, item, item ] },
{'a': [ item, item, item ] },
]
Code language: JavaScript (javascript)
Как обернуть новые строки в отдельные блоки
Все элементы которые разбиты по строкам собраны в массивы и нам теперь остается их развернуть добавив примесь собственной верстки. Поскольку у нас двойная вложенность и мы работаем с массивом, то нам доступен такой удобный метод перебора как map, с его помощью мы и будем разворачивать наш массив:
parent.innerHTML = wrappedItems.map( item => `<div class="wrapped">${item.a.map( elem => elem.outerHTML).join('') }</div>`).join('');
Code language: JavaScript (javascript)
Поскольку map возвращает новый массив то мы сразу можем его записать в родителя через parent.innerHTML, таким образом перерисовыя контент в родителе. Также обратите внимание на использование метода join(”) он нам нужен чтобы перевести массив в строку и тем самым избежать появления запятых между элементами.
Отлично, теперь мы подошли к третьему главному вопросу, как перерисовывать верстку при изменении размеров экрана?
Как автоматически переделывать блоки при изменении размеров экрана
Для этого нам нужно
- С помощью window.onresize мы будем отслеживать изменения размеров окна
- Вернуть все как было до оборачивания в блоки, это важно чтобы позволить элементам свободно переноситься и создать новые строки в новых границах. Для этого мы мы еще раз пройдемся по элементам нашего массива и чтобы избавиться от оберток div просто возвратим их внутренности с помощью outerHTML. В результате родительский блок перезапишет внутренности через innerHTML.
- Заново запустить те операции которые уже были проделаны в начале, для этого удобно весь код разбить на три функции.
parent2.innerHTML = items2.map( item => [...item.children].map( item2 => item2.outerHTML ).join('')).join('');
Code language: JavaScript (javascript)
Итак весь код JavaScript выглядит так:
<script>
function detectWrap(className) {
const parent = document.querySelector(className);
let items = [...parent.children];
let wrappedItems = [];
let prevItem = [];
let currItem = [];
const obj = []; // главный массив который передаем дальше
for(const item of items) {
currItem = item.getBoundingClientRect().top; // координаты элемента
if (prevItem.length == 0) {
obj.push({'a': [item]}); //создать новый [] и поместить элемент
} else {
if (prevItem < currItem) {
wrappedItems = currItem; // то кидаем в массив
obj.push({'a': [item]}); //создать новый [] и поместить элемент
} else if (wrappedItems && wrappedItems === currItem) {
obj[obj.length-1].a.push(item);
} else {
obj[obj.length-1].a.push(item);
}
}
prevItem = currItem; // запоминаем последний
}
console.log(obj);
return obj;
}
function wrapp_lines(className){
let wrappedItems = detectWrap(className);
const parent = document.querySelector(className);
parent.innerHTML = wrappedItems.map( item => `<div class="wrapped">${item.a.map( elem => elem.outerHTML).join('') }</div>`).join('');
}
function resized_wrapp_lines(className){
const parent2 = document.querySelector(className);
const items2 = [...parent2.children];
// console.log(items2);
parent2.innerHTML = items2.map( item => [...item.children].map( item2 => item2.outerHTML ).join('')).join(''); // выводим отфильтрованные елементы в parent чтобы на основании их перерасположения определить TOP кажд ел.
wrapp_lines(className); // запускаем перерисовку
}
window.onload = function(){
wrapp_lines('.test');
window.onresize = function() {
resized_wrapp_lines('.test');
};
};
// obj.push( {'a': [item]} ); - пушим новый объект и первый элемент в него
// obj[obj.length-1]['a'].push(item) - пушим элемент в самый последний объект
// obj = [
// {'a': [ item, item, item ] },
// {'a': [ item, item, item ] },
// {'a': [ item, item, item ] },
// ]
</script>
Code language: JavaScript (javascript)
Если у вас есть замечания или альтернативные способы реализации, пишите в комментариях.