Как отследить перенос строк в браузере и обернуть их в 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 = []; // это будет главный массив который передаем дальше
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(”) он нам нужен чтобы перевести массив в строку и тем самым избежать появления запятых между элементами.

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

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

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

  1. С помощью window.onresize мы будем отслеживать изменения размеров окна
  2. Вернуть все как было до оборачивания в блоки, это важно чтобы позволить элементам свободно переноситься и создать новые строки в новых границах. Для этого мы мы еще раз пройдемся по элементам нашего массива и чтобы избавиться от оберток div просто возвратим их внутренности с помощью outerHTML. В результате родительский блок перезапишет внутренности через innerHTML.
  3. Заново запустить те операции которые уже были проделаны в начале, для этого удобно весь код разбить на три функции.
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)

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

Like this post? Please share to your friends:
Leave a Reply

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