Как отследить перенос строк в браузере и обернуть их в div на чистом JS

Wrapp-им набор DOM элементов с помощью JavaScript

Предположим у вас на сайте генерируется такая структура DOM:

Как отследить перенос строк в браузере и обернуть их в div на чистом JS

Для того чтобы элементы заполняли все пространство в ширину идя один за другим мы обычно применяем CSS стили, например родителю задаем стиль display:flex; flex-wrap:wrap. Элементы выстраиваются в ряд как текст и переносятся на новую строку достигнув границы. Но сможете ли вы как-то стилизовать эти новые строки? без JS никак…

В этой статье я расскажу о том, как с помощью чистого JavaScript можно:

  1. Как отловить событие переноса строки
  2. Как обернуть новые строки в отдельные блоки
  3. Как автоматически переделывать блоки при изменении размеров экрана

Для начала посмотрите на результат который у меня получился, попробуйте изменять размеры экрана чтобы увидеть как изменяется количество блоков и элементов в них. Черным цветом выделены сами элементы, а красным и серым – подставляемая обертка div:

Как отловить событие переноса строки

Откровенно говоря такого события к сожалению нету в чистом виде. Чтобы понять где наступил перенос я буд сравнивать каждый элемент с предыдущим, а точнее его координату по высоте и в этом мне поможет метод getBoundingClientRect()

Итак, нам понадобятся переменные-помощники:

  const parent = document.querySelector(className);
 // получаем родителя
let items = [...parent.children];
 // массив всех элементов у родителя
let wrappedItems = [];
 // для координаты последнего перенесенного элемента
let prevItem = [];
 // для координаты предыдущего элемента
let currItem = [];
 // сюда кладем координату активного элемента
let obj = []; // это будет главный массив который передаем дальше

С помощью метода 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; 
    };

Логика такая:

  • Если предыдущего элемента нету то создаем новый объект внутри 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 ] },
 ]

Как обернуть новые строки в отдельные блоки

Все элементы которые разбиты по строкам собраны в массивы и нам теперь остается их развернуть добавив примесь собственной верстки. Поскольку у нас двойная вложенность и мы работаем с массивом, то нам доступен такой удобный метод перебора как map, с его помощью мы и будем разворачивать наш массив:

parent.innerHTML = wrappedItems.map( item => `<div class="wrapped">${item.a.map( elem => elem.outerHTML).join('') }</div>`).join('');

Поскольку map возвращает новый массив то мы сразу можем его записать в родителя через parent.innerHTML, таким образом перерисовыя контент в родителе. Также обратите внимание на использование метода join(”) он нам нужен чтобы перевести массив в строку и тем самым избежать появления запятых между элементами.

Отлично, теперь мы подошли к третьему главному вопросу, как перерисовывать верстку при изменении размеров экрана?

Как автоматически переделывать блоки при изменении размеров экрана

Для этого нам нужно

  1. С помощью window.onresize мы будем отслеживать изменения размеров окна
  2. Вернуть все как было до оборачивания в блоки, это важно чтобы позволить элементам свободно переноситься и создать новые строки в новых границах. Для этого мы мы еще раз пройдемся по элементам нашего массива и чтобы избавиться от оберток div просто возвратим их внутренности с помощью outerHTML. В результате родительский блок перезапишет внутренности через innerHTML.
  3. Заново запустить те операции которые уже были проделаны в начале, для этого удобно весь код разбить на три функции.
parent2.innerHTML = items2.map( item => [...item.children].map( item2 => item2.outerHTML ).join('')).join('');

Итак весь код 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>

Если у вас есть замечания или альтернативные способы реализации, пишите в комментариях.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

64 − = 58

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Открыть чат
1
Здравствуйте! Чем могу помочь?